Guides
Responses & Errors
The two envelopes, numeric error codes, pagination and idempotency.
Envelopes
Console and guest — ApiResponse<T>:
{ "success": true, "data": {}, "message": null, "code": null, "errors": null }
Public — the partner envelope:
{ "ok": true, "data": [], "total": 120 }
{ "ok": false, "error": { "code": 409, "message": "…" } }
The code is the HTTP status
| Number | Name |
|---|---|
| 400 | IDEMPOTENCY_KEY_REQUIRED |
| 401 | UNAUTHORIZED · REAUTH_REQUIRED |
| 403 | FORBIDDEN · TENANT_MISMATCH |
| 404 | NOT_FOUND |
| 409 | CONFLICT · PERIOD_LOCKED |
| 422 | VALIDATION |
| 429 | RATE_LIMITED |
| 500 | INTERNAL |
Where a number is shared, the specific name is echoed in errors.code:
{
"success": false,
"message": "Business day 2026-09-01 is locked.",
"code": 409,
"errors": { "code": ["PeriodLocked"] }
}
Reading an error
success: true→ readdata.- Take the number:
codeif present, otherwise the HTTP status. - 422 →
VALIDATION;errorsis the field dictionary (field → messages). Do not readerrors.codeas a name here — a form may have a field calledcode. - Otherwise, if
errors.code[0]names a member (e.g.PeriodLocked), that name wins. - Otherwise use the number's own name from the table above. Unknown numbers are
INTERNAL. - Switch on the name — never on
message.
On /api/v1/public/**, read error.code. A refused credential, a missing scope, an unbindable
body or an unhandled fault there still arrives in the { success, … } shape — apply the steps
above to it.
Pagination
- Console lists:
page,per_page,search. - Public lists:
limit,offset; the response carriestotal.
Idempotency
Send Idempotency-Key: <uuid> on writes. It is mandatory on POST /api/v1/public/orders
and guest self-orders (missing → 400 IDEMPOTENCY_KEY_REQUIRED). A retry with the same key and
body returns the original result instead of creating a duplicate.