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

# Errors

> Every error has the same shape: a machine-readable code to branch on, a sentence for a person, and sometimes the field or the detail that caused it.

## The error envelope

```json theme={null}
{
  "error": {
    "code": "validation",
    "message": "`to` must not be before `from`",
    "field": "to"
  }
}
```

**error.code** `string` *required*

What went wrong, as a fixed word your code can branch on. Codes are part of v1 and do not change.

**error.message** `string` *required*

A sentence for a person, saying what to change. Log it and show it; do not parse it, except where this page says so.

**error.field** `string`

On some `validation` errors, the parameter or body field that caused it. A request that fails the basic checks on its shape (a value too long or of the wrong type, an unknown parameter or body key, a `limit` over the maximum) is also `400 validation`, but carries no `field`: the `message` names what to fix.

**error.detail** `object`

On a `conflict` or a `plan_limit`, the facts behind the refusal: the existing prompt's id, the limit and how much of it is used, and so on. Its keys are `snake_case`.

## Codes

| Status | `code` | What it means | What to do |
| - | - | - | - |
| `400` | `validation` | A parameter or body field is missing, malformed, or not allowed here. | Read `message`, and `field` when it is there, and fix the request. |
| `401` | `unauthorized` | No live API key in an `Authorization: Bearer` header. Missing, malformed, unknown, revoked and expired keys all get this same answer, as does a key whose workspace is suspended or cancelled. | Check the header and the key. Make a new key if this one was revoked. |
| `402` | `plan_limit` | The workspace's plan does not allow this: it is at its prompt or competitor limit, it has no active plan, or the engine you asked for is not on the plan. | Archive something, or change plan. At a limit, `detail` says how much is used; for an engine, it names the engine. |
| `403` | `forbidden` | The key is real but may not do this: it lacks the scope the message names, or it is a write-scoped key whose maker can no longer make this change in the app, or it is a write sent with an `Origin` header naming another site, as a browser page sends. | Make a key with the scope, or a new key as someone who can edit. Send writes from a server or a script. |
| `404` | `not_found` | No such thing among the projects this key can read. A project in another workspace, or outside the key's projects, is also `404`. | Check the id. |
| `409` | `conflict` | The change clashes with what is there: the same prompt already exists, a segment is not one you claim, an alias belongs to another brand, or the project is archived. | Read `message` and `detail`. |
| `429` | `rate_limited` | Over the key's or the workspace's budget for this minute. | Wait for `Retry-After` seconds. |
| `503` | `busy` | Your workspace already has a report running, or the API is building as many reports as it allows. | Wait for `Retry-After` seconds and try again. |
| `503` | `query_timeout` | The request asked for more than can be read in one go: the database stopped after five seconds. Nothing partial is returned. | Narrow the window or drop a dimension. |
| `500` | `internal` | Something failed on our side. The message is always "something went wrong". | Retry later. If it keeps happening, write to [support@citino.ai](mailto:support@citino.ai) with the time and the route. |

A path under `/v1/` that is not a route at all is also `404`, with the code `error`.

A refusal never tells you more than you could already see: an id in someone else's workspace is `404`, exactly like an id that does not exist, never `403`.

## Errors worth handling by name

**A report over its budget.** A report request whose window is too long for the project is `400 validation` with `field` set to `from`, and a message that starts `report_budget:` and says the most days this project allows without `date` as a dimension. Add `date` to `dimensions` and page, or shorten the window. A `week` or `month` interval too big for one page also starts `report_budget:`, with `field` set to `interval`: use `interval=day`. A window longer than the most a report may cover even with `date` starts `window_too_large:`. See [Reports](https://docs.citino.ai/api/reports.md).

**A cursor from a different query.** `400 validation` with `field` set to `cursor`: the parameters changed between pages. Request the first page again. See [Pagination](https://docs.citino.ai/api/pagination.md).

**`format` where CSV is not offered.** Leave `format` out on those routes: any value there, `json` included, is `400 validation` with `field` set to `format`, and the message lists the routes that offer CSV. See [CSV output](https://docs.citino.ai/api/csv.md).

**A duplicate prompt.** `409 conflict` with `detail.existing_prompt_id`, when you add a prompt or re-word one: the exact text is already one of your prompts, possibly one you archived or re-worded. Check that prompt's `status` before treating it as live. When adding, or re-wording an archived prompt, at the prompt limit the limit is checked first, so the same text is `402` instead. See [Writes](https://docs.citino.ai/api/writes.md).

**A prompt at the plan's limit.** `402 plan_limit` with `detail.used` and `detail.limit`, and `detail.actions` naming the two ways out, `upgrade` and `archive`.

```bash curl theme={null}
# -i prints the status line and the headers, including Retry-After.
curl -i "https://api.citino.ai/v1/projects/$PROJECT_ID/reports/brands?from=$FROM&to=$TO" -H "Authorization: Bearer $CITINO_API_KEY"
```

```python Python theme={null}
import os
import requests

BASE = "https://api.citino.ai/v1"
key = os.environ["CITINO_API_KEY"]
project_id = os.environ["PROJECT_ID"]

response = requests.get(
    f"{BASE}/projects/{project_id}/reports/brands",
    headers={"Authorization": f"Bearer {key}"},
    params={"from": os.environ["FROM"], "to": os.environ["TO"]},
)
if not response.ok:
    error = response.json()["error"]
    if error["code"] == "validation" and error["message"].startswith("report_budget:"):
        print("Window too long for one request; add date to the dimensions and page.")
    else:
        print(response.status_code, error["code"], error.get("field"), error["message"])
```

```javascript JavaScript theme={null}
const BASE = "https://api.citino.ai/v1";
const headers = { Authorization: `Bearer ${process.env.CITINO_API_KEY}` };
const query = new URLSearchParams({ from: process.env.FROM, to: process.env.TO });

const response = await fetch(
  `${BASE}/projects/${process.env.PROJECT_ID}/reports/brands?${query}`,
  { headers },
);
if (!response.ok) {
  const { error } = await response.json();
  if (error.code === "validation" && error.message.startsWith("report_budget:")) {
    console.log("Window too long for one request; add date to the dimensions and page.");
  } else {
    console.log(response.status, error.code, error.field, error.message);
  }
}
```

## Related

> [card] [Rate limits](https://docs.citino.ai/api/rate-limits.md)
>
> Budgets, `Retry-After`, and `busy`.

> [card] [Reports](https://docs.citino.ai/api/reports.md)
>
> What a report's budget is, and how to page past it.

> [card] [Writes](https://docs.citino.ai/api/writes.md)
>
> Conflicts, plan limits and retrying safely.

> [card] [Authentication](https://docs.citino.ai/api/authentication.md)
>
> Why a key gets `401` or `403`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.