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

# Errors

> The Chataigne error envelope, type and code taxonomy, HTTP status mapping, and how to handle failures reliably.

Chataigne uses conventional HTTP status codes to signal the outcome of a request, and returns a consistent, machine-readable JSON envelope on every error. Read the HTTP status to decide *how* to react (retry, fix input, escalate), and read the `error.type` and `error.code` fields to decide *what* went wrong.

<Note>
  Errors are identical in shape across both API surfaces — the public Chataigne API (`/v1`) and the Admin API (`/admin/v1`). The base URL for all requests is `https://server.chataigne.ai`.
</Note>

## The error envelope

Every error response — regardless of status code or surface — has the same top-level shape: a single `error` object.

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_invalid",
    "message": "currency must be one of CHF, EUR, GBP, BRL, USD, MXN.",
    "param": "currency",
    "request_id": "req_8sQx2pL0aZ",
    "details": {
      "allowed_values": ["CHF", "EUR", "GBP", "BRL", "USD", "MXN"]
    }
  }
}
```

<ResponseField name="error" type="object" required>
  The error envelope. Present on every non-2xx response.

  <Expandable title="properties" defaultOpen>
    <ResponseField name="type" type="string" required>
      The high-level category of error. Maps deterministically to the HTTP status code (see [Error types](#error-types)). Branch on this field first.
    </ResponseField>

    <ResponseField name="code" type="string" required>
      A stable, granular identifier for the specific failure (for example `parameter_invalid`, `resource_not_found`). Use this for fine-grained handling and user-facing messaging.
    </ResponseField>

    <ResponseField name="message" type="string" required>
      A human-readable description of what went wrong. Intended for logs and developers — **not** for displaying verbatim to end users. Wording may change over time; do not parse it.
    </ResponseField>

    <ResponseField name="param" type="string">
      Present on `invalid_request_error` when a single request parameter is at fault. Names the offending field using dot notation for nested fields (for example `address.postal_code`).
    </ResponseField>

    <ResponseField name="request_id" type="string" required>
      The unique identifier of the request, mirroring the `X-Request-Id` response header. Include this when contacting support.
    </ResponseField>

    <ResponseField name="details" type="object">
      Optional, error-specific structured context (for example allowed values, conflicting key, or rate-limit metadata). The shape depends on the `code`; treat it as additive.
    </ResponseField>
  </Expandable>
</ResponseField>

<Warning>
  Only `type`, `code`, `request_id`, and the overall envelope shape are part of the API contract. The `message` text and the contents of `details` may evolve — never branch on `message`, and treat `details` as best-effort.
</Warning>

## Error types

The `type` field maps one-to-one to an HTTP status code. This is the most reliable signal for deciding your retry and recovery strategy.

| `type`                  | HTTP status | Meaning                                                                                      | Safe to retry?                             |
| ----------------------- | ----------- | -------------------------------------------------------------------------------------------- | ------------------------------------------ |
| `invalid_request_error` | `400`       | The request was malformed, missing a required parameter, or a value failed validation.       | No — fix the request first.                |
| `authentication_error`  | `401`       | No API key was provided, or the key is invalid or revoked.                                   | No — fix credentials.                      |
| `authorization_error`   | `403`       | The key is valid but not permitted for this surface or resource.                             | No — use a key with the right scope.       |
| `not_found_error`       | `404`       | The requested resource does not exist or is not visible to your key.                         | No.                                        |
| `conflict_error`        | `409`       | The request conflicts with the current state (for example a concurrent in-flight duplicate). | Yes, after a short backoff.                |
| `idempotency_error`     | `409`       | An `Idempotency-Key` was reused with a different request body.                               | No — use a fresh key or the original body. |
| `rate_limit_error`      | `429`       | Too many requests for this key on this surface.                                              | Yes — honor `Retry-After`.                 |
| `api_error`             | `500`       | An unexpected error occurred on Chataigne's side.                                            | Yes, with exponential backoff.             |

<Note>
  As a rule of thumb: `4xx` errors (except `409` and `429`) indicate a problem with your request that you must fix before retrying. `409`, `429`, and `5xx` are transient and safe to retry with backoff.
</Note>

## Error codes

`code` narrows down `type` to a specific, stable cause. New codes may be added over time, so always provide a fallback path keyed off `type`.

### `invalid_request_error` (400)

| `code`                    | When it happens                                                                                                    |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `parameter_missing`       | A required parameter was omitted. `param` names the field.                                                         |
| `parameter_invalid`       | A parameter was present but failed validation (wrong type, format, or unsupported value). `param` names the field. |
| `parameter_unknown`       | An unexpected parameter was sent. `param` names the field.                                                         |
| `parameters_exclusive`    | Two mutually exclusive parameters were sent together (for example `starting_after` and `ending_before`).           |
| `idempotency_key_missing` | A resource-creating `POST` was sent without the required `Idempotency-Key` header.                                 |

### `authentication_error` (401)

| `code`            | When it happens                                     |
| ----------------- | --------------------------------------------------- |
| `api_key_missing` | No `x-api-key` header was provided.                 |
| `api_key_invalid` | The provided key is malformed, unknown, or revoked. |

### `authorization_error` (403)

| `code`                  | When it happens                                                                                    |
| ----------------------- | -------------------------------------------------------------------------------------------------- |
| `api_key_wrong_surface` | An admin key (`ch_admin_…`) was used on `/v1`, or an org key (`ch_org_…`) was used on `/admin/v1`. |
| `session_not_allowed`   | A dashboard session was used against the `/v1` or `/admin/v1` API.                                 |
| `permission_denied`     | The key is valid for the surface but not authorized for the target resource.                       |

### `not_found_error` (404)

| `code`               | When it happens                                           |
| -------------------- | --------------------------------------------------------- |
| `resource_not_found` | The resource does not exist, or your key cannot see it.   |
| `route_not_found`    | The HTTP method and path do not match any known endpoint. |

### `conflict_error` (409)

| `code`              | When it happens                                                                                           |
| ------------------- | --------------------------------------------------------------------------------------------------------- |
| `request_in_flight` | A concurrent request with the same `Idempotency-Key` is still being processed. Retry after a brief delay. |
| `resource_conflict` | The request conflicts with the current state of the resource.                                             |

### `idempotency_error` (409)

| `code`                   | When it happens                                                                                                                          |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `idempotency_key_reused` | The `Idempotency-Key` was previously used with a **different** request body. `details.original_request_id` references the first request. |

### `rate_limit_error` (429)

| `code`                | When it happens                                                                              |
| --------------------- | -------------------------------------------------------------------------------------------- |
| `rate_limit_exceeded` | You exceeded the request quota for this key on this surface. Honor the `Retry-After` header. |

### `api_error` (500)

| `code`           | When it happens                                                                                                                  |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `internal_error` | An unexpected error occurred on our side. Retry with exponential backoff; if it persists, contact support with the `request_id`. |

## The `param` field

For `invalid_request_error` responses caused by a single field, the envelope includes `param` — the exact path to the offending parameter. This lets you map the error straight to a form field or request property without parsing `message`.

Nested fields use dot notation, mirroring the request body:

<CodeGroup>
  ```json Top-level field theme={null}
  {
    "error": {
      "type": "invalid_request_error",
      "code": "parameter_missing",
      "message": "name is required.",
      "param": "name",
      "request_id": "req_J3kP9vTq1m"
    }
  }
  ```

  ```json Nested field theme={null}
  {
    "error": {
      "type": "invalid_request_error",
      "code": "parameter_invalid",
      "message": "address.postal_code is not valid for country CH.",
      "param": "address.postal_code",
      "request_id": "req_Lp02xVbn8C"
    }
  }
  ```
</CodeGroup>

<Note>
  `param` is only present on `invalid_request_error`. Errors that are not tied to a specific field (authentication, not found, rate limits, server errors) omit it.
</Note>

## Request IDs

Every response — successful or not — carries an `X-Request-Id` header. On errors, the same value is mirrored into `error.request_id`. Log it for every request: it is the fastest way for Chataigne support to locate what happened.

```bash theme={null}
curl -i https://server.chataigne.ai/v1/locations/loc_does_not_exist \
  -H "x-api-key: ch_org_..."
