# Idempotency

Every write endpoint requires an `Idempotency-Key` header:

```bash
curl -X POST https://api.wearenoe.fr/v1/blocks \
  -H "Authorization: Bearer $NOE_API_KEY" \
  -H "Idempotency-Key: 8f14e45f-ceea-467a-9c1e-2a3f5b7d9012" \
  -H "Content-Type: application/json" \
  -d '{"property_id":"…","start_date":"2026-09-10","end_date":"2026-09-14","reason_type":"maintenance"}'
```

Any unique string works; a UUID per logical operation is the easiest approach.

## Why it is mandatory

When a request times out you cannot know whether it landed. Retrying without
protection risks a second block on the same dates, or a calendar update applied
twice. Retrying **with the same key** replays the original response instead of
performing the write again, so a timeout becomes safe to retry — which is the
only reason a network blip does not become a data problem.

A replayed response carries `Idempotent-Replayed: true`. Keys are remembered for
**24 hours**.

## Three outcomes

| Situation | Result |
| --- | --- |
| Same key, same payload, original completed | The stored response is replayed |
| Same key, same payload, original still running | `409 idempotency_in_progress` — retry shortly |
| Same key, **different** payload | `409 idempotency_key_reused` |

That last one is worth explaining. Reusing a key for a different operation is a
client bug, and silently replaying the old response would hide a write that
never happened. We surface it instead. Generate a fresh key per operation, not
per retry.

## Choosing keys

- **One key per logical operation**, reused across every retry of that
  operation. A key derived from your own record id (`block-create-<your-id>`)
  works well and survives a process restart.
- **Not a fresh key per attempt** — that defeats the mechanism entirely.
- **Not a shared key across operations**, or the second one gets `409`.

## Batch endpoints

`POST /v1/blocks/batch` and `PUT /v1/calendar` take one `Idempotency-Key` for
the **whole call**. Retrying replays the entire `207` response, including the
per-item results, so items that succeeded are not reapplied.
