Idempotency
Every write endpoint requires an Idempotency-Key header:
Code
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.

