ContentsThe library

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.

FIG 1A six-minute export attempted as one call
stepelapsedthe callerthe gatewayyour workerwhat happened
10swaitingconnection openstartedEverything correct so far. The work is real and it is proceeding.
260swaitingidle timeout, connection cutstill workingSomething in the middle made this decision. It was configured years ago by somebody else and it is not wrong.
361stimeout, no answergonestill workingThe caller has no idea the work continues. From their side this is identical to a crash.
462sretries the requestnew connectiona second export startsTwo copies of a six-minute job now running. The cost has doubled and neither copy will deliver an answer.
5360snothingnothingboth finish, nobody toldThe work succeeded twice and the result went nowhere, because the only address it could have been returned to was closed five minutes ago.
5 steps
No component here is broken. The gateway did its job, the client did its job, the worker did its job twice. The design asked for a guarantee that nothing in the path was built to provide.

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.

FIG 2Submission, then the three answers to a status request
plaintext
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}
The submission returns an address rather than a result, and the status operation returns one of three shapes. Notice that the finished shape points at the result rather than containing it, which keeps the status response small and lets the result live wherever large things live.

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 contents

This 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

The rest of this course

  1. 01Every Awkward Interface You Have Ever Used Was Awkward for the Same Reason, and the Reason Was Decided in the First Half Hour
  2. 02There Are Only Two Questions Worth Asking About Any Operation, and Neither of Them Is What It Is Calledopening only
  3. 03Your Error Message Is Read by a Program First and a Human Second, and Almost Every Interface Gets That Order Backwardsopening only
  4. 04The Second Call Is Not a Mistakeopening only
  5. 05Counting From the Start Versus Remembering the Placeopening only
  6. 06A Limit Is Only Useful If Somebody Can Obey Itopening only
  7. 07Hand Back a Receipt, Not an Answeryou are here
  8. 08Add, Migrate, Then Removeopening only

Read alongside