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

# Errors

> How the Ontora API signals failures, and how to handle each one.

The API uses standard HTTP status codes. Errors come back as JSON in this shape:

```json theme={null}
{
  "detail": "Workspace not accessible by this API key",
  "code": "workspace_forbidden"
}
```

When validation fails (invalid request body), `detail` is an array of field-level errors:

```json theme={null}
{
  "detail": [
    { "loc": ["body", "topics", 0, "name"], "msg": "field required", "type": "missing" }
  ]
}
```

## Status codes

| Code                        | Meaning                              | What to do                                                               |
| --------------------------- | ------------------------------------ | ------------------------------------------------------------------------ |
| `200 OK`                    | Success                              | —                                                                        |
| `201 Created`               | Resource created                     | Capture the returned id                                                  |
| `204 No Content`            | Success, no body                     | —                                                                        |
| `400 Bad Request`           | Malformed request                    | Fix the request body                                                     |
| `401 Unauthorized`          | Missing or invalid credentials       | Check `X-API-Key` or `Authorization` header                              |
| `403 Forbidden`             | Authenticated but not allowed        | Add the missing scope, or you're hitting a workspace you don't belong to |
| `404 Not Found`             | Resource doesn't exist               | Verify the id                                                            |
| `409 Conflict`              | Resource isn't in the expected state | Check the error code — e.g., `synthesis_pending`                         |
| `422 Unprocessable Entity`  | Validation error                     | Fix the field-level issues in `detail`                                   |
| `429 Too Many Requests`     | Rate limited                         | Honor `Retry-After`                                                      |
| `500 Internal Server Error` | Bug on our side                      | Retry; if persistent, contact support with the request id                |
| `503 Service Unavailable`   | Temporary outage                     | Retry with backoff                                                       |

## Common error codes

| `code`                | Meaning                                                                                    |
| --------------------- | ------------------------------------------------------------------------------------------ |
| `invalid_api_key`     | Key is missing, malformed, or revoked                                                      |
| `insufficient_scope`  | Key is missing a required scope                                                            |
| `workspace_forbidden` | Key belongs to a different workspace                                                       |
| `synthesis_pending`   | Campaign hasn't completed synthesis yet                                                    |
| `graph_not_ready`     | Entity-extraction pipeline hasn't finished                                                 |
| `campaign_locked`     | Lifecycle action invalid for current status (e.g., starting an already-completed campaign) |
| `rate_limited`        | Per-workspace rate limit exceeded                                                          |

## Idempotency

Safe-by-design endpoints (any `GET`, `DELETE`) are naturally idempotent. For `POST` endpoints, pass an `Idempotency-Key` header with a unique value (e.g., a UUID) to make retries safe:

```
Idempotency-Key: 2c4a9cf2-...
```

The same key replayed within 24 hours returns the original response without creating a duplicate.

## Rate limits

Rate limits apply per workspace, not per key. Limits are returned in headers on every response:

| Header                  | Meaning                               |
| ----------------------- | ------------------------------------- |
| `X-RateLimit-Limit`     | Limit for the current window          |
| `X-RateLimit-Remaining` | Calls remaining in this window        |
| `X-RateLimit-Reset`     | Unix timestamp when the window resets |
| `Retry-After`           | Seconds to wait (only on 429)         |
