# Errors and limits

## HTTP errors

A refused request answers with OpenAI's error object and no envelope:

```json
{ "error": { "message": "...", "type": "invalid_request_error", "param": null, "code": "invalid_request" } }
```

`param` names the offending field when there is one. Branch on `code`, never on `message`.

| HTTP | `type`                  | `code`                       | When                                                                                 |
| ---- | ----------------------- | ---------------------------- | ------------------------------------------------------------------------------------ |
| 400  | `invalid_request_error` | `invalid_request`            | The body is invalid; `param` names the field                                         |
| 400  | `invalid_request_error` | `unsupported_parameter`      | `previous_response_id`, `conversation` and similar                                   |
| 400  | `invalid_request_error` | `unsupported_jurisdiction`   | `X-Jurisdiction` holds `us`, `oecd`, `ifrs` or an unknown code                       |
| 400  | `invalid_request_error` | `idempotency_key_reused`     | The same `Idempotency-Key` with a different request                                  |
| 401  | `authentication_error`  | `invalid_api_key`            | The key is missing, unknown, revoked or expired                                      |
| 402  | `billing_error`         | `insufficient_credits`       | The balance cannot cover the request, or there is no account                         |
| 402  | `billing_error`         | `spend_cap_reached`          | The key's monthly spend cap is reached                                               |
| 403  | `permission_error`      | `account_suspended`          | The account is suspended                                                             |
| 404  | `not_found_error`       | `not_found`                  | Unknown response, or one that belongs to another key                                 |
| 409  | `invalid_request_error` | `idempotency_key_in_use`     | The same key is still being processed                                                |
| 409  | `invalid_request_error` | `response_finished`          | Cancel on a response that has ended                                                  |
| 429  | `rate_limit_error`      | `rate_limit_exceeded`        | The requests-per-minute limit                                                        |
| 429  | `rate_limit_error`      | `concurrency_limit_exceeded` | The in-progress responses limit                                                      |
| 500  | `server_error`          | `server_error`               | Anything else; never exception text                                                  |
| 503  | `server_error`          | `ledger_unavailable`         | The balance cannot be checked right now, so the API refuses rather than run unbilled |

## A failed or cancelled response

A response that fails or is cancelled is not a refused request. It answers `200` and keeps the Responses envelope, with `status` and `error: {code, message}`:

| `error.code`     | Meaning                                                                                      |
| ---------------- | -------------------------------------------------------------------------------------------- |
| `invalid_prompt` | The turn was declined: the question is outside what the service answers. Change the question |
| `server_error`   | The run could not produce an answer                                                          |
| `cancelled`      | You cancelled the response                                                                   |

These are terminal states. Sending the same turn again reaches the same result, so they are not retried as such. Failed and declined runs are free (see [Pricing](/guides/pricing)).

## Answers outside the error object

Two answers come from the layers around the API and are not in the error shape. Each carries `x-request-id`.

- A request that matches no route, such as an unknown path under `/v1` or a method the path does not accept, answers `404` or `405` with `{"detail": "..."}`.
- A body over 256 KiB answers `413`. It is the plain text `Content Too Large` when the request declares its `Content-Length`, and `{"detail": "Content Too Large"}` for a chunked body.

A request for an unknown response id on a known route is the exception: it is `404` `not_found` in the error object.

## Headers

| Header                           | Meaning                                                                                                             |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `x-request-id`                   | `req_` plus 32 lowercase hex characters, on every `/v1` request including errors. Quote it when you contact support |
| `Retry-After`                    | Whole seconds, on every `429` and `503`                                                                             |
| `x-ratelimit-limit-requests`     | The requests-per-minute limit                                                                                       |
| `x-ratelimit-remaining-requests` | Requests left in the current window                                                                                 |
| `x-ratelimit-reset-requests`     | Time until the window resets, in OpenAI's duration form (`1s`, `6m0s`)                                              |

## Rate limits

Limits are per key and depend on its mode. Requests are counted in one-minute windows; in-progress responses are counted from creation until the response ends.

| Key     | `POST /v1/responses` | In-progress responses | Lookups        |
| ------- | -------------------- | --------------------- | -------------- |
| Live    | 60 per minute        | 10                    | 600 per minute |
| Sandbox | 10 per minute        | 2                     | 60 per minute  |

Past the per-minute limit the answer is `429` `rate_limit_exceeded`. With the in-progress count at its limit it is `429` `concurrency_limit_exceeded`; nothing is reserved or charged for either.

## Retry policy

- Retry `429` and `503` after the `Retry-After` seconds. Retry `500` too, with a short backoff.
- Always reuse the same `Idempotency-Key` on the retry. A retry without `Idempotency-Key` is a new, charged request (see [Responses](/guides/responses)).
- Never retry `400`, `401`, `402` or `403`. The same request fails the same way; fix the request or the account first.
- Stop after a few attempts. The quickstart samples make at most 3.
