Core concepts
Errors
The standard error envelope and every HTTP status the API returns.
Errors use standard HTTP status codes. Unlike successful responses, an error is not wrapped in a data envelope — it is a flat object you can read directly.
Error shape
Every error carries statusCode, a human-readable message, the requestId (quote it in support requests), a timestamp and the request path. Validation errors also include a fields map of per-field messages.
{
"statusCode": 400,
"message": "Validation failed",
"error": "Bad Request",
"fields": {
"email": ["email must be an email"],
"password": ["password must be longer than or equal to 8 characters"]
},
"requestId": "req_9f3a...",
"timestamp": "2026-09-06T09:00:00.000Z",
"path": "/api/v1/auth/login"
}message may be a single string or an array of strings when several validation messages apply. In production, server errors never include a stack trace.
Status codes
| Status | Meaning |
|---|---|
400 Bad Request | Validation failed — a missing/invalid field, an unknown field (bodies are strictly whitelisted), or a bad relation. Check fields. |
401 Unauthorized | Missing, expired or invalid access token. Log in again or refresh. |
403 Forbidden | Authenticated, but your role or company scope does not permit this action. |
404 Not Found | The resource does not exist, or is outside your company scope. |
409 Conflict | A uniqueness conflict (e.g. a duplicate code), or an idempotent replay of an already-applied write (idempotentReplay: true). |
412 Precondition Failed | A dependent capability is not configured (for example, starting a payment-gateway checkout when no gateway is set up). |
429 Too Many Requests | A rate limit was exceeded — see rate limits. |
500 Internal Server Error | An unexpected server error. Retry later and quote the requestId. |
504 Gateway Timeout | The request exceeded the server’s per-request time budget. Retry, or split the work. |
Idempotent replays
When you retry a write that carried an Idempotency-Key and the original already succeeded, the API returns 409 with idempotentReplay: true. This is not a failure — the change was applied exactly once. Treat it as success and stop retrying.
{
"statusCode": 409,
"message": "This change was already applied.",
"idempotentReplay": true,
"requestId": "req_...",
"timestamp": "2026-09-06T09:00:00.000Z",
"path": "/api/v1/customers"
}Always log the requestId
requestId (in the error body, in meta on success, and in the X-Request-Id response header). Logging it makes any issue far faster to trace.