Errors & rate limits
Error format
All errors return JSON in the same shape:
{
"error": {
"code": "insufficient_scope",
"message": "Token is missing required scope: orders:write",
"required_scopes": ["orders:write"]
}
}
Status codes
| Status | Code | Meaning |
| ------ | ---------------------- | ------------------------------------------------ |
| 400 | invalid_request | Malformed body or parameters |
| 401 | unauthorized | Missing, invalid, expired, or revoked token |
| 403 | insufficient_scope | Token lacks a required scope |
| 404 | not_found | Resource does not exist, or belongs to another account |
| 409 | conflict | State conflict (e.g. order already dispatched) |
| 409 | duplicate_external_reference | external_reference is already in use. Pick another — see conventions |
| 409 | cancellation_window_closed | Past the one-hour window. Carries placed_at and cancellable_until |
| 409 | out_of_stock | Cannot be made. Carries an oversold list |
| 422 | validation_failed | Field-level validation failed |
| 429 | rate_limited | Rate limit exceeded |
| 5xx | server_error | Transient — retry with exponential backoff |
Rate limits
There are two independent limits. Whichever you hit first returns 429 rate_limited.
| Scope | Limit | Applies to |
| ----------- | ------------------- | -------------------------------------------------- |
| Access token | 120 req/min | Every /api/v1/* call made with that token |
| Client IP | 600 req/min | All requests from one IP, including rejected tokens |
The per-token limit is the one you'll normally plan against. The per-IP limit is an abuse backstop and sits high enough that ordinary integrations never reach it — but you can hit it if you drive many tokens from a single host, or if a bug sends a flood of calls with an invalid token (those are counted per-IP, since there's no valid token to charge them to).
Both are sliding windows. Rate-limit headers accompany responses:
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 117
X-RateLimit-Reset: 1712937600
On a 429, a Retry-After header tells you (in seconds) when to try again, and
X-RateLimit-Scope is token or ip so you can tell which limit you hit.
The OAuth token endpoint (/api/oauth/*) is limited separately and more tightly —
it's a human-paced flow, so a client refreshing normally will never approach it.
Best practices
- Respect
Retry-After— don't retry tighter than the server tells you to. - Back off exponentially on
5xxresponses. - Cache immutable reference data (catalog, tax tables) to reduce call volume.
- Paginate using cursors; do not scan whole collections in tight loops.