Errors
The relay's single error envelope and closed error-code set.
The envelope
Every non-2xx HTTP response, every SSE error event, and every WebSocket error frame carries the same JSON envelope:
{
"error": {
"code": "insufficient_credit",
"message": "the organization has insufficient credit for this request",
"retryable": false,
"request_id": "rreq_9f2c1a"
}
}codestringOne of the closed set below. New codes may be added over time, but codes are never repurposed.
retryablebooleanWhether retrying the same request (same idempotency key) can succeed without you changing anything.
request_idstringPresent when the request was assigned an ID. On request_already_started it names the original request.
Raw provider error bodies are never forwarded; provider failures are normalized into the codes below with fixed messages.
The code set
| HTTP status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Malformed body, missing Idempotency-Key, bad routing object, unknown fields |
| 400 | capability_unsupported | No routable provider supports what the request needs (for example, tools) |
| 401 | authentication_failed | Missing or invalid bearer token |
| 402 | insufficient_credit | Organization credit cannot cover the request's ceiling |
| 409 | request_in_progress | Same idempotency key + content, original still admitting — retryable |
| 409 | request_already_started | Same key + content, original already dispatched; carries the original request_id |
| 409 | idempotency_conflict | Same key, different content |
| 413 | payload_too_large | Body or audio part over the limits |
| 415 | unsupported_media | Audio container could not be parsed |
| 429 | rate_limited | Slow down |
| 429 | concurrency_exhausted | Organization or edge concurrency slots exhausted — retryable |
| 500 | relay_error | Internal relay failure |
| 502 | provider_error | The provider failed after output started |
| 503 | provider_unavailable | No provider currently available — retryable |
| — | budget_exhausted | Stream-only: a budget extension was refused mid-stream |
| — | lease_expired | Stream-only: the session's lease could not be renewed |
budget_exhausted and lease_expired have no HTTP status because they can only terminate an already-established stream — as the final WebSocket error frame or SSE error event.
Retry guidance
- Honor the
retryableflag first; it is per-error truth. - Retry with the same idempotency key so a request that actually started is not duplicated — you will get
request_already_startedwith the original request ID instead of a second charge. - Back off on
429and503; they indicate pressure, not a broken request. 4xxcodes other than409/429mean the request itself must change.