Skip to content

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.
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.
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.

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.

authentication_required

The route requires a signed-in session or bearer key. Add an Authorization: Bearer header with a valid key.

missing_api_key

The Authorization header was required but missing. Send Authorization: Bearer sk-ar-your-key.

invalid_api_key

The supplied bearer key was invalid, revoked, or inactive. Create a fresh key in the dashboard.

insufficient_balance

The account has run out of credits for a paid-model request. Add credits or subscribe. Free-tier models do not use this code. They use the daily cap (429 free_tier_daily_limit_exceeded) instead.

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 three that come up most:

model_not_allowed

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.

permission_error

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.

model_not_found

The requested model slug doesn't exist in the catalog. Use a full provider/model id from the catalog.

context_length_exceeded

Input is longer than the model supports. Shorten the prompt or choose a model with a larger context window.

rate_limit_exceeded

Per-key or per-IP rate limit exceeded (rate_limit_exceeded, ip_rate_limit_exceeded). Back off and honor the Retry-After header.

free_tier_daily_limit_exceeded

You used today's free-model allowance (anyrouter/free or a :free variant). Free is 10 requests/day. Paid plans are 1000/day. The cap resets at 00

UTC. Add credits or subscribe. Paid models do not consume this cap.

upstream_unavailable

All upstream providers for this model are down. Retry, or route to a different model.

content_policy

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.

Related