Errors¶
Errors use the same envelope everywhere, with a stable code you can branch on.
{
"success": false,
"error": {
"code": "validation_error",
"message": "name is required",
"details": { "field": "name" }
}
}
code— a stable machine-readable string (safe to switch on).message— human-readable, may change wording over time.details— optional structured context (the offending field, required scope, …).
The HTTP status code matches the error.
| HTTP | code |
Meaning |
|---|---|---|
| 400 | bad_request |
malformed request |
| 401 | unauthorized |
missing or invalid API key |
| 403 | forbidden |
key lacks the required scope (details.required) |
| 403 | limit_exceeded |
your plan's limit is reached; message names it (e.g. Limit: sales) |
| 404 | not_found |
the resource does not exist (or isn't yours) |
| 405 | method_not_allowed |
that HTTP method isn't supported on this path |
| 409 | conflict |
the request conflicts with current state (e.g. a sale with this external_id already exists — details.sale_id) |
| 422 | validation_error |
a field failed validation (details.field) |
| 422 | carrier_error |
the delivery service rejected the request (e.g. a waybill); message carries its text |
| 422 | promotion_* |
the promotion of a sale item was not applied, see Sale with a promotion |
| 429 | rate_limited |
over the rate limit — retry after Retry-After seconds |
| 500 | server_error |
an unexpected error on our side |
| 502 | carrier_error |
the delivery service did not answer (e.g. an address lookup) — retry later |
Branch on code, not message
Treat message as display text only. Program against code and the HTTP status.
Composite /query isolates errors per sub-query — a failing sub-query carries this same
error object in its slot while the others still return data. See
Composite query.