HTTP 202 is Accepted: the server has taken the request for processing but hasn’t finished, and the work may still fail or never happen (RFC 9110 section 15.3.3). RFC 9110 calls it intentionally noncommittal. HTTP has no way to deliver the real outcome later, so the response ought to describe the request’s current status and point to, or embed, a status monitor the client can check.
The pattern
POST /exports HTTP/1.1
Content-Type: application/json
{"format": "csv", "month": "2026-09"}
HTTP/1.1 202 Accepted
Location: /exports/jobs/7f3a
Retry-After: 5
Content-Type: application/json
{"id": "7f3a", "status": "queued"}
The client polls the monitor until the job ends:
GET /exports/jobs/7f3a HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json
{"id": "7f3a", "status": "done", "result": "/exports/7f3a.csv"}
RFC 9110 defines what Location means on a 201 or a 3xx, not on a 202, so pointing it at the monitor is a convention, as is Retry-After for the polling interval. Azure Resource Manager’s long-running operations work this way. Say in the body where the monitor is too, and report failures there as a status, since the monitor itself answered fine.
202, 201 or 200
the work is done and created something 201 Created, Location names it
the work is done 200 OK or 204 No Content
the work is queued or still running 202 Accepted
Don’t send 202 for work you finished synchronously, and don’t send 201 for a resource that won’t exist until a job runs.
Who sends it
- GitHub’s repository statistics endpoints answer 202 while they compute the numbers; GitHub asks clients to wait a moment and request again.
- AWS Lambda answers an asynchronous invocation (
InvocationType=Event) with 202: the event is queued, not run. - Webhook receivers often answer 202 at once and process the event in the background, so the sender doesn’t time out.
Gotchas
- 202 isn’t heuristically cacheable. Mark the monitor
Cache-Control: no-storeso no cache hands back an oldqueued. - A client that times out and resends the POST can queue the job twice. Deduplicate retried submissions, for example with a client-supplied idempotency key.
app.post('/exports', async (req, res) => { // Express
const job = await queue.add('export', req.body);
res.status(202).location(`/exports/jobs/${job.id}`).set('Retry-After', '5').json({ id: job.id, status: 'queued' });
});
@app.post("/exports", status_code=202) # FastAPI
async def create_export(req: ExportRequest):
job = await queue.enqueue(req)
return {"id": job.id, "status": "queued"}
w.Header().Set("Location", "/exports/jobs/"+job.ID)
w.WriteHeader(http.StatusAccepted)
Search 201 202 in the tool below to compare the two.