Python: Improving API Reliability with Idempotency Keys

Network failures create an uncomfortable situation for API clients: a request may have succeeded on the server even though the client never received the response.

If the client retries a payment or order-creation request, the server might perform the operation twice. Idempotency keys help prevent this class of duplicate operation.

The Retry Problem

Imagine a client submits an order:

POST /api/orders
Idempotency-Key: 4c0c7f7a-example

The server creates the order, but the connection drops before the response reaches the client. The client cannot determine whether the operation succeeded, so it retries.

Without duplicate-request protection, the application may create two orders.

An idempotency key gives the server a way to recognize that both requests represent the same logical operation.

Define the Key’s Purpose

The client should generate a unique key for one intended operation and reuse that key when retrying the same operation.

A new logical operation should use a new key.

The server should associate the key with enough information to determine whether a request is a valid retry. This often includes the authenticated account, the operation type, and a representation or hash of the request payload.

The key should not be treated as authentication. Authorization must still be checked independently.

Store the Result Reliably

A conceptual database table might contain:

  • A scope, such as account and endpoint.
  • The idempotency key.
  • A request fingerprint.
  • The operation status.
  • The resulting resource identifier or stored response.
  • Creation and expiration timestamps.

A uniqueness constraint on the relevant scope and key is essential. Application-level checks alone can race when two identical requests arrive simultaneously.

A simplified SQL structure could look like this:

CREATE TABLE idempotency_records (
    account_id BIGINT NOT NULL,
    idempotency_key VARCHAR(200) NOT NULL,
    request_hash VARCHAR(64) NOT NULL,
    status VARCHAR(20) NOT NULL,
    response_body TEXT,
    created_at TIMESTAMP NOT NULL,
    PRIMARY KEY (account_id, idempotency_key)
);

Adapt the schema to your database and retention requirements.

Handle Concurrent Requests

Two requests with the same key can arrive at nearly the same time. Both may initially observe that no record exists.

The database must determine which request owns the operation. Use a transaction and a uniqueness constraint, or another carefully designed concurrency mechanism, to ensure that only one request performs the protected action.

The other request should wait, return a suitable in-progress response, or retrieve the completed result according to the API’s contract.

Make Side Effects Part of the Design

Idempotency becomes more complicated when an operation calls an external payment provider, sends a message, or updates another system.

A database transaction cannot automatically make an unrelated external service call atomic. Consider provider-supported idempotency keys, transactional outbox patterns, and reconciliation processes for uncertain outcomes.

Avoid marking an operation as completed before its critical side effect has actually succeeded.

Decide What to Do with Conflicting Requests

If the same key arrives with a different payload, do not silently treat it as the original request. Return a documented client error or other appropriate response.

Also define how long keys remain valid. Retention must cover the realistic retry window while preventing unbounded storage growth.

Conclusion

Idempotency keys make retryable APIs safer by linking repeated requests to one logical operation. Reliable implementations require persistent records, concurrency control, request consistency checks, and careful handling of external side effects.

Retries are an important reliability tool, but they work best when duplicate execution is explicitly addressed.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *