> ## 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.

# Idempotency

> Safely retry resource-creating POSTs with an Idempotency-Key, with replay semantics and 24h key retention.

Network failures are unavoidable. If a `POST` times out, you can't tell whether the
resource was created or not — retrying blindly risks creating a duplicate.

**Idempotency keys** solve this. By attaching a unique key to a creating `POST`,
you can safely retry the same request as many times as you need: the Chataigne API
performs the operation exactly once and returns the original response on every replay.

<Note>
  An idempotency key is **required** on every resource-creating `POST` (for example,
  creating a location or a special closing). It is ignored on `GET`, `PATCH`, `PUT`,
  and `DELETE`, which are already naturally idempotent.
</Note>

## How it works

Send a unique value in the `Idempotency-Key` header. The API stores the first
response associated with that key and replays it for any subsequent request that
arrives with the same key.

<ParamField header="Idempotency-Key" type="string" required>
  A unique value you generate per logical operation. Use a UUID (v4) or any other
  high-entropy, collision-resistant string. Keys are scoped per API key and retained
  for **24 hours**, after which they are discarded and a request with the same key is
  treated as new.
</ParamField>

| Scenario                                            | Behavior                                                                                                      |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| **First request**                                   | The operation runs and the response is stored against the key.                                                |
| **Replay** — same key, same body                    | The **original** response is returned with `Idempotent-Replayed: true`. The operation does **not** run again. |
| **Mismatch** — same key, different body             | `409` with error type `idempotency_error`.                                                                    |
| **In-flight** — same key, original still processing | `409` with error type `conflict_error`.                                                                       |
| **Expired** — same key, more than 24h later         | Treated as a brand-new request.                                                                               |

<ResponseField name="Idempotent-Replayed" type="boolean">
  Present and set to `true` only on replayed responses. Its absence means the request
  was processed for the first time. Use it to detect retries in logs and metrics.
</ResponseField>

## Creating the same location twice

The example below creates a location under an organization, then issues the exact
same request a second time. The second call returns the **same** `loc_…` resource —
no duplicate is created.

<CodeGroup>
  ```bash First request theme={null}
  curl https://server.chataigne.ai/v1/organizations/org_8sK2pQ4mNvT1/locations \
    -H "x-api-key: ch_org_live_9aF3kLpQ7mZ2" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: 6f1a9c2e-3b7d-4a51-9e44-2c8f0d5b71a3" \
    -d '{
      "name": "Pizzeria Centrale",
      "currency": "CHF",
      "country": "CH",
      "timezone": "Europe/Zurich"
    }'
  ```

  ```bash Replay (same key, same body) theme={null}
  # Identical request — returns the original location with Idempotent-Replayed: true
  curl https://server.chataigne.ai/v1/organizations/org_8sK2pQ4mNvT1/locations \
    -H "x-api-key: ch_org_live_9aF3kLpQ7mZ2" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: 6f1a9c2e-3b7d-4a51-9e44-2c8f0d5b71a3" \
    -d '{
      "name": "Pizzeria Centrale",
      "currency": "CHF",
      "country": "CH",
      "timezone": "Europe/Zurich"
    }'
  ```

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

  const idempotencyKey = randomUUID();

  async function createLocation() {
    const res = await fetch(
      "https://server.chataigne.ai/v1/organizations/org_8sK2pQ4mNvT1/locations",
      {
        method: "POST",
        headers: {
          "x-api-key": process.env.CHATAIGNE_API_KEY,
          "Content-Type": "application/json",
          // Reuse the SAME key when retrying this operation
          "Idempotency-Key": idempotencyKey,
        },
        body: JSON.stringify({
          name: "Pizzeria Centrale",
          currency: "CHF",
          country: "CH",
          timezone: "Europe/Zurich",
        }),
      },
    );

    const replayed = res.headers.get("idempotent-replayed") === "true";
    const location = await res.json();
    return { location, replayed };
  }
  ```
</CodeGroup>

Both requests return the same object. The replay is distinguished only by its header:

```http First response — 201 Created theme={null}
HTTP/1.1 201 Created
X-Request-Id: req_4Qm8Zt2yLpVc
Content-Type: application/json

