# Idempotency

> Retry writes safely with the Idempotency-Key header — a retry replays the first response instead of acting twice.



Send an `Idempotency-Key` header on writes. Retrying with the same key replays the first response instead of acting a second time.

```bash
curl -X POST https://your-site.awrora.app/api/v1/bookings \
  -H "Authorization: Bearer aur_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c1e0a-9c2b-4a10-8f7e-3f0b5d1c2a44" \
  -d '{ … }'
```

The key is any string of **8–200 characters**; a UUID per logical operation is the right choice. It is scoped to the organisation and **the API key**, and bound to **the route and the exact request body** for **24 hours**. Another key reusing the same string is treated as a different body.

## Where it is required

| Endpoint                                                                  | `Idempotency-Key`            |
| ------------------------------------------------------------------------- | ---------------------------- |
| [`POST /v1/bookings`](/api-reference/bookings/create-booking)             | **Required**                 |
| [`POST /v1/bookings/{id}/cancel`](/api-reference/bookings/cancel-booking) | **Required**                 |
| Every other `POST` and `PATCH`                                            | Optional, honoured when sent |
| `GET`, `DELETE`                                                           | Ignored                      |

It is required on the two that spend something real — a duplicated booking takes a seat from a guest who could have had it, and there is no way for us to tell a retry from a genuine second booking without the key. Missing the header where it is required is `400` `idempotency_key_required`.

## Replays

A replayed response is **byte-for-byte the first response**, including its status code, plus one header:

```http
Idempotent-Replayed: true
```

So a client that timed out on a `201` gets that same `201` and the same booking — not a second booking, and not an error it has to special-case.

## The four outcomes

| Situation                                             | Response                                          |
| ----------------------------------------------------- | ------------------------------------------------- |
| First request with this key                           | Runs normally                                     |
| Same key, same body, first one finished               | The stored response + `Idempotent-Replayed: true` |
| Same key, **different** body, first one finished      | `422` `idempotency_key_reused`                    |
| Same key, first one still running — whatever the body | `409` `idempotency_in_progress` — retry shortly   |

<Callout type="warning" title="idempotency_key_reused is a bug">
  It is not a transient error. Reusing a key for a different request would make the second one silently disappear. Generate a new key per operation.
</Callout>

## What is not stored

A `5xx`, or a request that crashed, **releases** the key — otherwise a transient failure would burn it for 24 hours and every retry would replay the same error. Retry a `5xx` with the same key.

A `4xx` **is** stored. `409` `insufficient_capacity` is an answer to your question, and a retry should get the same answer rather than suddenly succeeding because somebody else cancelled. To try again after a `4xx`, fix the request and use a new key.
