· 3 min read · Backend & Databases

Designing APIs for Retries: Idempotency, Timeouts, and Honest Errors

Networks fail in the middle of everything. An API designed for that fact saves its callers from inventing dangerous workarounds.

Accept idempotency keys on mutating endpoints, store the first result, and return it on repeats. Set real timeouts, and make errors say what happened and what to do next.

Retries are a fact, not a behavior problem

Somewhere between your server and your caller there is a load balancer, a mobile network, and a user double-tapping a button. Requests will be sent twice. Responses will be lost after the work already happened. If your API treats every arrival as brand new, your database will eventually contain the proof, twice.

Idempotency keys turn that chaos into a contract. The client generates one key per intended operation and sends it with the request. The server stores the key with the result. A retry gets the original answer replayed, not the operation repeated.

CREATE TABLE processed_requests (
  idempotency_key TEXT PRIMARY KEY,
  response_body JSONB NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
One unique key per intended operation makes duplicates structurally impossible

Timeouts are promises

A request without a timeout is a thread waiting to become an outage. Set budgets at every hop, and make them shrink as you go deeper: the caller gives up first, then the service, then the database query. When everything times out in the right order, failures stay small and explainable instead of cascading into a thread pool full of zombies.

And when you must fail, fail honestly. A 503 with a retry hint beats a 200 carrying an error string, and a specific message beats a shrug. Callers build automation on top of your errors. Vague errors get automated into vague, dangerous retries.

“Design the second request first. The retry is where your API tells the truth about itself.”

What this looks like on a calm Tuesday

None of this needs new infrastructure. A table, a unique constraint, explicit timeouts, and an error format your team agrees to stop improvising. The payoff is that the next network hiccup at 2 a.m. produces a log line and a replayed response, not a duplicate charge and a very awake engineer.