# Idempotency

Every **write** endpoint on `/v1/bookings` requires an `Idempotency-Key`
header. The key lets you safely retry a request after a network failure
without creating a duplicate booking or applying the same mutation twice.

## When it's required

| Endpoint | Required? |
|---|---|
| `POST /v1/bookings` | yes |
| `POST /v1/bookings/:uid/cancel` | yes |
| `POST /v1/bookings/:uid/reschedule` | yes |
| `PATCH /v1/bookings/:uid` | yes |

Calling one of these without the header returns
**`400 missing_idempotency_key`**.

## Picking a key

Any caller-supplied string up to **255 characters**. Use something unique
per *logical* operation — a UUID is the safest default:

```
Idempotency-Key: 1f0a3b6e-7c1d-4a3e-9e91-9b6c0f8b18f4
```

Reuse the **same** key on retries of the same operation. Use a **different**
key for a different operation, even when the body looks similar.

## What "replay" means

Each `(account, Idempotency-Key)` pair is remembered for **24 hours**. Within
that window:

- **Same key, same body** → the original response is replayed, including its
  HTTP status code. The work is **not** executed twice.
- **Same key, different body** → **`409 idempotency_key_conflict`**. The
  server fingerprints the request body and refuses to associate one key with
  two different requests.
- **Same key, request still in flight** → **`409 idempotency_in_progress`**
  with `Retry-After: 1`. The first attempt is mid-execution; retry shortly.
- **First attempt errored** → the key reservation is **released** so a retry
  can proceed with the same key. Only successful responses are cached.

Body comparison uses a canonical JSON fingerprint — key order and whitespace
don't matter.

## What it does not protect against

- **Bumping resource versions.** Idempotency is request-level; optimistic
  locking via [`If-Match`](/help/api/optimistic-locking) is what guards
  concurrent edits.
- **Different keys, same intent.** If you retry with a fresh key the server
  has no way to know it's a retry — you'll create a second booking.

## Example

```bash
# First attempt
curl -X POST https://api.42min.us/v1/bookings \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: 1f0a3b6e-7c1d-4a3e-9e91-9b6c0f8b18f4" \
  -H "Content-Type: application/json" \
  -d '{"event_type_id":"…","start":"2026-05-20T15:00:00Z","attendee":{"email":"a@b.com"}}'
# → 201 { "data": { "uid": "abc…", … }, "meta": { "request_id": "req_…" } }

# Network failure on the way back? Retry with the SAME key:
curl -X POST https://api.42min.us/v1/bookings \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: 1f0a3b6e-7c1d-4a3e-9e91-9b6c0f8b18f4" \
  -H "Content-Type: application/json" \
  -d '{"event_type_id":"…","start":"2026-05-20T15:00:00Z","attendee":{"email":"a@b.com"}}'
# → 201 (replayed) — the SAME booking, no duplicate.
```

## Best practices

- Generate the key **before** sending the request and store it alongside the
  pending operation in your own system.
- Use a **fresh key** for each new operation — never reuse one across
  semantically different requests.
- On `409 idempotency_in_progress`, sleep briefly and retry — the original
  request will land or fail within seconds.
- On `409 idempotency_key_conflict`, your client has a bug — you're reusing a
  key for two different requests. Mint a new key.
