---
title: Errors and retries
description: The error format, the codes, and which ones to retry.
---
Every error has the same shape: an HTTP status and one `error` object.

```json
{
  "error": {
    "code": "invalid_request",
    "message": "Invalid request body, parameters, or headers.",
    "request_id": "req_0ee311b4334e4b118a94c7d1c48577fd",
    "category": "validation",
    "stage": "validation",
    "retryable": false,
    "retry_after_seconds": null,
    "public_reason": "invalid_request",
    "next_action": "fix_input",
    "details": {
      "errors": [
        {
          "path": "prompt",
          "loc": [
            "prompt"
          ],
          "message": "must have required property 'prompt'",
          "type": "required"
        }
      ]
    }
  }
}
```

| Field | Meaning |
|---|---|
| `code` | Stable, machine-readable. Branch on this. |
| `message` | For people. Do not parse it. |
| `request_id` | Also in the `x-sume-request-id` header. Quote it to support. |
| `retryable` | `true` when the same request can succeed later. |
| `retry_after_seconds` | How long to wait first, when known (also the `retry-after` header). |
| `next_action` | `fix_input`, `authenticate`, `retry`, `inspect_events` and similar. |
| `details` | Extra facts, such as the failing fields for `invalid_request`. |

## Codes

| HTTP | `code` | Meaning | Retry? |
|---|---|---|---|
| 400 | `bad_request` | The body is not valid JSON. | No, fix the body |
| 400 | `invalid_request` | A field is missing or wrong. `details.errors[]` names it. | No, fix the field |
| 400 | `concurrency_limit_reached` | Your workspace has as many jobs running and queued as it may. | After a job finishes |
| 401 | `unauthorized` | No key, or the key is wrong or revoked. | No |
| 401 | `session_not_accepted` | A dashboard session was sent to the public API. | No, send a key |
| 402 | `insufficient_funds` | The open.sume.com balance cannot cover the price. `details` has `required_usd` and `available_usd`. | After a top-up |
| 403 | `open_key_route_not_allowed` | The key called a route outside the model API ([list](/docs/authentication)). | No |
| 404 | `not_found` | No such job or model in your workspace. | No |
| 409 | `idempotency_conflict` | The `Idempotency-Key` was used for a different request. | No, use a new key |
| 409 | `job_not_completed` | You read a result before the job finished. | Yes, poll first |
| 409 | `job_not_cancelable` | Generation has started; the job can no longer be canceled. | No |
| 413 | `payload_too_large` | The request body is too big. | No |
| 429 | `rate_limited` | Too many requests for your key in this window. | Yes, after `retry-after` |
| 503 | `database_busy` | A short overload. | Yes, after `retry-after` |
| 500 | `internal_error` | A fault on Sume's side. | Yes, with backoff |

Malformed JSON is always a 400, never a 500:

```json
{
  "error": {
    "code": "bad_request",
    "message": "Body is not valid JSON but content-type is set to 'application/json'",
    "request_id": "req_74c4d70612334f37a3d923cf6fd0868b",
    "category": "validation",
    "stage": "validation",
    "retryable": false,
    "retry_after_seconds": null,
    "public_reason": "invalid_request",
    "next_action": "fix_input"
  }
}
```

## Retrying safely

- Retry `GET` requests after a network error, a `429` or a `5xx`, with exponential backoff and jitter. Honour `retry-after`.
- Retry a generate request only with the same `Idempotency-Key`. See [Idempotency](/docs/idempotency).
- Do not retry `401`, `403`, `404` or a `400` you have not fixed.
- A job that ends `failed` is not an HTTP error. Its `error` says what went wrong, and its price is refunded.
