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

NumberName
400IDEMPOTENCY_KEY_REQUIRED
401UNAUTHORIZED · REAUTH_REQUIRED
403FORBIDDEN · TENANT_MISMATCH
404NOT_FOUND
409CONFLICT · PERIOD_LOCKED
422VALIDATION
429RATE_LIMITED
500INTERNAL

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

  1. success: true → read data.
  2. Take the number: code if present, otherwise the HTTP status.
  3. 422 → VALIDATION; errors is the field dictionary (field → messages). Do not read errors.code as a name here — a form may have a field called code.
  4. Otherwise, if errors.code[0] names a member (e.g. PeriodLocked), that name wins.
  5. Otherwise use the number's own name from the table above. Unknown numbers are INTERNAL.
  6. 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 carries total.

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.