What Does Idempotency Mean in an API?
An API operation is idempotent when sending the same request multiple times has the same intended effect on server state as sending it once. Idempotency is a reliability property: it lets clients recover from uncertain network outcomes without accidentally creating a second payment, order, ticket, or other side effect.
HTTP semantics define safe methods plus PUT and DELETE as idempotent. POST is not inherently idempotent, which is why APIs often add an application-level idempotency mechanism for operations that clients need to retry safely.
Idempotency API Example for a POST Request
Consider an API that creates a payment. The client generates a unique key for one logical operation and reuses that key only when retrying that same operation:
Idempotency-Key is a widely used API convention, but it is not currently an HTTP RFC. The IETF HTTPAPI draft reached version 07 and is now expired, so an API must document its key syntax, scope, retention and replay behavior explicitly.POST /v1/payments HTTP/1.1
Content-Type: application/json
Idempotency-Key: "4b3f48d2-8c6f-4b43-b262-5017bb87352b"
{
"order_id": "ord_8421",
"amount": 12500,
"currency": "USD"
}On first execution, the server can create the payment and persist the key, a request fingerprint, execution state, response status, and response body:
HTTP/1.1 201 Created
Content-Type: application/json
{
"id": "pay_9001",
"order_id": "ord_8421",
"status": "accepted"
}If the connection fails after the server commits the payment, the client retries with the same key and same payload. The server recognizes the logical request and returns the stored result instead of creating pay_9002.
How to Design Idempotency on the Server
- Choose the scope. Decide whether a key is unique globally, per tenant, per account, or per endpoint. Tenant + endpoint + key is a common composite scope.
- Validate before executing. Reject malformed requests before creating a durable idempotency record when no business operation has started.
- Create a request fingerprint. Hash the normalized method, target and relevant payload so the same key cannot be reused for a different operation.
- Reserve the key atomically. Use a unique constraint or compare-and-set operation so two concurrent requests cannot both become the first execution.
- Persist the outcome. Store enough response information to replay a deterministic result to retries.
- Define retention. Keep keys long enough to cover realistic retry windows and document when a pruned key becomes reusable or invalid.
idempotency_records
- tenant_id
- endpoint
- idempotency_key UNIQUE within scope
- request_hash
- state processing | completed | failed
- response_status
- response_body
- created_at
- expires_atThe Idempotency-Key header is widely used in production APIs. As of October 2026, the IETF HTTPAPI document defining it remains an expired Internet-Draft rather than a published RFC, so treat its exact wire format as a design convention unless your own API contract adopts it explicitly.
Define Idempotency-Key Reuse Semantics
| Key state | Recommended contract |
|---|---|
| New key | Reserve atomically, execute once, then persist the outcome. |
| Same key + same logical request, completed | Return the stored outcome without repeating the side effect. |
| Same key + different request fingerprint | Reject as a conflict. 409 Conflict is a common application choice, but it is not mandated by an idempotency-key standard. |
| Same key still in progress | Return a documented in-progress/conflict response or wait within a bounded server policy; never execute a second copy concurrently. |
Fingerprint the fields that define the logical operation and normalize them deliberately. Avoid retaining full sensitive bodies merely to compare retries.
Idempotency and REST HTTP Methods
| Method | HTTP semantic | Typical API use | Retry implication |
|---|---|---|---|
| GET | Safe and idempotent | Read resource | Normally safe to retry, subject to client/network policy. |
| PUT | Idempotent | Create/replace a known resource URI | Repeating the same representation should have the same intended effect. |
| DELETE | Idempotent | Delete target resource | A retry may return a different status, but should not delete “twice.” |
| POST | Not inherently idempotent | Create/process action | Use explicit API semantics or an idempotency key when safe retry is required. |
| PATCH | Not inherently idempotent by method definition | Partial modification | Some patch documents can be idempotent by design, but do not assume it. |
For broader method semantics, see REST API PUT vs POST vs PATCH.
Handling Concurrent Requests with the Same Idempotency Key
The hardest case is not a later retry—it is two copies arriving at almost the same time. The storage layer must ensure only one request owns execution. A unique database constraint, transactional insert, distributed lock, or atomic key-value operation can provide that guarantee.
If the first request is still processing, the second request can wait briefly, return a conflict/in-progress response, or expose an operation-status resource. Pick one contract and document it. Do not let both requests enter the side-effecting code path and attempt to deduplicate after the fact.
Idempotency Security Considerations
- Never trust the key as authorization. The authenticated principal must still be allowed to perform the operation.
- Bind the key to tenant/account context. One customer must not retrieve another customer’s cached response by guessing or reusing a key.
- Compare request fingerprints. Reusing one key with a different amount, destination or object must fail rather than silently replaying the old result.
- Do not leak secrets in stored response bodies. Apply the same data-minimization and retention controls used for normal API logs or transaction records.
- Protect against key flooding. Attackers can generate endless unique keys; combine retention limits, quotas and normal API rate controls.
Idempotency also reduces the operational risk of retries, but it does not stop intentional API replay attacks where an attacker is authorized or has stolen valid request material.
Common Idempotency API Mistakes
- Generating the idempotency key on the server after the client has already lost certainty about the first request.
- Treating every repeated payload as a duplicate even when it represents a legitimate new operation.
- Reusing the same key for different payloads.
- Storing the key only in application memory, so a restart loses deduplication state.
- Checking for an existing key and then inserting later without an atomic uniqueness guarantee.
- Caching successful responses but forgetting how failures, timeouts and in-progress requests are handled.
How to Test Idempotency
- Send the same key and payload twice; verify exactly one business operation is created.
- Simulate a connection drop after server commit and retry the request.
- Send the same key with a different payload and verify it is rejected.
- Fire two identical requests concurrently and confirm only one reaches the side-effecting operation.
- Repeat after the documented key-retention window and verify the behavior matches the contract.
- Test tenant isolation by attempting to reuse a key from another account.
Idempotency Failure Handling: What Should Be Stored?
An idempotency layer needs a deliberate failure model. If validation fails before execution begins, many APIs do not reserve the key because the client can correct the request and try again. Once execution begins, however, the key should normally be tied to that logical operation so a client cannot submit the same key with different content while the outcome is uncertain.
| Situation | Recommended behavior | Why |
|---|---|---|
| Malformed request / schema validation fails | Return validation error; usually do not create durable execution record | No side effect started. |
| Key already completed with same fingerprint | Replay stored status/body or point to original resource | Prevents duplicate effect. |
| Key already exists with different fingerprint | Reject conflict | Prevents one key representing two operations. |
| Key is currently processing | Return in-progress result or wait according to contract | Prevents concurrent duplicate execution. |
| Execution committed but response was lost | Replay committed result on retry | This is the core uncertain-outcome problem idempotency solves. |
Idempotency Key Retention, Scope and Cleanup
Retention should reflect how long a client may reasonably retry after losing a response. Payment or provisioning workflows may need longer retention than a lightweight write. Document the window because deleting the record changes behavior: a retry after expiry may become a new operation.
Scope is equally important. A raw key such as a UUID should not be treated as globally meaningful unless your API explicitly defines it that way. A composite lookup like (tenant_id, endpoint_family, idempotency_key) prevents accidental collisions and makes tenant isolation clearer.
# Conceptual lookup
record = find(tenant_id, "create-payment", idempotency_key)
if record and record.request_hash != hash(normalized_request):
return 409 # key reused with different operation
if record and record.state == "completed":
return record.responseCleanup jobs should delete expired records predictably and preserve any accounting/audit data that must live longer in a different system of record. Do not retain full sensitive request bodies merely because the idempotency implementation needs a fingerprint.
How Ammune Fits
Idempotency protects the correctness of intended retries. Runtime API security still needs to detect abnormal automation, token misuse, replay patterns and business-logic abuse that can involve technically valid idempotency keys or intentionally unique requests. Ammune adds behavioral visibility around those interactions while the application remains responsible for transaction-level idempotency guarantees.
Production Implementation Checklist
- Define which operations support idempotency and publish the contract.
- Generate the key on the client before the first attempt.
- Scope keys by tenant/account and operation.
- Create a normalized request fingerprint and reject mismatched reuse.
- Reserve keys atomically before side effects execute.
- Define behavior for in-progress duplicate requests.
- Persist the committed outcome needed for replay.
- Set and document a retention/expiry window.
- Protect stored response data according to its sensitivity.
- Load-test concurrent duplicate submissions and failure-after-commit scenarios.
Frequently Asked Questions
What does idempotency mean in an API?
It means repeating the same request has the same intended effect on server state as performing it once. This is especially important when clients retry after timeouts or connection failures.
Is POST idempotent in REST APIs?
POST is not idempotent by HTTP method semantics. An API can make a specific POST operation safely retryable by defining additional behavior such as an idempotency key and server-side deduplication.
What is an Idempotency-Key?
It is a client-generated identifier for one logical operation. The server remembers the key and outcome so retries of that operation can return the original result without repeating the side effect.
Should an idempotency key be a UUID?
A random UUID is a common choice, but the requirement is enough uniqueness within the API’s documented scope. The server should still bind the key to the caller, endpoint and request fingerprint.
How long should idempotency keys be stored?
Long enough to cover the API’s realistic retry window and business requirements. The retention period should be documented because a retry after expiration may be treated as a new request.
Primary References
Make API Retries Safe—and Observable
Use idempotency to protect transaction correctness, and runtime API monitoring to understand when retries, automation or replay behavior deviates from normal use.
