> ## Documentation Index
> Fetch the complete documentation index at: https://docs.protege.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> Status codes, the error body, and which failures are worth retrying.

Errors use standard HTTP status codes and a consistent body.

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_task_name",
    "message": "task must match ^[a-z0-9_-]{1,64}$",
    "param": "task"
  }
}
```

<ResponseField name="error.type" type="string">
  Broad category: `invalid_request_error`, `authentication_error`,
  `rate_limit_error`, `api_error` or `upstream_error`.
</ResponseField>

<ResponseField name="error.code" type="string">
  Specific machine-readable cause. Branch on this, not on the message.
</ResponseField>

<ResponseField name="error.message" type="string">
  Human-readable explanation. Wording may change; do not parse it.
</ResponseField>

<ResponseField name="error.param" type="string">
  The offending request field, when the error is attributable to one.
</ResponseField>

## Status codes

| Status      | Type                    | Meaning                                              | Retry                       |
| ----------- | ----------------------- | ---------------------------------------------------- | --------------------------- |
| `400`       | `invalid_request_error` | Malformed body, bad `task` name, unknown `model`     | No, fix the request         |
| `401`       | `authentication_error`  | Missing or invalid key (`invalid_api_key`)           | No                          |
| `402`       | `insufficient_credit`   | Account balance below the minimum to start a request | No, top up                  |
| `403`       | `authentication_error`  | Key lacks access to the requested model or task      | No                          |
| `404`       | `invalid_request_error` | Unknown path or task ID                              | No                          |
| `413`       | `invalid_request_error` | Body or context exceeds the route's limit            | No, shorten the input       |
| `429`       | `rate_limit_error`      | Rate or spend limit exhausted                        | Yes, after the reset header |
| `500`       | `api_error`             | Fault on our side                                    | Yes, with backoff           |
| `502` `503` | `upstream_error`        | The routed provider failed or timed out              | Yes, with backoff           |

## Retrying

Retry `429`, `500`, `502` and `503`. Do not retry `4xx` other than `429`, since
the same request will fail the same way.

Use exponential backoff with jitter, and respect
`x-ratelimit-reset-requests` on a `429` rather than guessing an interval.

```python theme={null}
import time, random
from openai import APIStatusError

def with_retries(fn, attempts=4):
    for i in range(attempts):
        try:
            return fn()
        except APIStatusError as e:
            if e.status_code not in (429, 500, 502, 503) or i == attempts - 1:
                raise
            reset = e.response.headers.get("x-ratelimit-reset-requests")
            delay = float(reset) if reset else (2 ** i) + random.random()
            time.sleep(delay)
```

<Warning>
  Retrying a completion bills again. Cap attempts, and make sure a retry loop
  around a streaming call does not silently double a long generation's cost.
</Warning>

## Upstream failures

A `502` or `503` carries `upstream_unavailable`: the provider behind the model
failed. Protégé does not silently substitute a different model, because a
substitution you did not ask for is a quality change you did not approve.

Include `x-request-id` from the failed response when reporting it.
