Category: Design Tags: idempotency, idempotency-key, retries, safe-retries, duplicate-prevention, consistency
Idempotency-Key header.Idempotency-Key header MUST be a client-generated UUID.Idempotency-Key it has already processed, it MUST return the original response without re-executing the operation.Idempotency-Key differ in request body content, the server MUST return 422 Unprocessable Entity.Idempotency-Key in the response so clients can confirm the key was accepted.Idempotency-Key.An operation is idempotent if calling it once or N times produces the same result. From HTTP Verbs:
| Method | Idempotent? |
|---|---|
GET |
✅ Yes |
PUT |
✅ Yes (when guaranteed) |
DELETE |
✅ Yes |
POST |
❌ No |
PATCH |
❌ No (unless implemented idempotently) |
The problem: Networks are unreliable. When a client sends a POST to create a resource and receives a network error (no response), it cannot know whether the server processed the request. Retrying without idempotency protection may create duplicate resources.
The solution: An Idempotency-Key allows a client to safely retry a request. The server uses the key to detect duplicate requests and return the original response instead of re-executing the operation.
The client generates a unique key before making the request and attaches it to the Idempotency-Key header.
POST /orders HTTP/1.1
Authorization: Bearer eyJ...
Content-Type: application/json
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
{
"customerId": "cust_abc123",
"items": [
{ "productId": "prod_xyz", "quantity": 2 }
]
}
First call (new key): The server processes the request normally and returns 201 Created.
Retry (same key, same body): The server recognises the key, skips execution, and returns the stored 201 Created response.
HTTP/1.1 201 Created
Location: /orders/ord_789
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
{
"id": "ord_789",
"status": "pending"
}
If the same key is received with a different request body, return an error:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"fault": {
"faultId": "abc-123",
"traceId": "xyz-456",
"errors": [
{
"errorCode": "IDEMPOTENCY_CONFLICT",
"description": "An Idempotency-Key may not be reused with a different request body."
}
]
}
}
When two requests with the same key arrive simultaneously (before the first has completed), the second request SHOULD receive 409 Conflict with guidance to retry after a short delay.
Idempotency keys are transient; they are not permanent identifiers.
flowchart TD
A["Client wants to create a resource"] --> B["Generate a new UUID\nIdempotency-Key"]
B --> C["Send POST with Idempotency-Key"]
C --> D{"Response received?"}
D -- "Yes: 2xx" --> E["Success — store resource ID\nDiscard Idempotency-Key"]
D -- "Yes: 4xx (not 408/429)" --> F["Client error — do not retry\nFix request and use a new Idempotency-Key"]
D -- "Yes: 5xx or network error or timeout" --> G["Retry using the SAME Idempotency-Key"]
G --> C
Rules for clients:
Support Idempotency-Key on POST endpoints where:
| Scenario | Recommended? |
|---|---|
| Creating a resource with significant side effects (payment, order, booking) | Yes — strongly recommended |
| Triggering an asynchronous operation | Yes — recommended |
| Sending a notification or email | Yes — recommended |
| Creating a resource with no side effects (adding a tag, a comment) | Optional |
| Queries (POST returning 200) | Not applicable — no state change |
GET, PUT, and DELETE are inherently idempotent and do not require this header.
paths:
/orders:
post:
summary: Create order
parameters:
- name: Idempotency-Key
in: header
required: false
description: |
A client-generated UUID used to safely retry the request.
If a request with this key has already been processed, the server returns
the original response without re-executing the operation.
The key is valid for 24 hours.
schema:
type: string
format: uuid
example: "550e8400-e29b-41d4-a716-446655440000"
PUT is already idempotent by definition — calling it multiple times with the same body leaves the resource in the same state. Idempotency keys are therefore not needed for PUT.
However, if a PUT implementation has side effects that break idempotency (e.g. it publishes an event each time it is called), then PUT MUST NOT be used — use POST or PATCH instead, and support Idempotency-Key on that endpoint.
See HTTP Verbs for idempotency rules on each method.