What is Idempotency? HTTP Methods, Idempotency Keys, Examples

Idempotency means repeating an operation leaves the same state as doing it once. See which HTTP methods qualify and how keys make POST safe to retry.

What is idempotency?

An operation is idempotent if doing it several times leaves the system in the same state as doing it once. In HTTP, that means a client can repeat a request after a timeout or a dropped connection without causing a second charge, order or email. GET, PUT and DELETE are idempotent by definition. POST is not.

An elevator call button is the usual picture: pressing it ten times still brings one elevator. A "pay" button without protection is the opposite, and ten presses can mean ten charges. Idempotent design makes retries safe.

Idempotency in HTTP methods

RFC 9110 (which replaced RFC 7231) says PUT, DELETE and all safe methods are idempotent:

MethodIdempotent?Why
GET, HEAD, OPTIONS, TRACEYesSafe methods: they read, they do not change state
PUTYesReplaces the resource with the same representation every time
DELETEYesThe first call removes the resource, later calls find nothing left to remove
POSTNoTypically creates a new resource or triggers an action on every call
PATCHNot guaranteedDepends on the patch: setting a value is idempotent, incrementing is not

Idempotent does not mean "returns the same response". A first DELETE may return 204 and a repeat 404, yet the server state is identical. The property describes the intended effect on the server, not the response. RFC 5789 notes that a PATCH request can be issued in a way that makes it idempotent.

Examples

  • PUT /users/123 with {"name": "Alice"} sets the name to Alice. Ten calls, same result.
  • DELETE /sessions/abc leaves no session abc, however often you call it.
  • POST /orders creates a new order each time, so ten calls make ten orders.
  • PATCH /counters/x with {"increment": 1} adds one each time. It is not idempotent.
  • POST /emails sends a message per call. A side effect outside your database still counts.

Why idempotency matters

  • Timeouts and dropped connections. The client cannot tell whether the server processed the request before the connection failed. With an idempotent operation it can simply retry.
  • Automatic retries. RFC 9110 lets clients retry idempotent requests automatically after a connection failure, and says a proxy must not automatically retry a non-idempotent one. SDKs and service meshes follow similar rules.
  • At-least-once delivery. Queues and webhooks can deliver the same message more than once, so the receiver has to cope with duplicates.
  • People. Users double-click submit buttons and refresh half-finished pages.

Idempotency keys

For operations that are not naturally idempotent, such as creating an order or charging a card, the common pattern is an idempotency key: a unique value the client generates for one logical operation and sends with every attempt of it.

POST /charges HTTP/1.1
Idempotency-Key: 8e0a4e6b-1b8e-4e3a-b9f7-5e1d3f9c4b8a
Content-Type: application/json

{"amount": 5000, "currency": "usd"}

The server records the result under that key. A repeat with the same key returns the stored result instead of running the operation again. Stripe is the best-known example and documents the details: the Idempotency-Key header, keys of up to 255 characters, V4 UUIDs suggested, and for API v1 the status code and body of the first request saved and replayed, including 500 errors. Keys can be pruned after at least 24 hours, and reusing a key with different parameters returns an error. Stripe does not need keys on GET and DELETE, because they are idempotent already.

An IETF draft (draft-ietf-httpapi-idempotency-key-header) is working to standardize the Idempotency-Key header. It is still an Internet-Draft, not an RFC. It describes a 409 response when a retry arrives while the original request is still running, and a 422 when a key is reused with a different payload.

Implementing keys on the server

  1. Read the key from the request and store it with a fingerprint of the request body.
  2. Look the key up. If a finished result exists and the fingerprint matches, return it without side effects.
  3. If the fingerprint differs, reject the request (422 in the draft).
  4. If no entry exists, record the key as in progress, run the operation, store the response, and mark it done. A unique constraint on the key column stops two concurrent requests from both running.
  5. If the key is in progress, wait or return 409.
  6. Expire keys after a fixed window so storage does not grow without limit.

Common mistakes

  • Assuming PATCH is idempotent. {"set": 5} is; {"increment": 1} is not.
  • New key per retry. The key belongs to the operation, not the attempt. If each retry generates a fresh key, nothing is deduplicated.
  • Forgetting side effects outside the database. An "idempotent" endpoint that sends an email on every call is not idempotent.
  • No expiry. Keeping every key forever grows storage without limit. Pick a window that covers your clients' retries.
  • Race conditions. Two simultaneous requests with one key can both run unless you lock or use a unique constraint.
  • Mixing up safe and idempotent. Safe means no state change (GET). PUT and DELETE are idempotent but not safe.

Idempotency in load testing and monitoring

  • Non-idempotent endpoints pile up data. A POST test creates records on every iteration, so plan cleanup or use a test environment. At a 5-minute interval, a monitoring check that posts runs 288 times a day, so prefer a read-only check or a dedicated test endpoint.
  • Generate a unique key per virtual user and iteration. If every virtual user reuses one key, they all get the stored first response and your test only loaded the first request.
  • Test the mechanism itself. Send the same request twice, and concurrently, and assert that the responses match and only one record exists. See API testing.
  • Watch retries under load. Clients that retry on timeouts add traffic exactly when the server is slow, which is one way rate limits get hit (see common causes of API rate limiting).

LoadFocus runs JMeter and k6 scripts from 25 cloud regions (load testing), which suits concurrent-retry tests. To feed each virtual user its own keys, see data-driven load testing scripts, and for a first API test, load testing RESTful APIs with LoadFocus. For scheduled read-only checks, use API monitoring. For the CRUD verbs behind these rules, see CRUD operations.

FAQ: idempotency

Is GET always idempotent?

By definition, yes: GET is a safe method and should not change state. Some APIs misuse GET for actions such as tracking or unsubscribing, which breaks that rule and lets prefetching and retries trigger the action.

Is PATCH idempotent?

Not by definition. It depends on the operation: setting a field to a value is idempotent, incrementing a counter is not.

What is the difference between idempotent and safe?

Safe means the request is not meant to change server state (GET, HEAD, OPTIONS). Idempotent means repeating it has the same intended effect as sending it once. PUT and DELETE are idempotent but not safe.

How do I make a POST request idempotent?

Send an idempotency key, a unique value for the operation that you reuse on every retry. The server stores the first result under that key and returns it for repeats.

How long should an idempotency key last?

Long enough to cover your clients' retry window. Stripe prunes keys on API v1 after at least 24 hours. Choose your own window and document it.

Does idempotency apply to GraphQL and gRPC?

Yes. GraphQL queries read data, while mutations need their own protection, for example a key passed in the input. gRPC lets a method be marked as idempotent in its definition, which tells clients when a retry is safe.

How fast is your website?

Elevate its speed and SEO seamlessly with our Free Speed Test.

Free Website Speed Test

Analyze your website's load speed and improve its performance with our free page speed checker.