Guarantees at a glance
| Surface | What you see |
|---|---|
x-to11-error-code header | Stable cross-provider classification — same value regardless of upstream or SDK. |
x-to11-upstream-provider header | Provider slug for the upstream that returned the error. |
| Response body (cross-provider routes) | Translated into the surface SDK’s native error envelope so SDK retry logic triggers. |
| Response body (same-provider routes) | Forwarded verbatim from the upstream. |
| Response body (native passthrough surfaces) | Forwarded byte-for-byte. |
| Response body (5xx errors) | Message redacted to provider returned status N. |
| HTTP status | Verbatim from the upstream. |
Headers always classify
Every error response carries two stable headers regardless of which provider, which SDK surface, or which routing strategy produced it:x-to11-error-code taxonomy is the canonical way to classify gateway errors. Values are stable across all combinations of upstream provider and surface SDK:
x-to11-error-code | Meaning |
|---|---|
auth | Credentials missing, invalid, or revoked (401). |
forbidden | Authenticated but not permitted (403). |
bad_request | Malformed request, including context-length and other 4xx conditions. |
quota | Account-level quota exhausted; retry will not clear. |
rate_limit | Throttled — retry with backoff will likely succeed. |
overloaded | Upstream under capacity pressure; retry with backoff. |
content_policy | Upstream content / safety filter blocked the request or response. |
model_not_found | Model name did not resolve at the upstream (404). |
org_verification_required | Gated-model verification flow required. |
upstream | Generic 5xx upstream condition or network fault (body redacted). |
feature_disabled | The requested routing surface is disabled for this project (403). |
Bodies on cross-provider routes match the surface SDK
When your SDK’s wire format does not match the upstream provider (for example, OpenAI SDK → Anthropic upstream), the gateway lifts the upstream’s native error envelope into its internal domain model and lowers it into the SDK’s surface envelope. The net effect: your SDK’s built-in retry policy fires correctly on cross-provider errors that previously would have surfaced as a generic gateway-shaped envelope. The classic example is Anthropic’s529 overloaded_error. Through an OpenAI SDK client, the gateway translates it to OpenAI’s rate_limit_error + rate_limit_exceeded body shape so the official OpenAI SDK’s automatic retry loop activates:
x-to11-error-code) stays the same no matter which provider produced the error or which SDK surface you called through.
Retry-After is threaded through
When the upstream response carries aRetry-After header (integer-seconds form), the gateway re-emits it on the translated response. SDKs that honour Retry-After (the official OpenAI and Anthropic SDKs both do) back off accordingly. When the upstream omits the header, the gateway does not synthesise a default — Retry-After is absent on the gateway response too, and the SDK falls back to its own backoff policy.
Same-provider routes remain verbatim
When the surface SDK and the upstream provider match (OpenAI SDK → OpenAI upstream, Anthropic SDK → Anthropic upstream), the gateway forwards the upstream error body verbatim. This preserves any provider-specific signal — header fields,param indicators, request IDs — that the SDK might inspect beyond the standard envelope shape.
The classification headers (x-to11-error-code, x-to11-upstream-provider) are still attached. The HTTP status is still verbatim.
Native passthrough remains byte-for-byte
Provider-native passthrough routes forward the upstream response byte-for-byte on both the success and error paths. These give you bit-identical compatibility with a provider’s native API, at the cost of cross-provider translation.5xx bodies are redacted
For an upstream 5xx, the response body’s message is rewritten to a conservativeprovider returned status N form. The upstream’s full message text never reaches the client, even if it would have carried useful debugging signal — that signal is preserved on the trace for the request, which you can inspect in the to11 dashboard (Projects → Traces), not in the response body.
A generic upstream 5xx is surfaced to the client as 502 Bad Gateway. The one exception is capacity overload, which is surfaced as 529 (the status Anthropic uses) and classified as overloaded. Either way the x-to11-error-code header reaches the client, so your SDK can classify and retry without depending on body text.
Examples
OpenAI SDK retrying through an Anthropic upstream overload
The official OpenAI Python SDK retries automatically onrate_limit_error bodies with Retry-After headers. With the gateway in front of an Anthropic upstream, an Anthropic 529 overloaded_error is translated to OpenAI’s rate_limit_exceeded body shape, so the retry loop just works:
rate_limit_exceeded + Retry-After, sleeps the requested interval, and retries — even though the upstream was Anthropic.
Inspecting x-to11-error-code for richer classification
When you need to differentiate quota-exhaustion (retry will not clear) from generic rate-limiting (retry with backoff clears) or from server-side overload, the response body’s error.code is intentionally lossy — it always maps to rate_limit_exceeded on the OpenAI surface so SDK retry behavior works uniformly. The x-to11-error-code header is where the precise classification lives:
Unrecognized envelopes
When an upstream returns an error shape the gateway does not recognize (an empty body, malformed JSON, or a new upstream error kind), it falls back to a generic error body rather than a translated one. Thex-to11-error-code header still classifies the error correctly, so retry and alerting logic built on the header keeps working regardless.
Mid-stream SSE error frames
When an upstream returns200 OK for a streaming request and then emits an error partway through the stream, the gateway delivers that error as an SSE error frame in the shape your SDK expects, and marks the stream aborted so no false success terminator follows:
- OpenAI surface —
data: {"error":{...}}(the OpenAI SDK parses the top-levelerrorkey into the appropriate exception class). - Anthropic surface —
event: errorfollowed bydata: {"type":"error","error":{...}}(the Anthropic SDK dispatches by SSE event name).
200 OK — the error lives in the response body, not on the status line.
Related
- API Reference — endpoints, status codes, request/response shapes.
- Telemetry — how error classification shows up in spans and metrics.
- Streaming — error-handling on the streaming path.