Safe and Idempotent Methods: Retries and Idempotency Keys

Intermediate
11 min

Safe and Idempotent Methods: Retries and Idempotency Keys

A client sends a request, the connection drops, and it never learns whether the server processed it. Whether it can safely retry depends on two properties of HTTP methods: safety and idempotency. In this lesson you will learn which methods have which property, why it matters for retries, and how to make POST retryable with an Idempotency-Key header.

Safe Methods

A method is safe when it is read-only: calling it must not change state the client cares about. The safe methods are GET, HEAD, OPTIONS and TRACE.

Safety is a promise about intent; writing a log line on GET is fine. Browsers, crawlers and prefetchers rely on it and issue GET requests freely, so an endpoint like GET /users/42/delete is a genuine security bug, not just bad style.

Idempotent Methods

A method is idempotent when sending the same request once or many times leaves the server in the same state. Every safe method is idempotent, and so are PUT and DELETE:

  • PUT /articles/7 with the same body twice stores the same article.
  • DELETE /articles/7 twice deletes it once. The second response may be 404, but the server state is identical, which is all that idempotency requires.

POST is not idempotent: POST /orders twice creates two orders. PATCH is not guaranteed either: setting status to "shipped" is idempotent, "add 1 to quantity" is not.

| Method | Safe | Idempotent | Retry without extra work? | |---|---|---|---| | GET, HEAD, OPTIONS | Yes | Yes | Yes | | PUT, DELETE | No | Yes | Yes | | PATCH | No | Not guaranteed | Only if the patch itself is idempotent | | POST | No | No | No, use an idempotency key |

Why This Matters for Retries

SDKs and gateways retry on network errors and on 5xx or 429 responses. That is only correct if the method is idempotent, which the caller can tell from the method alone. A well-behaved client:

  • retries only on connection failures, 408, 429 and 5xx; other 4xx errors will not fix themselves;
  • waits with exponential backoff (300 ms, 600 ms, 1.2 s) plus random jitter, and honors Retry-After;
  • never retries a plain POST without an idempotency key.

Making POST Retryable with Idempotency-Key

Payment providers popularized a pattern the IETF is standardizing as the Idempotency-Key header: the client generates a UUID per logical operation and sends the same key on every retry.

bash
curl -X POST https://api.example.com/payments \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 3f6a2c1e-9b0d-4c7e-a1f2-5d8e9c0b1a23" \ -d '{"amount": 4999, "currency": "INR"}'

The server stores the key with a fingerprint of the body and the response it produced. On a repeat:

  1. Same key, same fingerprint: return the stored response without running the operation again.
  2. Same key, different body: reject with 422 Unprocessable Content.
  3. Same key, original request still running: return 409 Conflict so the client retries later.
javascript
const seen = new Map(); // production: Redis with a 24-hour TTL app.post("/payments", (req, res) => { const key = req.get("Idempotency-Key"); if (!key) return res.status(400).json({ error: "Idempotency-Key header required" }); const fingerprint = JSON.stringify(req.body); const previous = seen.get(key); if (previous) { if (previous.fingerprint !== fingerprint) { return res.status(422).json({ error: "Idempotency-Key reused with a different payload" }); } return res.status(previous.status).json(previous.body); } const payment = { id: "pay_" + Date.now(), amount: req.body.amount, status: "captured" }; seen.set(key, { fingerprint, status: 201, body: payment }); res.status(201).json(payment); });

Scope keys per client and expire them after a day. Fresh or replayed, the client sees:

json
{ "id": "pay_1727433600000", "amount": 4999, "status": "captured" }

Common Mistakes

  • Putting actions behind GET, such as GET /cart/checkout. Prefetchers trigger them.
  • Retrying POST on timeout without a key, which duplicates orders or charges.
Quick Quiz
Question 1 of 2

Which statement about calling `DELETE /items/5` twice is correct?

Key Takeaways

  • Safe methods (GET, HEAD, OPTIONS) must not change server state.
  • Idempotent methods (PUT, DELETE, plus all safe methods) can be retried freely.
  • POST and most PATCH requests are not idempotent, so blind retries create duplicates.
  • The Idempotency-Key header lets a server detect repeats and replay the stored response.
  • Retry only on network errors, 429 and 5xx, with exponential backoff and jitter.

Next lesson: JSON in REST APIs — structure request and response bodies the way well-known APIs do.

Safe and Idempotent Methods: Retries and Idempotency Keys - REST APIs | CodeYourCraft | CodeYourCraft