```

```http theme={null}
HTTP/1.1 404 Not Found
X-Request-Id: req_8sQx2pL0aZ
Content-Type: application/json

{
  "error": {
    "type": "not_found_error",
    "code": "resource_not_found",
    "message": "No location found with id loc_does_not_exist.",
    "request_id": "req_8sQx2pL0aZ"
  }
}
```

<Tip>
  You can supply your own correlation value via the `x-request-id` request header and we will echo it back, which makes it easy to trace a request across your systems and ours.
</Tip>

See [Request IDs](/concepts/request-ids) for the full lifecycle.

## Errors specific to other features

A few behaviors surface as errors but are governed by dedicated features:

<CardGroup cols={2}>
  <Card title="Idempotency" icon="key" href="/concepts/idempotency">
    Reusing an `Idempotency-Key` with a different body returns `idempotency_error` (409). A concurrent duplicate returns `conflict_error` (409). Keys are retained for 24 hours.
  </Card>

  <Card title="Rate limits" icon="gauge-high" href="/concepts/rate-limits">
    Limits are per-key, per-surface. `429` responses add a `Retry-After` header alongside the `X-RateLimit-*` headers.
  </Card>

  <Card title="Authentication" icon="lock" href="/concepts/authentication">
    Missing keys return `401`. Using a key on the wrong surface — or a dashboard session on the API — returns `403`.
  </Card>

  <Card title="Pagination" icon="list" href="/concepts/pagination">
    Sending `starting_after` and `ending_before` together returns `invalid_request_error` with code `parameters_exclusive`.
  </Card>
</CardGroup>

## Handling errors

A robust client should:

1. **Branch on `error.type`** (via the HTTP status) to choose a strategy: fix the request, refresh credentials, or retry.
2. **Use `error.code`** for fine-grained handling and to map to user-facing messaging.
3. **Surface `error.param`** to point users at the exact field to correct.
4. **Retry only transient errors** — `409`, `429`, and `5xx` — with exponential backoff, and honor `Retry-After` on `429`.
5. **Always log `error.request_id`** so failures are traceable.

<CodeGroup>
  ```bash curl theme={null}
  # Inspect both the status code and the body. -f is intentionally NOT used so
  # the error envelope is printed instead of being swallowed by curl.
  curl -sS -w "\nHTTP %{http_code}\n" \
    https://server.chataigne.ai/v1/organizations/org_8sQx2pL0aZ/locations \
    -H "x-api-key: ch_org_..." \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: $(uuidgen)" \
    -d '{ "name": "Pizzeria Bellini", "currency": "ZZZ" }'

  # => HTTP 400
  # {
  #   "error": {
  #     "type": "invalid_request_error",
  #     "code": "parameter_invalid",
  #     "message": "currency must be one of CHF, EUR, GBP, BRL, USD, MXN.",
  #     "param": "currency",
  #     "request_id": "req_8sQx2pL0aZ"
  #   }
  # }
  ```

  ```js Node.js theme={null}
  import { randomUUID } from "node:crypto";

  const BASE_URL = "https://server.chataigne.ai";
  const RETRYABLE = new Set([409, 429, 500]);

  async function chataigneRequest(path, { method = "GET", body } = {}, attempt = 0) {
    const res = await fetch(`${BASE_URL}${path}`, {
      method,
      headers: {
        "x-api-key": process.env.CHATAIGNE_API_KEY,
        "Content-Type": "application/json",
        // Required on resource-creating POSTs; stable across retries.
        ...(method === "POST" ? { "Idempotency-Key": randomUUID() } : {}),
      },
      body: body ? JSON.stringify(body) : undefined,
    });

    const requestId = res.headers.get("X-Request-Id");

    if (res.ok) return res.json();

    const { error } = await res.json();

    // Retry transient failures with exponential backoff, honoring Retry-After.
    if (RETRYABLE.has(res.status) && attempt < 3) {
      const retryAfter = Number(res.headers.get("Retry-After")) || 0;
      const backoffMs = Math.max(retryAfter * 1000, 2 ** attempt * 250);
      await new Promise((resolve) => setTimeout(resolve, backoffMs));
      return chataigneRequest(path, { method, body }, attempt + 1);
    }

    // Non-retryable: branch on the stable type/code and surface request_id.
    switch (error.type) {
      case "invalid_request_error":
        throw new Error(`Fix "${error.param}": ${error.message} [${requestId}]`);
      case "authentication_error":
      case "authorization_error":
        throw new Error(`Credentials problem (${error.code}) [${requestId}]`);
      default:
        throw new Error(`${error.type}/${error.code}: ${error.message} [${requestId}]`);
    }
  }

  await chataigneRequest("/v1/organizations/org_8sQx2pL0aZ/locations", {
    method: "POST",
    body: { name: "Pizzeria Bellini", currency: "CHF" },
  });
  ```
</CodeGroup>

<Warning>
  Retrying a `POST` is only safe when you reuse the **same** `Idempotency-Key` across attempts. Generating a new key per attempt can create duplicate resources. Generate the key once per logical operation, before the first attempt.
</Warning>
