AnyRouter

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

StatusTypeRetry?Description
400invalid_request_errorNoMalformed request — fix and re-send.
401authentication_errorNoMissing or invalid API key.
403permission_errorNoKey lacks the required scope, or (code: ip_not_allowed) the client IP is not on the workspace IP allowlist.
404not_found_errorNoModel or resource does not exist.
409conflict_errorNoConflicting state (e.g. duplicate key name).
413request_too_largeNoInput exceeds the model's context window.
422invalid_request_errorNoRequest shape is valid but semantically wrong.
429rate_limit_errorYes, with backoffRate limit hit — honor Retry-After.
499client_closed_requestNoClient disconnected before response.
500internal_server_errorYesAnyRouter internal issue.
502upstream_errorYesUpstream provider returned a 5xx.
502upstream_error (code: empty_stream)YesThe provider answered with an empty stream. See Empty stream.
503service_unavailableYesNo healthy upstream available.
504upstream_timeoutYesUpstream 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.