Advanced

Safe to retry

A POST timed out and I retried, and the record was created twice. A retry is only safe if the call is idempotent. You make it idempotent with a key you choose.

What you will see

{"id": 1, "amount": 50}

About 2 min of reading

Try It

Make a retryable POST safe with an idempotency key.

1

POST without a key, then retry after a timeout. The record is created twice.

curl -s -X POST -d '{"amount":50}' https://api.example/charge

Before you run it, say out loud what it will print. Then open the answer.

What it prints
{"id": 1, "amount": 50}
2

POST with a key and retry with the same key. The server returns the first result, no duplicate.

curl -s -X POST -H 'Idempotency-Key: 7f3a' -d '{"amount":50}' https://api.example/charge

Before you run it, say out loud what it will print. Then open the answer.

What it prints
{"id": 1, "amount": 50}
A fresh key on every retry defeats the point. The key must be the same across retries of one attempt, and different for a new attempt. Reuse it on retry, renew it for new work.

Data Flow

A timeout leaves the result unknown; an idempotency key you choose lets the server recognise a retry and return the first result instead of acting twice.
1 POST times out, result unknown
↓
2 You send an idempotency key
↓
3 Retry reuses the same key
↓
4 Server returns first result
Why this works
GET is free, POST is not

GET and DELETE are naturally idempotent: doing them twice is the same as once. POST is not. Any POST whose result you might retry needs an idempotency key so the server can recognise the repeat.

Key per attempt, not per call

The key identifies one logical attempt. Reuse it across retries of that attempt, but use a fresh key for a genuinely new action. A key that never changes turns every real submission into a no-op.

Examples

The double charge

POST /charge  ->  (timeout, unknown result)
POST /charge  ->  created twice

A timeout leaves you unsure whether it worked. Retrying a plain POST can duplicate the effect. That is the expensive bug.

Send a key

POST /charge
Idempotency-Key: 7f3a-9c21

You pick a key for the attempt. The server remembers it, so a retry with the same key returns the first result, not a new charge.

Reuse the key on retry

key = new_uuid()
while True:
    r = post('/charge', headers={'Idempotency-Key': key})
    if r.ok or r.status == 429: break

Same key every try. The retry is now a question, not a second action.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← When the server says slow down
Previous Lesson
When the server says slow down
Next Lesson
Signing a request
Signing a request →