LLM API error codes, explained and fixed
Check the HTTP status, the error type and code, and the stream's terminal event together. A 400 can mean the wrong model endpoint; 402 means a billing limit; 429 means rate limiting. An HTTP 200 stream can still end with response.failed. Start with the final request row in Dashboard → Logs and keep the request_id so support can correlate intermediate attempts.
Quick reference
The first thing to check for each status code:
| Code | Meaning | First thing to check |
|---|---|---|
| 400 | Invalid request, or the wrong endpoint for that model | The message names the problem: a Responses feature the gateway does not run (background, image_generation), an incomplete response_format envelope, or a model that has to be called on another path — model '<id>' must be called via /v1/messages or /v1/chat/completions. |
| 401 | Invalid or missing API key | The Authorization header carries your key as `Bearer sk-…` — check for typos, extra whitespace, or an empty env var in the shell that runs your client. |
| 402 | Insufficient available funds or key budget | Read the error message and any X-Billing-Funds-Reason header. Funds may be reserved by in-flight requests, or the key's maxSpend may be reached. A wallet top-up does not raise a key's spend cap. |
| 403 | Forbidden | The key is disabled, or the request hit a permission boundary. Verify the key's status in the dashboard and confirm you're calling the right base URL for your client. |
| 404 | Unknown route or model | The path or model ID doesn't exist. Copy the exact model ID from the /models page — IDs are case-sensitive. |
| 429 | Rate limit exceeded | You hit the key's rateLimit / tokenLimitTpm ceiling or an upstream limit. Check Dashboard → Logs, slow the request rate, or contact support to raise limits. |
| 499 | Client cancelled the request | Your client closed the connection before the response finished — a stop button, an SDK timeout, or a dropped network. Not an upstream failure and never failed over: on /v1/chat/completions and /v1/responses the trace records the usage the upstream reported (see the streaming guide for the settlement rule). |
| 500 | Internal server error | Transient server-side failure. Retry with backoff; contact support if it persists. |
| 502 | Upstream response failed | Inspect the error body or stream event and keep the request_id. A 502 alone does not show whether every eligible route failed or whether a nested error was a rate limit. |
| 503 | Service or model route temporarily unavailable | Check the catalog and error code. PROVIDER_UNAVAILABLE can mean that no eligible route is available now; it does not by itself mean the model ID is unknown. Retry with bounded backoff if the error is transient. |
| 504 | Upstream wait timed out | Router One can normalize a timeout or deadline error to 504 PROVIDER_UNAVAILABLE. It does not prove the upstream returned HTTP 504. Keep the error message, endpoint, request_id and time; distinguish a timeout waiting for headers from an interrupted stream. |
| 529 | Upstream overloaded | The model's upstream is saturated. If the error is retryable and another healthy provider serves the same exact model, Router One can try that route; otherwise retry with bounded backoff. |
| Timeout | No response in time | Identify which layer stopped waiting: your SDK, a proxy, the gateway, or the upstream. A client timeout can occur without an HTTP error response. Compare its configured deadline with response headers, stream events and the final request record. |
402 with balance remaining: wallet funds or a key cap?
When a 402 response includes X-Billing-Funds-Reason, use that value with the error message to distinguish available funds from temporary reservations and key limits. These diagnostics depend on the returned response; if a client does not expose them, retain the message and request_id for support instead of inferring a reason from the dashboard balance alone.
| Reason, when present | Next step |
|---|---|
| wallet_reserved | Some wallet funds are held by in-flight requests. Let them settle and check the available amount again; final charges may reduce what is released. |
| key_reserved | In-flight requests reserve part of this key's budget. Wait for settlement or adjust its maxSpend in Dashboard → API Keys. A wallet top-up does not lift this limit. |
| key_cap | The key's available spend budget is too small. Raise or clear its maxSpend if appropriate; adding wallet funds does not change the cap. |
| wallet_insufficient | Available wallet funds are below the required initial reservation. Review the request size and available amount, then reduce the input or top up. |
- If present, X-Billing-Available-Balance-USD and X-Billing-Reserved-Balance-USD distinguish available wallet funds from funds already held; X-Billing-Minimum-Required-USD describes the minimum initial reservation for admission, not the final bill.
- For a capped key, X-Billing-Available-Key-Budget-USD and X-Billing-Reserved-Key-Budget-USD describe the separate key budget. Released reservations do not guarantee the next request will fit; final charges and other concurrent requests can change availability.
Why did HTTP 200 end with response.failed?
HTTP 200 on an SSE connection means the stream started; it does not prove generation completed. For Responses, read response.error inside response.failed as well as top-level error events. A rate_limit_error or Too many pending requests message points to rate limiting; response.incomplete needs its incomplete_details inspected. Preserve partial output, and treat an unexpected disconnect before a terminal event as incomplete. Once output has reached the client, do not assume the gateway can replay the request on another route.
event: response.failed
data: {"type":"response.failed","response":{"status":"failed","error":{"type":"rate_limit_error","message":"Too many pending requests, please retry later"}}}504 or SDK timeout: which layer stopped waiting?
A timeout describes an elapsed limit, not a complete diagnosis. Start with whether an HTTP response arrived and whether any model output reached the client. Keep the endpoint, model ID, request_id when available, time and timezone, elapsed duration, and exact error text. Do not include your API key. Support can correlate the final request with intermediate attempts; the status alone cannot identify the responsible network hop.
| Observed evidence | What it establishes | Next check |
|---|---|---|
| SDK timeout with no HTTP response | The client stopped waiting; it does not establish an upstream HTTP error. | Check connect/read/total limits and local network or proxy errors. If the request reached the gateway and its connection was closed, the final log may record client cancellation. |
| Client.Timeout exceeded while awaiting headers in the correlated upstream attempt | The gateway's HTTP client timed out before it received upstream response headers. A normalized 504 is not proof of an upstream HTTP 504 response. | Ask support to check the raw attempt and its timeout setting. Do not assume the SDK timeout or the same setting applies to every endpoint. |
| HTTP 200, then an error event or unexpected EOF | The SSE transport opened, but generation may have failed or remained incomplete. | Inspect the terminal event and preserve partial output. Check the streaming guide before retrying; an already delivered stream cannot be assumed to restart on another route. |
- The long-wait policy for a named model's native /v1/responses stream does not apply to every protocol or non-streaming request. Client, proxy and upstream limits still matter, and model:auto retains its candidate budgets.
- Only a real upstream HTTP response establishes the upstream's status code. Gateway-normalized errors and individual failed attempts must be distinguished from the final outcome, which may succeed after an eligible fallback.
Read the trace before guessing
Router One's customer-facing request row shows the model, tokens, settled cost, latency, and status. Dashboard → Logs filters by model and date, so you can see whether an error is isolated or recurring. It does not expose the provider or intermediate attempts; share the request_id with support when the route or fallback chain needs investigation. Requests awaiting pricing may not appear yet, so an absent row is not proof of a zero charge.
Errors in Claude Code
Claude Code talks to the Anthropic-compatible endpoint at https://api.router.one — note: no /v1 suffix. Most 401/403s in Claude Code come from environment variables that are missing, exported in a different shell, or overridden by an earlier official-API login. Claude Code authenticates against a gateway with ANTHROPIC_AUTH_TOKEN; ANTHROPIC_API_KEY is not required and triggers an extra approval prompt when it is set. A 400 whose message reads model '<id>' must be called via /v1/chat/completions is a different problem: the model ID you configured is not served on the Anthropic-compatible endpoint, so pick one whose detail page lists POST /v1/messages (currently listed Claude models) or call that model on /v1/chat/completions instead. For the auth case, set these and restart the terminal:
export ANTHROPIC_BASE_URL=https://api.router.one export ANTHROPIC_AUTH_TOKEN=sk-your-router-one-key unset ANTHROPIC_API_KEY
Errors in Codex CLI
Codex CLI speaks the Responses API wire format, which many OpenAI-compatible relays don't implement — that's why Codex 404s against them. Router One supports wire_api = "responses" natively. Configure Codex in ~/.codex/config.toml with base_url = "https://api.router.one/v1", wire_api = "responses", and env_key = "ROUTER_ONE_API_KEY", then export ROUTER_ONE_API_KEY with your Router One key. Codex does not read OPENAI_BASE_URL or OPENAI_API_KEY for a custom provider, and requires_openai_auth makes it ignore env_key entirely. A 400 invalid_request that mentions background or image_generation is the gateway refusing two Responses features it does not run: drop background = true, and generate images through /v1/images/generations instead of the image_generation tool; hosted tools and previous_response_id work on models served natively over the Responses wire format. A 400 that reads model '<id>' must be called via /v1/messages or /v1/chat/completions is a different case: a Claude-family model ID reached the Responses endpoint, where Claude is not served — point Codex at a currently listed GPT-family model and use Claude Code for Claude. Full walkthrough on the Codex Responses API page.
FAQ
My key looks correct but I still get 401. Why?
The three usual causes: the env var is set in a different shell (or profile file) than the one running your client; the key was pasted with a trailing space or newline; or the client reads a different variable than the one you set. In the same session, check that the expected variable is set without printing the key or including it in screenshots.
Which Authorization header mistakes cause a 401 most often?
The value must be exactly Bearer followed by one space and the key. The mangled forms we see most: the scheme added twice (Bearer Bearer sk-…) because a client prefixes it and the env var already contains it; the key wrapped in quotes that were part of the pasted value; a newline or space inside the key; the Authorization header present but empty, or x-api-key sent empty while Authorization is missing; a dashboard session token (a JWT) sent where an API key is expected; and Authorization: sk-… with no scheme at all. Fix the value, not the key — a valid key mangled this way is rejected before any lookup.
What's the difference between 402 and 429?
402 concerns the funds or key budget available for a request, including amounts held by in-flight requests; read any X-Billing-Funds-Reason diagnostics before topping up. 429 concerns request or token limits, or an upstream limit. Check the response code and message, then correlate the request in Dashboard → Logs when a record is available.
Does 504 PROVIDER_UNAVAILABLE prove the upstream returned HTTP 504?
No. Router One can produce that status by normalizing a timeout or deadline error, even if no upstream response headers arrived. A client-side SDK timeout is another case and may have no HTTP response at all. Preserve the exact message and request_id when available so support can separate the raw upstream attempt, any fallback, and the final request outcome.
Do 5xx errors mean I have to write retry logic?
An eligible 5xx or timeout can be retried on another healthy provider serving the same exact model, when one is available. That is not guaranteed for every error, so use bounded backoff for failures returned to your app and contact support if the pattern continues.
I got HTTP 200 with an empty message. Should my client retry?
First distinguish a completed Chat Completions reply from a failed stream. A completed reply with non-zero usage can be empty because of reasoning or refusal; inspect finish_reason, refusal and usage before retrying, since repeating it can incur another charge. HTTP 200 with response.failed, an error event, or an interrupted stream is a different case: inspect the terminal event and preserve the request_id. A truly empty upstream reply with no usage is treated as an upstream error.
Does response.failed with Too many pending requests mean my key is invalid?
No. A nested rate_limit_error indicates rate limiting, not an authentication failure. Reduce concurrency and retry with bounded backoff. Keep the request_id, model ID, endpoint, time and timezone, and error type/code for support; do not include your API key. The error alone does not prove which route was tried or whether fallback ran.
Why does Claude Code return 403 while the same key works with curl?
curl talks to the OpenAI-compatible endpoint; Claude Code needs the Anthropic-compatible base URL without the /v1 suffix, plus ANTHROPIC_AUTH_TOKEN. ANTHROPIC_API_KEY is not required — leave it unset. See the dedicated Claude Code 403 guide for the full checklist.
Where do I see exactly which request failed and why?
Dashboard → Logs shows request status, model, tokens, settled cost, and latency, with model and date filters. The customer view does not show the provider or fallback chain. Keep the request_id from the response when available, including while a row is not yet visible, so support can investigate the final outcome and intermediate attempts.
I get unsupported_country_region_territory or insufficient_quota — are those Router One errors?
No — both come from the official OpenAI API, not the gateway. unsupported_country_region_territory is a region block on the platform edge; insufficient_quota is exhausted billing credit disguised as a 429. Each has a dedicated fix guide: the region error page and the insufficient_quota page.