๐Ÿ—“๏ธ 29042026 1145
๐Ÿ“Ž #api #http

HTTP METHODS IDEMPOTENCY SAFETY

The two formal properties that decide whether an HTTP method is safe to retry, cache, or replay. Misclassifying a method is the root cause of subtle bugs (double charges, lost updates) that survive code review.

The Two Propertiesโ€‹

ABSTRACT

Safe โ€” the method is read-only by intent; calling it should produce no observable side effect on the server. Idempotent โ€” calling the method N>1 times produces the same server-side state as calling it once.

Safety is a stricter promise than idempotency. Every safe method is idempotent; not every idempotent method is safe.

Method Matrixโ€‹

MethodSafeIdempotentCacheableNotes
GETโœ“โœ“โœ“Pure read.
HEADโœ“โœ“โœ“Like GET, no body.
OPTIONSโœ“โœ“(rare)Capability discovery, CORS preflight.
TRACEโœ“โœ“โœ—Diagnostic; usually disabled.
PUTโœ—โœ“โœ—Full replace; same payload twice = same result.
DELETEโœ—โœ“โœ—Repeated deletes leave the same state (gone).
POSTโœ—โœ— (default)(only with explicit headers)The wildcard. Treat as creating something new each call.
PATCHโœ—โœ— (default)โœ—Partial update; depends on the patch representation.

Idempotency vs Same-Responseโ€‹

State idempotency, not response idempotency. A second DELETE /users/42 returns 404 (the user is already gone), not 204. The server state is the same โ€” that's what counts. Common confusion: people conflate "same response" with "idempotent" and incorrectly conclude DELETE isn't idempotent.

PUT writes the same content twice โ†’ server state identical โ†’ idempotent. Even if the second PUT returns a different status (e.g. 200 instead of 201), the resource is the same.

Why POST Isn't Idempotentโ€‹

POST /orders typically creates an order. Calling twice creates two orders. Two charges, two shipments. Bad.

To make POST safe to retry, the application layer adds an idempotency key:

POST /charges HTTP/1.1
Idempotency-Key: 7f8e9c10-...

The server stores the result keyed by the idempotency key; replays return the cached result. See idempotency_keys_api_design.

Why PATCH Isn't Idempotent (in general)โ€‹

PATCH /counter body: { "increment": 1 }

Each call shifts state by +1. Not idempotent.

Versus:

PATCH /counter body: { "value": 5 }

Final state is the same regardless of repetition. Idempotent.

PATCH idempotency is a property of the patch payload, not the method. RFC 5789 explicitly notes this. Default to "treat PATCH as non-idempotent unless your patch grammar guarantees it".

Practical Implicationsโ€‹

NeedWhat method/protection
Read a resourceGET
Create with server-assigned IDPOST + idempotency key
Create with client-supplied IDPUT (idempotent natively)
Replace a resourcePUT
Partial updatePATCH + idempotency key
DeleteDELETE
Trigger an action ("charge", "send")POST + idempotency key (always)
Bulk read with parametersGET with query string (URL-cacheable) or POST (if too long; lose caching)

Caching, Retries, Reverse Proxiesโ€‹

  • Idempotent methods can be retried safely by clients, proxies, and SDKs without coordination.
  • Safe methods can additionally be cached (subject to Cache-Control).
  • Non-idempotent methods must not be auto-retried by infrastructure. Client SDKs (and load balancers) must distinguish.
  • HTTP/2 connection close: a non-idempotent in-flight request gets surfaced as a hard error rather than auto-retry, by spec.

Common Pitfallsโ€‹

  • POST that "kind of feels like a read" โ€” search endpoints with complex bodies. Tempting to POST, but loses caching and retry-safety. Prefer GET with long query strings or a dedicated query language.
  • DELETE that returns 404 the second time โ€” that's correct! Don't treat it as an error in the client retry logic.
  • PATCH with addition operations โ€” += 1, append, etc. Not idempotent. Use idempotency keys or restructure to PUT.
  • Idempotent POST without idempotency key โ€” saying "our POST is idempotent because the body is the same" is not enough; the server must dedupe. Without a key, the server has no stable identifier for "same logical request".
  • Retrying POST in load balancers โ€” proxies that retry on 5xx will double-create unless idempotency is enforced server-side. Most reverse proxies are conservative here for a reason.
  • GET with side effects โ€” analytics endpoints fire on GET pings. Technically violates safety. Browsers prefetch GETs aggressively (link rel=prefetch); a GET that has side effects can fire silently. Use POST/Beacon for analytics events.

Referencesโ€‹