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 5xx responses.
  • Cache immutable reference data (catalog, tax tables) to reduce call volume.
  • Paginate using cursors; do not scan whole collections in tight loops.