Section 13

13. Errors

Every error is the same envelope:

json
{"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.

CodeHTTPMeaning
auth.unauthorized401no credential
auth.invalid_api_key401unknown key
auth.key_expired / auth.key_revoked401key no longer valid
auth.missing_scope403key lacks a required scope
auth.forbidden403authenticated but not permitted
resource.not_found404unknown model, or not yours
resource.conflict409already exists / concurrent change
request.invalid400malformed request
request.unsupported_parameter400provider cannot honour a parameter
request.unprocessable422well-formed but semantically wrong
idempotency.key_required400mutating route needs the header
idempotency.key_conflict409key reused with a different body
rate_limit.exceeded429your rate limit
quota.exceeded429a plan ceiling
budget.exceeded429monthly allowance exhausted
upstream.rate_limited429the provider throttled us
upstream.timeout / upstream.unavailable / upstream.bad_gateway502/503/504provider failed
server.internal500our fault
server.not_implemented501route 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.