# Errors

> Curriculo API error formats and HTTP status codes, from 400 to 504, with what each means and what to do. Failed requests are never billed.

**Source:** /docs/errors/ (Curriculo Docs)  
**Updated:** 15 October 2026  
**Contact:** hello@curriculo.me

Errors are JSON (except the edge's own `413` and `404` pages) and carry the HTTP status that matters. Nothing fails silently: a rejected request tells you why. Responses from the synchronous operations carry an `X-Curriculo-Request-Id` header (requests rejected before they reach an operation, background-task calls and screening calls do not); quote it when you contact us.

## Error formats

The shape tells you where the request stopped, not which operation you called. Both can come from most endpoints.

| Shape | Returned when | Statuses |
|---|---|---|
| `{"success": false, "error": "...", "message": "..."}` | The gateway rejects the request before any processing: missing, invalid or expired key, no credit, suspended or unprovisioned account, rate limit, an unknown path. Every error from `/screening-interview` and from the task endpoints (`/{op}/async`, `/{op}/tasks/{task_id}`) also uses this shape. | `400`, `401`, `403`, `404`, `413`, `429` |
| `{"detail": "..."}` | The request reached processing and the operation itself refused or failed it: an unreadable document, a validation error, an unknown `job_id`, a timeout. `"Upstream unavailable"` with `503` also uses this shape. | `400`, `404`, `413`, `422`, `500`, `503`, `504` |

Check for `detail` first, then `message`, so one handler covers both:

```python
body = resp.json()
reason = body.get("detail") or body.get("message") or body.get("error")
```

For validation errors (`422`), `detail` is usually a list of objects, each naming the field in `loc` and the problem in `msg`.

In a list request, a failed item carries its own error inside `results`, and the HTTP status of the request stays `200`. See [Batching](/docs/batching/).

## HTTP status codes

| Status | Meaning | What to do |
|---|---|---|
| `400` | The input is unusable: empty file, blank text, unreadable or password-protected document, blank `job_details`, a duplicate JSON key. For a recording task, not enough credit to reserve the 2-hour maximum. | Fix the payload, or add credit. Retrying identical bytes will fail identically. |
| `401` | "Missing API token…", "Invalid API token." (unknown or revoked key), "API token expired.", or "Unknown API endpoint" for a path under `/public/` that does not exist. | Check the `Authorization` header, the key, and the path. |
| `403` | The key is valid but the account cannot serve traffic: no prepaid credit ("…Buy credits in the Console."), the account is suspended, or "Account provisioning incomplete". | Add credit in **Console → Billing**, or wait for provisioning to finish. If the account is suspended, contact us. |
| `404` | Unknown `job_id`, or one registered by a different account. Also an unknown task id, or a path outside `/public/`. | Register the job again; ids are scoped to the account that created them. Check the base URL. |
| `413` | Over a size ceiling on a synchronous call: request body, document, recording, batch length, or character count. | Split the workload, or send recordings by link. See [request size limits](/docs/requests-and-responses/#request-size-limits). |
| `422` | Schema validation failed: a required field of `/jobs/`, `/summary/`, `/translate/` or `/prompt/` is missing or misnamed (a missing file on `/parse/`, `/evaluate/` or `/transcribe/` is a `400`). Also a translation language outside the 202 supported languages. | Compare against the [API reference](/docs/api/). Field names are case-sensitive. See [Languages](/docs/languages/). |
| `429` | Rate limit exceeded, or another request is still running on a balance under $1. | Back off and retry. Honour `Retry-After` when present. |
| `500` | Our failure. | Retry once. If it persists, contact us with the `X-Curriculo-Request-Id`. |
| `503` | A dependency is unavailable, the service is busy, or a feature is not enabled here (for example media transcription). | Retry with backoff. |
| `504` | Processing exceeded the 300-second ceiling. | Send fewer items or a smaller document, or use the [async lane](/docs/background-processing/). |

> [!NOTE]
> Failed requests are never charged. A `4xx` or `5xx` costs you nothing, and neither does a failed background task.

## Retry guidance

- **Do not retry** `400`, `401`, `403`, `404`, `413` or `422` unchanged. Fix the request first.
- **Retry with backoff** on `429` and `503`.
- **Retry once** on `500`.
- **On `504`,** resend with fewer items or a smaller document. In a list request, resubmit only the items that returned a `504` entry.
- Re-sending a single failed document is always safe. It cannot create duplicates or half-finished records.

## Contact

Still stuck? Use the [contact page](/contact/) or write to [hello@curriculo.me](mailto:hello@curriculo.me) with the time, the operation and the `X-Curriculo-Request-Id`.

## Next steps

- [Rate limits](/docs/rate-limits/): ceilings and `Retry-After`.
- [Batching](/docs/batching/): per-item errors inside a list.
