# Errors

> HTTP status codes, error envelopes, and recommended retry behavior for every AnyRouter API error.


# Errors

AnyRouter uses standard HTTP status codes and wraps every error in an OpenAI-compatible envelope:

```json
{
  "error": {
    "message": "Human-readable message",
    "type": "error_category",
    "code": "machine_code",
    "param": "optional_request_field"
  }
}
```

## 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. |
| `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. |
| `503` | `service_unavailable` | Yes | No healthy upstream available. |
| `504` | `upstream_timeout` | Yes | Upstream provider timed out. |

## Retry strategy

For retryable errors (`429`, `5xx`), use exponential backoff with jitter:

```typescript
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")
}
```

Honor the `Retry-After` response header when present — it tells you exactly how long to wait before the next attempt.

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

## Common error codes

| Code | Meaning |
|---|---|
| `invalid_api_key` | The `Authorization` header didn't match any AnyRouter key. |
| `insufficient_credits` | The account has run out of credits. Top up to continue. |
| `model_not_found` | The requested model slug doesn't exist in the catalog. |
| `context_length_exceeded` | Input is longer than the model supports. |
| `rate_limited` | Per-key or per-org rate limit exceeded. |
| `upstream_unavailable` | All upstream providers for this model are down. |
| `content_policy` | Upstream refused the request due to a content-policy filter. |

## Debugging

Every response includes an `X-AnyRouter-Request-Id` header. Include it when reporting bugs — it lets us find your exact request in the audit log.

```bash
curl -i https://anyrouter.dev/api/v1/chat/completions \
  -H "Authorization: Bearer ar-your-key" \
  -H "Content-Type: application/json" \
  -d '{"model": "openai/gpt-4-turbo", "messages": [{"role": "user", "content": "hi"}]}'
# HTTP/2 200
# x-anyrouter-request-id: req_01HQ...
```
