13. Errors
Every error is the same envelope:
{"error": {
"code": "auth.missing_scope",
"message": "missing required scope: catalog:read",
"details": null,
"request_id": "01a06a15-d526-7310-afb6-27482d2ac2a0",
"trace_id": "d286c5a55c5b2a710a77f15a4a3b5557"}}Branch on code, never on message. Codes are a closed set; messages
improve over time.
| Code | HTTP | Meaning |
|---|---|---|
auth.unauthorized | 401 | no credential |
auth.invalid_api_key | 401 | unknown key |
auth.key_expired / auth.key_revoked | 401 | key no longer valid |
auth.missing_scope | 403 | key lacks a required scope |
auth.forbidden | 403 | authenticated but not permitted |
resource.not_found | 404 | unknown model, or not yours |
resource.conflict | 409 | already exists / concurrent change |
request.invalid | 400 | malformed request |
request.unsupported_parameter | 400 | provider cannot honour a parameter |
request.unprocessable | 422 | well-formed but semantically wrong |
idempotency.key_required | 400 | mutating route needs the header |
idempotency.key_conflict | 409 | key reused with a different body |
rate_limit.exceeded | 429 | your rate limit |
quota.exceeded | 429 | a plan ceiling |
budget.exceeded | 429 | monthly allowance exhausted |
upstream.rate_limited | 429 | the provider throttled us |
upstream.timeout / upstream.unavailable / upstream.bad_gateway | 502/503/504 | provider failed |
server.internal | 500 | our fault |
server.not_implemented | 501 | route reserved, not built |
A buffered inference call may run for up to 900 s before the gateway answers upstream.timeout; the router's own upstream bound is 900 s as well and must never be set below the gateway's (both are NOZZLE_HTTP_REQUEST_TIMEOUT_SECS on the gateway and NOZZLE_UPSTREAM_TIMEOUT on the router, defaults pinned to the composes by test). Bound your own per-call timeouts at or below that, and use streaming for anything that legitimately runs long.
upstream.* versus rate_limit.exceeded is a meaningful distinction: the first
is the provider's limit and worth retrying, the second is yours and is not.
Retryable refusals carry Retry-After when the delay is known.