{
  "id": "loc_5RhT9wXk2bND",
  "object": "location",
  "organization_id": "org_8sK2pQ4mNvT1",
  "name": "Pizzeria Centrale",
  "currency": "CHF",
  "country": "CH",
  "timezone": "Europe/Zurich",
  "default_language": "fr",
  "address": null,
  "created_at": "2026-05-29T09:14:03Z",
  "updated_at": "2026-05-29T09:14:03Z"
}
```

```http Replayed response — 201 Created theme={null}
HTTP/1.1 201 Created
Idempotent-Replayed: true
X-Request-Id: req_7Bn1Cv5xRwQa
Content-Type: application/json

{
  "id": "loc_5RhT9wXk2bND",
  "object": "location",
  "organization_id": "org_8sK2pQ4mNvT1",
  "name": "Pizzeria Centrale",
  "currency": "CHF",
  "country": "CH",
  "timezone": "Europe/Zurich",
  "default_language": "fr",
  "address": null,
  "created_at": "2026-05-29T09:14:03Z",
  "updated_at": "2026-05-29T09:14:03Z"
}
```

<Note>
  `X-Request-Id` differs between the original and the replay — each HTTP exchange gets
  its own request ID — but the response **body** is byte-for-byte the original.
</Note>

## Error cases

### Mismatched body — `idempotency_error`

Reusing a key with a **different** request body is almost always a bug (the most
common cause is regenerating data on retry instead of reusing the original payload).
The API rejects it rather than silently returning a stale result.

```http 409 Conflict theme={null}
HTTP/1.1 409 Conflict
X-Request-Id: req_2Lp9Ke7vNmZ4
Content-Type: application/json

{
  "error": {
    "type": "idempotency_error",
    "code": "idempotency_key_reused",
    "message": "This Idempotency-Key was already used with a different request body.",
    "request_id": "req_2Lp9Ke7vNmZ4"
  }
}
```

<Warning>
  Generate the idempotency key **once**, before the first attempt, and reuse the
  exact same key and body on every retry. Never derive the key from values that can
  change between attempts (timestamps, random IDs, retry counters).
</Warning>

### Concurrent duplicate — `conflict_error`

If a second request arrives with the same key while the first is **still being
processed**, the duplicate is rejected with `conflict_error`. This protects you from
double-submits caused by impatient retries or parallel workers.

```http 409 Conflict theme={null}
HTTP/1.1 409 Conflict
X-Request-Id: req_8Wd3Hs6tBcXq
Content-Type: application/json

{
  "error": {
    "type": "conflict_error",
    "code": "idempotency_key_in_flight",
    "message": "A request with this Idempotency-Key is already in progress.",
    "request_id": "req_8Wd3Hs6tBcXq"
  }
}
```

When you receive `conflict_error`, wait briefly and retry with the **same** key — once
the original request completes, the retry will replay its stored response.

## Best practices

<CardGroup cols={2}>
  <Card title="One key per operation" icon="key">
    Generate a fresh key for each distinct create. Reuse it only when retrying that
    same operation.
  </Card>

  <Card title="Use a UUID" icon="fingerprint">
    A v4 UUID gives you collision-free keys without coordination across processes.
  </Card>

  <Card title="Retry within 24h" icon="clock">
    Keys are retained for 24 hours. Retry inside that window to guarantee a replay
    rather than a new resource.
  </Card>

  <Card title="Check the replay header" icon="rotate">
    Inspect `Idempotent-Replayed` to tell first-time creates from retries in your
    logs and dashboards.
  </Card>
</CardGroup>

<Note>
  Idempotency keys behave identically on the Admin API (`/admin/v1`). The same rules,
  headers, and 24-hour retention apply.
</Note>
