# Errors

Claudech uses conventional HTTP status codes and returns a consistent JSON envelope for every error, on every endpoint.

## The error envelope

```json
{
  "error": {
    "message": "The model 'claud-ultra' does not exist or you do not have access to it.",
    "type": "invalid_request_error",
    "code": "model_not_found",
    "param": "model",
    "request_id": "req_01J9X3Q5K7M2N8P4R6T0V2W4Y6"
  }
}
```

- `message` (string): A human-readable description. Safe to log; never contains secrets.
- `type` (string): The error family (see below). Use it for coarse handling such as "retry" versus "fix the request".
- `code` (string): A stable machine-readable code. Branch on this, not on `message`.
- `param` (string): Present when the error relates to a specific request field, for example `messages` or `model`.
- `request_id` (string): Identifies the request in Claudech's logs. Include it when contacting support. Also returned in the `x-request-id` header on every response.

For `429` and some `503` errors a `retry-after` header tells you how many seconds to wait.

### Anthropic-format endpoints

Errors raised by `POST /v1/messages` follow the Messages API convention so the Anthropic SDKs surface them correctly. The `code` and `request_id` fields are the same as above:

```json
{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "max_tokens: Invalid input: expected number, received undefined",
    "code": "invalid_request",
    "request_id": "req_01J9X3Q5K7M2N8P4R6T0V2W4Y6"
  }
}
```

Authentication failures on that endpoint use the standard envelope shown first; both SDK families read `error.message` from it.

## Error types

