# Idempotency Keys — API Design and Versioning

Source: https://www.geekswithgeeks.com/en/api-design/rel-idempotency

> Make POST requests safe to retry with a client-supplied idempotency key.

## The double-charge problem

A client sends `POST /payments`, the connection drops, and it does not know if the payment happened. Retrying blindly could charge twice. An **idempotency key** solves it: the client generates a unique value (a UUID) for the *intent* and sends it in an `Idempotency-Key` header. The server stores the **result** under that key; if the same key arrives again it returns the stored result instead of repeating the action, usually with a header marking it as a replay. Keys should expire after a period (for example 24 hours), and reusing a key with a **different** request body should be rejected. Make such keys required for operations with real side effects: payments, orders, emails.

## Safe retries, cheap repeats

Networks fail, so well-designed APIs make retries safe and repeated reads cheap.

![Four tools: idempotency, caching, limits, async.](assets/figures/api-design/section-3-map.svg) — Figure 3.1 — Idempotency, caching, limits and async.

## A replayed payment, run

I ran this against a small real HTTP API built with only the Python standard library (full code in the case study). The first `POST` with key `abc-123` creates `pay_1` (`201`, with a `Location` header). The identical retry returns the same payment with `200` and `Idempotent-Replayed: true`, and `b1 == b2` is True. A missing key gives `400`, and an invalid amount gives `422` with field details.

```python
# uses srv, base and call() from the runnable demo in the case study
k = {"Idempotency-Key": "abc-123", "Content-Type": "application/json"}
s1, h1, b1 = call("POST", "/v1/payments", {"amount": 500}, k); s2, h2, b2 = call("POST", "/v1/payments", {"amount": 500}, k)
print(s1, b1, h1.get("Location")); print(s2, b2, h2.get("Idempotent-Replayed"), b1 == b2)
print(call("POST", "/v1/payments", {"amount": 500}, {"Content-Type": "application/json"})[0], call("POST", "/v1/payments", {"amount": -5}, {**k, "Idempotency-Key": "z"})[2]["errors"])

```

Output:

```
201 {'id': 'pay_1', 'amount': 500, 'status': 'succeeded'} /v1/payments/pay_1
200 {'id': 'pay_1', 'amount': 500, 'status': 'succeeded'} true True
400 [{'field': 'amount', 'message': 'must be > 0'}]
```

## Store the key and the result atomically

Two retries can arrive at the same time. Reserve the key before doing the work (for example with a unique database constraint) so only one request executes and the other waits or gets the stored result.

**Quiz:** What does a server do when it receives a repeated Idempotency-Key with the same request?

- [ ] Deletes the first result
- [ ] Performs the action again
- [x] Returns the stored result without repeating the action
- [ ] Crashes

*Answer:* Returns the stored result without repeating the action. The key links retries to the original attempt so the side effect happens once.
