Arezgitfield notes / engineering
Engineering practiceUPDATED AUG 18, 2026

Design Idempotent API Write Operations

A server-side design for safe API retries using idempotency keys, atomic records, payload fingerprints, concurrent-request handling, retention, and observability.

AREZGIT / FIELD NOTEENGINEERING PRACTICE
A client can lose the response after a server completes a write. Retrying may create a second order, enqueue a second job, or apply the same transition twice. The server cannot solve this by
READ / VERIFY / APPLYTECHNICALLY REVIEWED

A client can lose the response after a server completes a write. Retrying may create a second order, enqueue a second job, or apply the same transition twice. The server cannot solve this by assuming that one network request equals one business action.

Idempotency gives repeated attempts for the same intended operation one durable outcome. It requires more than an Idempotency-Key header: key scope, payload identity, concurrency, storage, retention, authorization, and failure states must form one server-side contract.

Separate HTTP method semantics from business idempotency

RFC 9110 defines an idempotent method as one where multiple identical requests have the same intended effect as one request. PUT and DELETE are idempotent by method semantics; POST is not inherently idempotent.

The response to repeated idempotent requests need not be identical. A first DELETE might succeed and a later one might report that the resource is already absent. What matters is the intended server effect.

Business operations can need stronger handling. A POST /jobs request may be retried after a timeout. If the server supports an idempotency key, it can associate both attempts with one job-creation result. Method choice alone does not provide that association.

Define key scope and payload identity

An idempotency key must be unique within a documented scope. Scope it at least by authenticated principal or tenant and operation. A key used by one account must not reveal or replay another account's result.

Store a fingerprint of the canonical request fields that define the operation. Exclude transport noise such as tracing headers, but include every value that changes the intended effect. When the same key arrives with a different fingerprint, reject it with a stable client error rather than silently returning the first result.

Canonicalization needs a specification. JSON member order may not be meaningful, while omitted and explicit null fields may or may not be equivalent under the API contract. Hashing raw bytes is simple but can reject semantically equivalent encodings; hashing parsed values is safer only when normalization preserves all meaningful distinctions.

Keys should be opaque, high-entropy values generated by the client. Do not embed credentials, personal data, or sequential business identifiers in them. Validate length and character constraints before storage.

Claim the key atomically with the business operation

The server must handle two identical attempts arriving concurrently. A “check then insert” sequence without a uniqueness constraint can let both proceed.

Use a unique database constraint over the defined key scope and an atomic state transition. A useful record contains:

  • scoped key identity;
  • request fingerprint;
  • status such as in progress, succeeded, or terminally failed;
  • stable resource identifier or replayable response reference;
  • creation and expiry timestamps;
  • bounded diagnostic metadata.

When possible, create the idempotency record and perform the business write in one transaction. If the business effect occurs in an external system, use an outbox, downstream idempotency contract, or reconciled state machine. A local record cannot undo a duplicate effect already accepted by a non-idempotent dependency.

Concurrent duplicates should either wait for a bounded period, return a clear “in progress” response, or replay a completed outcome. They must not independently execute the operation.

Model ambiguous and failed outcomes

Not every failure should be cached forever. Distinguish failures before execution from terminal business outcomes and ambiguous dependency results.

  • Validation failure occurred before the operation and may be returned consistently for that payload.
  • Authorization must be evaluated for each request under current policy; a stored result must not bypass it.
  • A domain rejection such as an invalid state transition can be a stable terminal outcome.
  • A timeout after sending a request to a dependency is ambiguous until reconciled.
  • An internal crash may leave an in-progress record requiring recovery.

Do not mark an operation failed and permit a fresh execution merely because the caller saw a timeout. First determine whether the original effect occurred. Recovery workers should use the same state machine and downstream identifiers as the request path.

Return stable resource identity when replaying a success. The API may also return the original status and selected response fields, but avoid storing sensitive full responses unless retention and access controls justify it.

Bound retention without surprising retrying clients

Idempotency records cannot grow forever. Choose a retention period from the maximum credible retry window, asynchronous completion time, client offline behavior, and business consequences of duplication. Publish the behavior after expiry: the server may treat the key as new, reject it as expired, or consult a longer-lived business uniqueness rule.

Expiry cleanup must not race with an in-progress operation. Separate “eligible for client retry” from “safe to delete,” and retain durable business identifiers according to their own data policy.

An idempotency key does not replace a domain uniqueness constraint. If only one subscription may exist for an account and plan, encode that invariant in authoritative storage as well.

Test concurrency and recovery, not only replay

The minimum test set includes:

  • same key and same payload after success;
  • same key with a different payload;
  • two simultaneous identical requests;
  • crash after key claim but before business commit;
  • business commit followed by response loss;
  • dependency timeout with an unknown remote outcome;
  • retry after the key expires;
  • key reuse under another tenant;
  • authorization changes between attempts.

Observe first execution, replay, conflict, in-progress wait, expiry, and reconciliation as separate bounded metrics. Never put raw keys or request bodies in logs. A keyed hash or internal record ID can correlate events without exposing the client token.

Idempotency is successful when the server can explain the state of an intended operation after retries, concurrency, and partial failure. Suppressing duplicate HTTP handlers is insufficient; the business effect and every downstream boundary must participate in the design.

READER SIGNAL

Was this guide useful?

Your rating helps us prioritize clearer, more practical technical content.

AREZGIT / DESKTOP WORKSPACEFROM FIELD NOTE TO RELEASE

Review diffs, run checks, and prepare the release in Arezgit.

Keep Git review, security scanning, API checks, database inspection, and release preparation together in one local desktop application.

Explore Arezgit