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.
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.
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 |
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:
408, 429 and 5xx; other 4xx errors will not fix themselves;Retry-After;POST without an 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.
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:
422 Unprocessable Content.409 Conflict so the client retries later.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:
{ "id": "pay_1727433600000", "amount": 4999, "status": "captured" }GET, such as GET /cart/checkout. Prefetchers trigger them.POST on timeout without a key, which duplicates orders or charges.Which statement about calling `DELETE /items/5` twice is correct?
GET, HEAD, OPTIONS) must not change server state.PUT, DELETE, plus all safe methods) can be retried freely.POST and most PATCH requests are not idempotent, so blind retries create duplicates.Idempotency-Key header lets a server detect repeats and replay the stored response.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.