| `type` | Status | Meaning |
| --- | --- | --- |
| `invalid_request_error` | 400, 404, 413 | The request is malformed or refers to something that does not exist. Fix the request; retrying will not help. |
| `authentication_error` | 401 | The key is missing, malformed, expired or revoked. |
| `permission_error` | 403 | The key is valid but the account or plan is not allowed to do this. |
| `insufficient_balance_error` | 402 | Your token balance is too low to start the request. [Top up](https://claudech.com/billing). |
| `not_found_error` | 404 | Unknown route or resource. |
| `conflict_error` | 409 | The action conflicts with current state (for example rotating a key that is already revoked). |
| `rate_limit_error` | 429 | A per-minute, concurrency or plan cap was hit. Honour `retry-after`. |
| `provider_error` | 502, 503, 504 | The upstream model failed, timed out or is paused. Usually transient; retry with back-off. |
| `maintenance_error` | 503 | Claudech is in scheduled maintenance. |
| `configuration_error` | 503 | A model is misconfigured on our side (for example no published price). We are alerted automatically. |
| `server_error` | 500 | Something unexpected happened. Retry once, then contact support with the `request_id`. |

## Error codes

### Request validation (400)

| `code` | Notes |
| --- | --- |
| `invalid_request_error` | Schema validation failed. `message` names the field and the problem, e.g. `messages.0.content: Required`. |
| `invalid_request` | A semantic problem such as too many messages; `param` names the field. |
| `context_length_exceeded` | The prompt exceeds the model's 1 million token context window. Shorten the prompt. |
| `payload_too_large` | The request body exceeds the size limit (`413`). |

### Authentication (401)

| `code` | Notes |
| --- | --- |
| `missing_api_key` | No `Authorization` header, or not using the `Bearer` scheme. |
| `invalid_api_key` | The key is unknown, expired or revoked. |

### Permission (403)

| `code` | Notes |
| --- | --- |
| `email_not_verified` | Verify the account's email address first. |
| `account_inactive` | The account is suspended or scheduled for deletion. |
| `account_blocked`, `api_key_blocked`, `ip_blocked` | Blocked for abuse. Contact support if you believe this is a mistake. |
| `model_not_in_plan` | The model is not included in the current plan (`param: "model"`). |
| `api_key_limit` | You have reached the maximum number of active keys for your plan. |
| `key_management_requires_session` | Creating, rotating or revoking keys requires a dashboard session, not an API key. |

### Models (404)

| `code` | Notes |
| --- | --- |
| `model_not_found` | Unknown model slug or alias, or the model is disabled. Use `GET /v1/models` for the current list. |

### Balance (402)

| `code` | Notes |
| --- | --- |
| `insufficient_balance` | Not enough tokens to cover the worst-case cost of the request. Claudech reserves an estimate up front and settles the actual cost afterwards, so a balance that looks "just enough" may still be rejected. |

### Rate limits and caps (429)

| `code` | Notes |
| --- | --- |
| `rate_limit_exceeded` | Requests per minute. |
| `tokens_per_minute_exceeded` | Tokens per minute (prompt estimate + expected output). |
| `concurrency_limit_exceeded` | Too many in-flight requests. |
| `daily_limit_exceeded`, `monthly_limit_exceeded` | A plan cap was reached; resets at UTC midnight / month start. |

See [Rate limits](https://claudech.com/docs/rate-limits) for the numbers per plan and the headers to watch.

### Upstream and platform (5xx)

| `code` | Status | Notes |
| --- | --- | --- |
| `provider_error` | 502 | The provider returned an error. Not billed. |
| `provider_timeout` | 504 | The provider did not respond in time. Not billed. |
| `provider_unavailable` | 503 | The provider is overloaded, or the feature is temporarily disabled. `retry-after: 10`. |
| `spending_limit_reached` | 503 | A cost-protection budget paused the model; automatically resumes. `retry-after: 60`. |
| `maintenance_mode` | 503 | Scheduled maintenance. `retry-after: 300`. |
| `configuration_error` | 503 | Model configuration problem on our side. |
| `internal_error` | 500 | Unexpected failure. |

## Common error messages

### "Provider returned error"

Many AI API services show this text when the model behind them fails. On Claudech the same situation returns `502` with the code `provider_error` and the message "The upstream model provider returned an error." These requests are not billed. Retry with back-off; if one model keeps failing, try another model and send support the `request_id`.

### "Invalid beta flag"

Anthropic-format clients such as Claude Code and the Anthropic SDKs can send an `anthropic-beta` header that turns on experimental features. Endpoints that do not recognise a flag reject the request with "invalid beta flag". Claudech's `POST /v1/messages` accepts the header and ignores it, so this error does not come from Claudech. If you see it, the client is still sending requests somewhere else: in Claude Code, check that `ANTHROPIC_BASE_URL` is exactly `https://api.claudech.com` ([Claude Code setup](https://claudech.com/docs/integrations/claude-code)).

### "Rate limit reached"

A `429` with `rate_limit_exceeded`, `tokens_per_minute_exceeded` or `concurrency_limit_exceeded`. Wait for the number of seconds in `retry-after` and send the request again. The limits for each plan are on [Rate limits](https://claudech.com/docs/rate-limits).

### "Insufficient balance"

A `402` with `insufficient_balance`: the balance cannot cover the worst-case cost of the request. Top up under [Billing](https://claudech.com/billing); retrying without topping up fails the same way.

## Errors during a stream

If a stream has already started when something fails, the HTTP status is already `200`. Claudech then sends an error event on the stream, followed by `data: [DONE]`:

```text
data: {"error":{"message":"The model provider timed out.","type":"provider_error","code":"provider_timeout","request_id":"req_..."}}

data: [DONE]
```

Any tokens generated before the failure are billed; the reservation for the rest is released. See [Streaming](https://claudech.com/docs/streaming).

## Handling errors well

**JavaScript**

```javascript
import OpenAI, { APIError, RateLimitError } from 'openai';

const client = new OpenAI({ apiKey: process.env.CLAUDECH_API_KEY, baseURL: 'https://api.claudech.com/v1', maxRetries: 3 });

try {
  const res = await client.chat.completions.create({ model: 'claude-opus-5-5', messages });
} catch (err) {
  if (err instanceof RateLimitError) {
    // The SDK already retried with back-off; surface a friendly message.
  } else if (err instanceof APIError) {
    const code = err.error?.error?.code ?? err.code;
    if (code === 'insufficient_balance') notifyBillingOwner();
    else if (code === 'context_length_exceeded') truncateHistoryAndRetry();
    else console.error(`Claudech error ${code} (request ${err.headers?.['x-request-id']})`);
  } else {
    throw err;
  }
}
```

**Python**

```python
from openai import OpenAI, APIError, RateLimitError

client = OpenAI(api_key=CLAUDECH_API_KEY, base_url="https://api.claudech.com/v1", max_retries=3)

try:
    res = client.chat.completions.create(model="claude-opus-5-5", messages=messages)
except RateLimitError:
    # The SDK already retried with back-off; surface a friendly message.
    ...
except APIError as err:
    code = (err.body or {}).get("error", {}).get("code") if isinstance(err.body, dict) else None
    if code == "insufficient_balance":
        notify_billing_owner()
    elif code == "context_length_exceeded":
        truncate_history_and_retry()
    else:
        print("Claudech error", code, "request", err.request_id)
```

- **Retry** `429`, `502`, `503` and `504` with exponential back-off and jitter, respecting `retry-after`. The OpenAI SDKs do this by default.
- **Do not retry** `400`, `401`, `402`, `403` or `404`; the same request will fail again.
- **Log `request_id`** on every failure. It lets support trace the exact request across the gateway, the ledger and the provider.

---

Source: https://claudech.com/docs/errors
