The API queues the job. The job will take thirty seconds. The API returns 200. The client has no idea whether the work succeeded, is still running, or was silently dropped.
This is common. It is wrong. The HTTP specification has a status code for this case: 202 Accepted.
What 202 Means
202 Accepted means: I received your request, I have decided to act on it, but I have not done it yet. The response body should tell the client how to check what happened.
The standard pattern:
HTTP/1.1 202 Accepted
Location: /jobs/abc123
Retry-After: 10
{
"job_id": "abc123",
"status": "pending",
"status_url": "/jobs/abc123"
}The client polls /jobs/abc123. The Retry-After header tells it how long to wait before the first poll. When the job finishes, the status endpoint returns the result — either inline or via a redirect to the completed resource.
The Decision Tree
When your endpoint receives a request and produces an output, one question determines the status code: is the work done when you return the response?
- 200 OK: Work is done. Response contains the result.
- 201 Created: Work is done. A new resource was created.
Locationheader points to it. - 202 Accepted: Work was accepted and queued. The resource does not exist yet. Response contains a way to check status.
- 204 No Content: Work is done. Nothing to return.
If yes: 200, 201, or 204. If no: 202.
The Location Header Is Not Optional
A 202 without a Location header or equivalent status information in the body is a broken contract. You have told the client their work was accepted and given them no way to find out what happened next. They will retry the original request. You will process the job twice.
The status endpoint needs to distinguish between:
- pending: still queued
- running: in progress
- completed: done, with result or link to result
- failed: failed, with structured error details
Clients write retry logic against this contract. Ambiguous status produces ambiguous behavior at 2am during an incident.
The Monitoring Blind Spot
Here is the operational problem with 200-for-queued-work: your uptime monitor reports 200 as healthy. Your error rate dashboard shows nothing. Meanwhile your job queue is backed up, workers are crashing, and nothing is completing.
When your API returns 202 correctly, the job status endpoint becomes a monitoring surface. Alert on jobs stuck in pending for more than N minutes. Alert on failure rate from the status endpoint. Track queue depth. A 200-for-everything API hides all of this from your observability stack.
Three Patterns That Fail
Returning 200 with a job ID in the body. The client must parse the body to discover that work is still pending. Nothing in the status code signals async behavior. New clients reading the documentation will assume synchronous completion and not poll.
Returning 202 without a status endpoint. You have signaled async correctly but provided nowhere to go. Clients will retry the original submission. You will process the job multiple times and face idempotency problems you did not plan for.
Returning 202 for operations that complete in under 100ms. If you can complete the work synchronously within your response window, do it. 202 is overhead: it requires client polling, a status table, cleanup logic for abandoned jobs, and increased client complexity. Do not add async machinery for fast operations.
Retry-After Belongs in Your 202
The Retry-After header tells the client how long to wait before polling. Without it, clients implement their own backoff — usually wrong, often aggressive. Use a value that reflects expected job duration: ten seconds for a job expected to take thirty seconds. The status endpoint should also return an updated Retry-After while the job is still running.
Clients that respect the header will poll at the right cadence. The clients that ignore it will poll anyway. Either way, providing the signal is the correct default.
Building an API-first product? builds.anethoth.com tracks software projects in progress — public build dossiers with real milestones.