skip to content

An API operation cannot finish within the request — it kicks off a video encode that takes minutes. How would you design the response contract around HTTP status 202 Accepted, and what must the client be able to do afterwards?

level: middleimportance: should knowfreq 40%

answer

  1. 202 = accepted, not done, may fail
  2. Location on 202 = status URL, not the result
  3. Job GET returns 200 even when status = failed
  4. succeeded → link or 303 to the result
  5. Idempotency-Key so a retry reuses the job

basics

~20 s

Return 202 Accepted with a pointer to a job or status resource (Location or a body link). The client polls that resource, which reports pending/running/succeeded/failed and links to the result. 202 means accepted for processing, not done and not guaranteed to succeed.

solid answer

~50 s

`202 Accepted` says: I have taken responsibility for this work, it is not finished, and it may still fail. The contract needs three parts: 1. **A handle.** Return `Location: /jobs/abc123` (and repeat it in the body). Without it, 202 is a dead end. 2. **A status resource.** `GET /jobs/abc123` returns a state machine — `pending | running | succeeded | failed` — plus progress if useful, an error object on failure, and a link to the created resource on success. Serve `Retry-After` to control poll rate. 3. **A terminal outcome the client can consume.** Either the job resource carries the result, or it 303-redirects to `/videos/42` once complete. Make the submission idempotent — an `Idempotency-Key` or a client-generated job id — so a retried POST returns the *same* job rather than starting a second encode. Keep completed jobs readable for a documented retention window, and offer webhooks or SSE for clients that should not poll.

go deeper

for a junior

Know that 202 means the request was accepted but not completed, and that the response must point at somewhere to check the result.

for a middle

Describe the full loop: 202 plus Location, a job resource with a state machine, Retry-After, and a link to the result on success.

for a senior

Add idempotent submission, failure representation, retention of terminal job records, and webhooks or SSE as an alternative to polling.

for a principal

Decide when async is warranted at all, standardize one job-resource shape across services, and back-pressure the queue with 429/503 instead of accepting unbounded work.

## What 202 actually claims `202 Accepted` means the request was valid and has been queued, but processing is incomplete and the eventual outcome is unknown. It is deliberately non-committal: the work may later fail, and 202 does not promise otherwise. That is why it must come with a way to find out what happened. The contrast is with `201 Created` (the resource exists now) and `200 OK` (the work is done). Returning 200 for queued work is a lie clients will build on. ## The three-part contract ### 1. Submission ``` POST /videos/42/encodings Idempotency-Key: 6f1c0b6e-9f6a-4f2c-9b0e-6a1d2e3f4a5b HTTP/1.1 202 Accepted Location: /jobs/abc123 Retry-After: 5 Content-Type: application/json {"jobId":"abc123","status":"pending","statusUrl":"/jobs/abc123"} ``` `Location` on a 202 does **not** mean "the created resource"; it means "where to watch this". Putting the link in the body too is worth it — plenty of client libraries do not surface response headers conveniently. ### 2. Status resource ``` GET /jobs/abc123 HTTP/1.1 200 OK Retry-After: 10 { "jobId":"abc123", "status":"running", "progress":0.42, "submittedAt":"2026-08-12T10:00:00Z" } ``` Note the status *resource* returns `200` — it exists and you are reading it successfully. A frequent error is returning 202 from the polling endpoint; that conflates "the job is unfinished" with "I have accepted your poll request", and it confuses generic HTTP clients. The terminal states matter most: - **succeeded** → include a link to the result (`"result": {"href": "/videos/42"}`), or answer the job GET with `303 See Other` + `Location: /videos/42` so a plain client follows the redirect to the finished artifact. - **failed** → `status: "failed"` with a structured error (stable code + message). The HTTP status of the *job read* stays 200 — the read succeeded; the job failed. Making the poll return 500 on job failure is wrong and triggers client retry logic against a healthy endpoint. ### 3. Cancellation and lifetime Decide whether `DELETE /jobs/abc123` cancels; if it does, define what happens to partial work. Decide how long completed jobs stay readable — a client that polls after your retention window expires must not see a 404 it interprets as "never existed". Document the window and, if possible, keep terminal records much longer than in-flight ones since they are small. ## Idempotent submission Asynchronous endpoints are exactly where duplicate work is most expensive. A client that times out on the POST cannot know whether a job started. Two standard fixes: - **`Idempotency-Key`** on the POST: the server records the key, and a repeat within the window returns the original 202 and the same job id. - **Client-generated job id**: `PUT /jobs/{uuid}` makes submission idempotent by construction. Without one of these, a retry storm becomes a duplicate-encode storm. ## Polling versus push Polling is simple and firewall-friendly but wasteful. Control it with `Retry-After` on both the 202 and the job reads, and document exponential backoff. For high-volume or latency-sensitive cases, offer a push channel — a webhook to a client-registered URL (signed, retried, with the job id so the receiver can reconcile), or SSE/WebSocket for browser clients. Push should be an *addition* to the pollable job resource, never a replacement: webhooks get lost, and clients need a way to ask. ## When not to use 202 If the work reliably completes in tens of milliseconds, do it synchronously — an async contract costs clients a state machine. If the client cannot act on the result at all, a fire-and-forget 202 is fine but say so and skip the job resource. And if the operation *creates* something immediately visible while continuing background work (a draft row that is later enriched), `201` on the resource with a `status` field inside it is often friendlier than 202, because the client gets a real URL straight away. ## Operational notes Monitor queue depth and age of oldest pending job, not just HTTP latency — a 202 endpoint stays fast while the system falls apart behind it. Cap the queue and return `429` or `503` with `Retry-After` when it is saturated, rather than accepting work you cannot do.

  • What status should the job-status endpoint return while the work is still running?
    200 OK with a body whose status field says pending or running, plus Retry-After to pace polling. The read itself succeeded, so 200 is accurate; returning 202 from the poll conflates the job's state with the poll request's outcome and confuses generic clients. Reserve non-2xx on that endpoint for problems reading the job, such as 404 for an unknown id.
  • How do you stop a retried submission from starting the work twice?
    Make submission idempotent: accept an Idempotency-Key header and return the original 202 and job id for a repeat within the retention window, or let the client PUT to a job id it generated so the URL itself deduplicates. Without this, any client timeout or gateway retry can double the workload, which is worst precisely when the system is already slow.

saying these in an interview costs you the question

  • Returning 202 with no way to find the job or the result
  • Returning 200 or 201 for work that has only been queued
  • Making the status endpoint return 202 while running, or 500 when the job failed
  • Assuming 202 means the operation will eventually succeed
  • Deleting job records quickly so late polls look like the job never existed

context