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

# Errors

> Error envelope, every code the API returns, and what is worth retrying.

Failures share one envelope:

```json theme={null}
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [{ "field": "payload.prompt", "message": "payload.prompt or payload.query is required" }],
    "timestamp": "2026-07-09T04:12:00.000Z"
  }
}
```

`code` is stable and safe to branch on. `message` is for humans and may change. `details`
is present when we can name the offending field.

## Codes

| Code                      | Status | Meaning                                                                                                                      | Retry?                        |
| ------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
| `MISSING_API_KEY`         | 401    | Missing or wrong bearer token.                                                                                               | No — fix the key              |
| `VALIDATION_ERROR`        | 400    | Malformed body, unknown `taskType`, or neither `payload.prompt` nor `payload.query` present.                                 | No — fix the request          |
| `REGION_UNAVAILABLE`      | 422    | No account or IP capacity for the requested `country` on this engine.                                                        | Later, after capacity returns |
| `RESOURCE_ALREADY_EXISTS` | 409    | Duplicate `idempotencyKey` on the single-task endpoint.                                                                      | No — the task already exists  |
| `PAYLOAD_TOO_LARGE`       | 413    | Batch body exceeds 8 MB.                                                                                                     | No — send smaller chunks      |
| `RESOURCE_NOT_FOUND`      | 404    | Task id unknown, or the task aged out of the 24-hour retention window.                                                       | No                            |
| `ENQUEUE_ERROR`           | —      | Per-item failure inside a batch (e.g. a database hiccup on one lane). Appears in `results[].error`, never as an HTTP status. | Yes                           |

<Note>
  `400` is for a request we cannot parse or understand. `422` is reserved for a request that
  is perfectly well-formed but that we cannot process right now — today that means
  `REGION_UNAVAILABLE` alone. Branching on `error.code` is more durable than branching on
  the status.
</Note>

## Region unavailability

`CHATGPT` and `CLAUDE` draw on pools of real accounts that are bound to a region. Before
queuing, the API checks whether it can actually serve `payload.country` (defaulting to
`US`) and rejects up front if it cannot:

```json theme={null}
{
  "success": false,
  "error": {
    "code": "REGION_UNAVAILABLE",
    "message": "cannot serve region 'DE': no live IPs provisioned. Check GET /capacity.",
    "details": { "country": "DE", "serveable": 0, "hasIps": false },
    "timestamp": "2026-07-09T04:12:00.000Z"
  }
}
```

This is a deliberate fast failure. Without it the task would be accepted and then spin,
failing to acquire an account until it exhausted its retries — a slow, expensive way to
learn the same thing. Query [`GET /capacity`](/api-reference/capacity) to see what each
region can currently serve.

Every other engine is stateless from an account perspective and is never rejected this way.

## Batch failures are per-item

A well-formed batch always returns `200`, even if every task inside it failed. The HTTP
status describes the *request*; `results[].success` describes each *task*.

```json theme={null}
{
  "success": true,
  "summary": { "total": 3, "succeeded": 2, "failed": 1 },
  "results": [
    { "success": true,  "index": 0, "task": { "...": "..." } },
    { "success": false, "index": 1, "error": { "code": "REGION_UNAVAILABLE", "message": "..." } },
    { "success": true,  "index": 2, "task": { "...": "..." } }
  ]
}
```

Only a malformed request as a whole — non-JSON, not an array, empty, over 500 items, over
8 MB, or unauthorized — produces a 4xx for the batch itself.

<Note>
  A batch item can fail with `REGION_UNAVAILABLE` or `ENQUEUE_ERROR`, neither of which
  appears in Cloro's documented per-item error enum. Both are additions, not replacements —
  `VALIDATION_ERROR` and `RESOURCE_ALREADY_EXISTS` behave as Cloro specifies.
</Note>

## Task-level failures

A task that fails *after* being queued does not produce an HTTP error at all. You learn
about it from the terminal status:

```json theme={null}
{
  "success": true,
  "task": { "id": "8f2c1e40-...", "status": "FAILED", "...": "..." },
  "credits": { "creditsToCharge": 0, "creditsCharged": 0 },
  "error": "engine timeout after 3 attempts"
}
```

Note the shape difference: this top-level `error` is a **string**, not the `{code, message,
timestamp}` object used by request-level errors. It is the last engine error recorded
before the task gave up. The same task delivers a webhook with `task.status === "FAILED"`.
