Designing an Interface to Call
Hand Back a Receipt, Not an Answer
Last timeSlowing a Caller Down
When an operation cannot finish inside one call, the shape changes: you accept the work, return an address, and the caller learns to live without an immediate answer.
When a call is the wrong shape
Some operations do not fit in a request. Rendering a report over a year of
data, re-encoding a video, exporting half a million records, running a model
over a large input. These take minutes, and a request that takes minutes is
not a slow request, it is a request in the wrong shape.
The reasons are mostly infrastructure. Load balancers, proxies and gateways
cut connections that have been idle for a while, and the usual defaults are
thirty or sixty seconds. The caller's own client has a timeout too, often
shorter. So the connection is likely to be severed by something in the middle
that has no idea the work is proceeding fine.
What happens next is the part that matters. The caller sees a timeout,
retries, and your service starts the same expensive work a second time while
the first copy is still running. Now you are doing it twice, and the second
copy will probably be cut off too. And if your own process restarts at any
point during those minutes, nobody anywhere can say whether the work
happened, because the only record of it was an open socket.
| step | elapsed | the caller | the gateway | your worker | what happened |
|---|---|---|---|---|---|
| 1 | 0s | waiting | connection open | started | Everything correct so far. The work is real and it is proceeding. |
| 2 | 60s | waiting | idle timeout, connection cut | still working | Something in the middle made this decision. It was configured years ago by somebody else and it is not wrong. |
| 3 | 61s | timeout, no answer | gone | still working | The caller has no idea the work continues. From their side this is identical to a crash. |
| 4 | 62s | retries the request | new connection | a second export starts | Two copies of a six-minute job now running. The cost has doubled and neither copy will deliver an answer. |
| 5 | 360s | nothing | nothing | both finish, nobody told | The work succeeded twice and the result went nowhere, because the only address it could have been returned to was closed five minutes ago. |
Accept the work, return an address
The fix is to separate accepting the work from doing it. The request arrives,
you validate it, write down that the work is to be done, and reply
immediately with the address of a job. The reply means accepted, not
finished, and it takes milliseconds.
POST /exports
{range: 2026-01-01 to 2026-09-30, format: csv}
202 Accepted
Location: /exports/exp_9Dm3kR
{id: exp_9Dm3kR, state: queued, submitted_at: 2026-10-09T14:12:08Z}
GET /exports/exp_9Dm3kR
while working
{id: exp_9Dm3kR, state: running, progress: 0.42, started_at: ...}
when done
{id: exp_9Dm3kR, state: succeeded, result: /exports/exp_9Dm3kR/file,
expires_at: 2026-10-16T14:12:08Z}
when it fails
{id: exp_9Dm3kR, state: failed, code: range_too_large,
retryable: false, detail: narrow the range to 90 days}Two design points are easy to get wrong here.
The submission should validate everything it can before accepting. A request
that is malformed, unpermitted or impossible should be refused on the spot
with the ordinary four classes of failure from the third lesson. Accepting
work you already know will fail converts an immediate, actionable error into
a job that fails two minutes later, which is strictly worse for everybody.
And the submission is exactly the operation that needs a key from the fourth
lesson. It is a creating operation over an unreliable connection, so it will
be retried, and a retry should return the address of the existing job rather
than starting a second one.
The lesson stops here
5 more paragraphs to go
You have read the opening. The rest of the argument, the problems that check whether it landed, and the lines worth keeping at the end all come with a plan.
The first lesson of every course in the library reads the whole way through, free, so you can see exactly what the rest of them are.
See the planThe contentsThis is the reading half
Starting the course gives you your own copy of it. Every idea on every page has problems standing under it, marked with a reason rather than a tick, and any sentence you do not believe can be opened and argued with. None of that can happen on a page nobody owns.
The contents