# Errors

The API uses standard HTTP status codes and OpenAI-compatible JSON error
responses. Use the HTTP status for broad handling and `error.code` when you
need to distinguish errors that share a status, such as the two `429`
responses.

## Error envelope
Celeris API errors use this shape:

```json
{
  "error": {
    "message": "Invalid or missing API key.",
    "type": "authentication_error",
    "code": "invalid_api_key"
  }
}
```

- **`message`**: a human-readable description.
- **`type`**: a broad error class, such as `authentication_error` or
  `rate_limit_exceeded`.
- **`code`**: a stable, machine-readable code. Use it to distinguish errors
  that share an HTTP status. See [`429`](#429).

Some `5xx` responses may not include a JSON body. Retry all `5xx` responses by
status rather than requiring an `error.code` value.

If you send an `X-Client-Trace-Id` header, Celeris echoes it in API responses
so you can correlate failures with application logs and support cases.

| Status | `code` | Retry? |
| --- | --- | --- |
| [`400`](#400) | `max_output_tokens_exceeded` or another validation code | After fixing the request |
| [`401`](#401) | `invalid_api_key` | After fixing credentials |
| [`402`](#402) | `insufficient_quota` | After adding credit |
| [`404`](#404) | `not_found` | After fixing the URL |
| [`408`](#408) | `request_timeout` | Yes |
| [`413`](#413) | `payload_too_large` | After reducing the request body |
| [`422`](#422) | None | After fixing the request |
| [`429`](#429) | `rate_limit_exceeded` / `service_busy` | Yes; honor `Retry-After` |
| [`502`](#502) | `upstream_error` | Yes, with backoff |
| [`503`](#503) | `service_unavailable` | Yes, with backoff |

## 400 Bad Request
The request is invalid. The response body describes the validation error.

Common causes:

- Malformed JSON, or a `messages` array that doesn't match the chat format.
  The [System One endpoint](/decisions#system-one-api) returns a [`422`](#422) for malformed JSON.
- An explicit `max_tokens`, `max_completion_tokens`, or `max_output_tokens` above the
  model's output limit returns `max_output_tokens_exceeded`. Reduce that value before
  retrying, or [contact support](mailto:support@celeris.ai) if you need a higher limit.
- `max_tokens` is zero or negative, or prompt tokens plus `max_tokens` exceed
  **131,072**. Requests are not silently truncated; see [Models](/models).
- On `/v1/responses`, an image part missing its required `detail`, or one written
  with the chat spellings (`text`, `image_url`) instead of
  `input_text` / `input_image`. The `400` body carries a schema error for every
  input shape the request was checked against, rather than naming the missing
  `detail`; see [Image input](/images#sending-images-on-the-responses-api).
- A [JSON-mode](/making-requests#json-mode) request with an invalid `json_schema`,
  or whose output could not be produced as conforming JSON. Streaming a
  `response_format` or a forced [`tool_choice`](/making-requests#tool-calling)
  request also returns `400`.
- On [`celeris-1-decision`](/decisions), `request_rejected` means the input
  exceeds the model's context limit or an image cannot be read. Shorten the
  state or questions, or check the image data before resending.

**What to do:** correct the request before retrying.

### Decisions validation
On [`POST /v1/decisions`](/decisions#openai-decisions-api), malformed JSON, invalid fields,
a model name other than `celeris-1-decision`, or exceeded
[request limits](/decisions#request-limits) return `400` with code
`invalid_request` and type `invalid_request_error`.

`message` describes the problem. `param` names the top-level field at fault,
or is `null` when the problem applies to the whole body. For example, a
choice question with fewer than two choices returns:

```json
{
  "error": {
    "message": "questions[0].choices: must hold at least two choices",
    "type": "invalid_request_error",
    "param": "questions",
    "code": "invalid_request",
    "ref": "q-d3"
  }
}
```

Correct the request before retrying. For a limit on the number of questions,
split them across requests. For a limit on JSON values, send fewer questions,
choices, or levels per request.

## 401 Unauthorized
Code: `invalid_api_key` (type `authentication_error`). The `Authorization`
header is missing, malformed, or carries a key that is unknown or revoked.

```json
{"error": {"message": "Invalid or missing API key.", "type": "authentication_error", "code": "invalid_api_key"}}
```

Common causes:

- No `Authorization: Bearer ck_...` header, or a typo in it.
- A revoked or rotated key still deployed somewhere.
- A recently created key that is still within the documented
  [activation window](/authentication#rotating-and-revoking).

**What to do:** check the header and the key's status in the
[Console](https://console.celeris.ai). Don't retry unchanged
requests. If the key was just created or rotated, wait up to one minute before
trying again.

## 402 Payment Required
Code: `insufficient_quota` (type `insufficient_quota`). Your workspace has no
prepaid credit remaining.

```json
{"error": {"message": "Your prepaid credit balance is exhausted. Add credit to continue.", "type": "insufficient_quota", "code": "insufficient_quota"}}
```

**What to do:** top up credits under
[**Settings → Billing**](https://console.celeris.ai/settings/billing), then
retry. Do not retry until credit is available. Because the balance is shared by
all keys in the workspace, production systems should alert on this response.

## 404 Not Found
Code: `not_found` (type `invalid_request_error`). The URL does not identify an
available model, usually because of a typo in the model segment of the
[base URL](/models#model-routing-and-base-urls).

```json
{"error": {"message": "No model is served at this path. Check the URL path segment.", "type": "invalid_request_error", "code": "not_found"}}
```

**What to do:** compare the URL and request-body `model` field with the values
in [Models](/models#model-routing-and-base-urls).

## 408 Request Timeout
Code: `request_timeout` (type `invalid_request_error`). The request body was
still arriving when the request's deadline passed.

```json
{"error": {"message": "The request body did not arrive in time. Send the request again.", "type": "invalid_request_error", "code": "request_timeout"}}
```

**What to do:** send the request again. If it recurs, check the client's upload
bandwidth and reduce the body size.

## 413 Payload Too Large
Code: `payload_too_large` (type `invalid_request_error`). The request body
exceeds the endpoint's 64 MiB (67,108,864-byte) maximum size, or 8 MiB for
[`celeris-1-decision`](/decisions).

```json
{"error": {"message": "The request body exceeds the maximum allowed size for this model.", "type": "invalid_request_error", "code": "payload_too_large"}}
```

**What to do:** reduce the request body before retrying. A prompt that fits the
byte limit but exceeds the input headroom left by `max_tokens` returns a
[`400`](#400).

## 422 Unprocessable Entity
Returned by [`POST /v1/systemone`](/decisions#system-one-api), for a request body that is
not a valid decision request or exceeds a [request limit](/decisions#request-limits).
The body does not use the error envelope. It is a `detail` array with one
entry, where `loc` is the path to the field at fault, `msg` says what is wrong
with it, and `type` is `value_error`, or `json_invalid` for malformed JSON:

```json
{"detail": [{"loc": ["body", "questions", "team", "criteria"], "msg": "field required", "type": "value_error"}]}
```

**What to do:** correct the field named by `loc` before retrying. If `msg` says
the answers do not fit in one response, shorten the question names. If `msg`
names a limit on questions or reads, split the questions across requests.
For a limit on JSON values, reduce the data in the request body.

## 429 Too Many Requests
Two codes share this status. Both are retriable and include a `Retry-After`
header in seconds:

- `rate_limit_exceeded`: the workspace exceeded its request-rate limit for the requested model.
- `service_busy`: the service is temporarily unable to accept more work.

```http
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 1

{"error": {"message": "You have exceeded your workspace's request rate. Slow down and retry, or contact support for a higher sustained limit.", "type": "rate_limit_exceeded", "code": "rate_limit_exceeded"}}
```

```json
{"error": {"message": "The service is busy. Please retry in a moment.", "type": "rate_limit_exceeded", "code": "service_busy"}}
```

Requests that return `429` are not charged.

**What to do:** wait at least as long as `Retry-After`, then retry with
exponential backoff and jitter. If `rate_limit_exceeded` persists, reduce
concurrency or [contact support](mailto:support@celeris.ai) about a higher
sustained rate. Report persistent `service_busy` responses to support.

## 502 Bad Gateway
Code: `upstream_error` (type `api_error`). The model service is temporarily
unavailable.

```json
{"error": {"message": "The model service is temporarily unavailable. Please retry with backoff.", "type": "api_error", "code": "upstream_error"}}
```

A decision request that returns `502` carries no `usage` and is not charged.

**What to do:** retry with backoff.

## 503 Service Unavailable
Code: `service_unavailable` (type `api_error`). The service is temporarily
unavailable.

```json
{"error": {"message": "The service is temporarily unavailable. Please retry shortly.", "type": "api_error", "code": "service_unavailable"}}
```

**What to do:** retry with backoff. If the error persists, check the Console
or [contact support](mailto:support@celeris.ai). Retry any `5xx` by status;
do not require a JSON body or a specific error code.

## Retries and timeouts
- **Retry** [`408`](#408), [`429`](#429), [`502`](#502), and [`503`](#503), with exponential
  backoff and jitter, honoring `Retry-After` when present.
- **Don't retry** [`400`](#400), [`401`](#401), [`402`](#402),
  [`404`](#404), or [`422`](#422) without changing something first.
- **Set client timeouts.** Choose a timeout that matches your workload and
  latency objective. Treat timeouts as transient and retry with backoff.

## A note for OpenAI SDK users
OpenAI SDKs expose Celeris errors as HTTP exceptions. Branch on the HTTP
status, or inspect `error.code` when you need to distinguish the two
[`429`](#429) responses:

```python
from openai import APIStatusError

try:
    response = client.chat.completions.create(...)
except APIStatusError as err:
    if err.status_code == 429:
        ...  # back off and retry
    elif err.status_code == 401:
        ...  # fix credentials; do not retry
    else:
        raise
```
