Designing Idempotent APIs and Resilient Pipelines

Networks are inherently unreliable, meaning API requests often fail or retry, resulting in duplicate actions. Implementing idempotency keys—unique identifiers sent by the client—ensures your system processes a mutation exactly once. This safeguards against critical bugs like double-charging and allows complex, multi-step pipelines to safely resume after mid-process failures.
One of the easiest ways to spot a junior engineer is their unshakeable optimism about network reliability. They write code under the assumption that a client request will arrive exactly once.
In the real world, networks are chaotic. A request doesn't arrive exactly once; it arrives anywhere from zero to N times. If your API isn't designed to handle those N attempts gracefully, you're going to end up with duplicate database entries, corrupt states, or worse—double-billing your users.
Let's look at how we can solve this using idempotency keys.
What is API idempotency and why should you care?
Idempotency is a system design property where executing the same request multiple times yields the exact same state and response as the first execution. It is vital because network timeouts and retries mean your servers will inevitably receive duplicate requests for the same action.
Imagine a mobile client attempting to subscribe to a service. The client sends a payment request, your database processes it, but the cellular connection drops before your HTTP 200 response reaches the phone. To the user, it looks like a hang. To the app, it looks like a failure. Naturally, the app retries.
Without an idempotent design, your server processes that second request as a brand-new transaction. Suddenly, you have a very angry customer who has been charged twice for a single subscription.
How do idempotency keys solve duplicate requests?
Idempotency keys solve duplicate requests by requiring the client to send a unique identifier (like a UUID) with every mutation request. The server records this key upon the first successful execution; if it sees the same key again, it short-circuits the processing and returns the cached response.
Instead of rerunning the business logic, you treat the second request as a harmless retry. You simply fetch the saved response from your cache and send it back.
Architecturally, this requires a fast lookup layer. Before any business logic runs, your application checks a high-speed database or cache to see if the UUID already exists. If the key is found, the server immediately returns the previously saved response payload, whether that payload is a "success" message, an error, or a status update. If the key is not found, the server processes the request normally, saves the final output against the UUID, and delivers the response.
How does idempotency protect multi-step orchestration pipelines?
In multi-step pipelines, idempotency allows you to replay an entire workflow from the beginning if a failure occurs halfway through. Steps that successfully completed on the first run recognize the duplicate idempotency key, skip execution, and immediately return their previous output to resume the pipeline.
Imagine you are building a 10-step data processing pipeline where each step is computationally expensive. If step 5 fails due to a transient database timeout, you do not want to scrap the entire run and start from scratch.
If you build each step with idempotency in mind, you can safely replay the entire pipeline from step 1. Steps 1 through 4 will look at the incoming event key, realize they already completed that work, skip the expensive computation, and pass the cached output forward. The pipeline seamlessly resumes real work at step 5.
The Idempotent Request Lifecycle
| Step | Component | Action | Expected Outcome |
|---|---|---|---|
| 1 | Client | Generates a unique UUID and attaches it to the Idempotency-Key header |
Establishes a unique identity for the specific mutation attempt |
| 2 | Server | Checks cache or database for the incoming key | If found, it short-circuits and immediately returns the cached response |
| 3 | Server | Locks the key, processes the action, and saves the result | Prevents race conditions from simultaneous duplicate requests |
| 4 | Server | Saves response to the cache and releases the lock | Ensures future retries receive the correct final output immediately |
FAQ
What happens if two identical requests arrive at the exact same millisecond?
To prevent race conditions, your server should acquire a distributed lock on the idempotency key as soon as the first request arrives. The second request will fail to acquire the lock and must wait until the first request completes and caches its response.
How long should you store idempotency keys in your database or cache?
For most transactional APIs, storing keys for 24 to 48 hours is standard. This provides a wide enough window for clients to retry failed network calls while preventing your database or Redis cache from growing indefinitely.
Should read operations (like GET requests) use idempotency keys?
No, standard HTTP GET requests are naturally idempotent because they only retrieve data without changing the state of the system. Idempotency keys should only be applied to state-mutating requests, such as POST, PUT, and PATCH operations.



