Errors
HTTP status codes, the error envelope, and recommended retry behavior for every AnyRouter API error.
AnyRouter uses standard HTTP status codes and wraps every error in a consistent envelope. Read the status code to decide whether to retry, and the code field to branch on the specific failure.
{
"error": {
"message": "Human-readable message",
"code": "machine_code",
"metadata": {
"type": "error_category"
}
}
}Status codes
| Status | Type | Retry? | Description |
|---|---|---|---|
400 | invalid_request_error | No | Malformed request — fix and re-send. |
401 | authentication_error | No | Missing or invalid API key. |
403 | permission_error | No | Key lacks the required scope, or (code: ip_not_allowed) the client IP is not on the workspace IP allowlist. |
404 | not_found_error | No | Model or resource does not exist. |
409 | conflict_error | No | Conflicting state (e.g. duplicate key name). |
413 | request_too_large | No | Input exceeds the model's context window. |
422 | invalid_request_error | No | Request shape is valid but semantically wrong. |
429 | rate_limit_error | Yes, with backoff | Rate limit hit — honor Retry-After. |
499 | client_closed_request | No | Client disconnected before response. |
500 | internal_server_error | Yes | AnyRouter internal issue. |
502 | upstream_error | Yes | Upstream provider returned a 5xx. |
502 | upstream_error (code: empty_stream) | Yes | The provider answered with an empty stream. See Empty stream. |
503 | service_unavailable | Yes | No healthy upstream available. |
504 | upstream_timeout | Yes | Upstream provider timed out. |
Never retry 4xx errors other than 429. A 401 will keep returning 401 until you fix the auth header — retrying just burns rate-limit budget.
Empty stream
A 502 with type: upstream_error and code: empty_stream means the provider ended the response without returning any content. It can occur on /chat/completions, /responses and /messages.
Where other providers are available for the model, AnyRouter has already tried them before returning this error, so the failure is not caused by your request. It is retryable: re-send the request with backoff. If it keeps happening for one model, try a different model or provider.
Retry retryable errors
For retryable errors (429, 5xx), use exponential backoff with jitter, and honor the Retry-After header when present:
async function withRetry<T>(fn: () => Promise<T>, attempts = 5): Promise<T> {
for (let i = 0; i < attempts; i++) {
try {
return await fn()
} catch (err) {
if (i === attempts - 1) throw err
if (!isRetryable(err)) throw err
const base = Math.min(1000 * 2 ** i, 30_000)
const jitter = Math.random() * 250
await new Promise((r) => setTimeout(r, base + jitter))
}
}
throw new Error("unreachable")
}Common error codes
The code field names the exact failure. Expand each for what it means and how to fix it.
The route requires a signed-in session or bearer key. Add an Authorization: Bearer header with a valid key.
The Authorization header was required but missing. Send Authorization: Bearer sk-ar-your-key.
The supplied bearer key was invalid, revoked, or inactive. Create a fresh key in the dashboard.
The account has run out of credits for a paid-model request. Add credits or subscribe. Free-tier models do not use this code.
When you see 403
A 403 is a permission gate — retry the same request will not help; you must change something on the account, the key, or the model. The ones that come up most:
Your key's allow-list excludes the requested model (per-key routing preferences), or the model is disabled for your account. Edit the key's allow-list in the dashboard or pick another provider/model from the catalog.
Your key lacks the scope for this endpoint (e.g. a management key on /chat/completions, or an inference key on /keys). Mint a key with the right group in the dashboard.
Zero data retention (ZDR) is on, for your account, key, preset or request, and it excludes every upstream that serves this model, so there is no route left to send the request to. Turn ZDR off in Privacy settings (or remove it from the key, preset or request), or pick a model that has a ZDR route. If the message says your privacy settings could not be read, nothing changed on your side: retry in a moment.
The requested model slug doesn't exist in the catalog. Use a full provider/model id from the catalog.
The model is announced and listed as Coming soon in the catalog, but it cannot serve requests yet (404). Use a live model until its page shows pricing and providers.
Input is longer than the model supports. Shorten the prompt or choose a model with a larger context window.
Per-key or per-IP rate limit exceeded (rate_limit_exceeded, ip_rate_limit_exceeded). Back off and honor the Retry-After header.
All upstream providers for this model are down. Retry, or route to a different model.
An upstream refused the request due to a content-policy filter. Adjust the prompt.
Debugging
Every inference response includes an X-Request-ID header (also sent under the canonical name X-AnyRouter-Trace-Id — the two carry the same value). Include it when reporting bugs — it lets us trace your exact request lifecycle across auth, rate-limit, routing, and upstream dispatch.
curl -i https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{"model": "openai/gpt-5.4-mini", "messages": [{"role": "user", "content": "hi"}]}'
# HTTP/2 200
# x-request-id: req_01HQ...Send a request-body trace object on Chat Completions or Responses and AnyRouter carries your trace_id through the internal step logs and AI Gateway correlation metadata — useful for lining up your app traces with AnyRouter routing behavior.