TT Lab
Get started
Learn Learning paths Courses

Queues and Asynchronous APIs

The Contract 202 Accepted Begins

Continue in TT Lab

Summary

202 means "received", not "done". Therefore an API that returns 202 must also tell you "where to check the result".

Why this was needed

If you make a 30-second job a synchronous API, three things break at once. It hits the load balancer's idle timeout (usually 60 seconds), if the client retries the same job runs twice, and the server workers are tied up for 30 seconds each.

An asynchronous API splits this into two stages: acceptance and checking. Acceptance finishes quickly and returns an identifier. Checking asks for the status with that identifier.

How it works

HTTP already has vocabulary for this pattern.

The acceptance response is 202 Accepted, with the status lookup URL in the Location header. The body contains the job identifier and the current status. Without these three, the client does not know what to do.

A status lookup returns the current state with 200. The simpler the status vocabulary the better — something like queued, running, succeeded, failed. If it is still in progress, it suggests the next polling time with Retry-After. Without this, clients each poll every second and the status API becomes the new bottleneck.

When it completes, there are two ways to deliver the result. You either include the result in the status response body, or point to the result resource URL with 303 See Other. The latter is more RESTful but increases client implementation effort. If the result is small, the former is enough.

Submission must also be idempotent. If the connection drops before the client receives 202 and it retries, two of the same job are created. Require an idempotency key on submission and return the same job id for the same key.

A webhook is an alternative to polling. On completion, the server POSTs to the client's URL. There is no delay and no polling load, but there is a cost — the client must have a public endpoint, that endpoint may be down so the server must have retries and a DLQ, and a signature is needed to prevent forgery. In practice, webhooks and polling are often offered together.

What you meet in the field

The most common design mistake is using the ID of the real result resource as the job id. It means handing out in advance the ID of a resource that has not been created yet, so when it fails, that ID becomes a ghost forever. It is safer to keep the job id and the result id separate.

Second. Do not keep job state forever. If you put a TTL on the state of completed jobs and give 410 Gone instead of 404 for expired lookups, the client can distinguish "never existed" from "existed and has passed".

The contract after returning 202

202 Accepted only means "received", not "it will be done". So you must provide three things together for the client to be able to write code.

HTTP/1.1 202 Accepted
Location: /api/jobs/7f3a-91cd
Retry-After: 3
Content-Type: application/json

{"job_id": "7f3a-91cd", "status": "queued",
 "poll_url": "/api/jobs/7f3a-91cd", "estimated_sec": 30}

A status lookup distinguishes at least four states.

{"status": "queued"}                                  → 아직 시작 안 함
{"status": "running", "progress": 0.4}                → 진행 중
{"status": "succeeded", "result_url": "/files/…"}     → 결과가 있다
{"status": "failed", "error": {"code": "…","message": "…"}, "retriable": false}

retriable is important. The client uses this value to decide whether to send again or to ask a person.

Notification instead of polling

Polling is simple but wasteful. There are three alternatives, each with a different value.

Method Server burden Client complexity Firewall
Polling As many as the requests The simplest No problem
Long polling Holds connections Simple Watch for proxy timeouts
SSE Holds connections Moderate Usually passes
Webhook The least Needs a receiving endpoint The other side must have a public address

If you use a webhook, you must design retries and signatures together. If the receiver dies briefly, you must send again, and to prevent forgery you must attach an HMAC signature to the body.

Preventing duplicate submission with an idempotency key

If the client times out before receiving 202, it sends the same request again. Then two jobs are created.

POST /api/jobs
Idempotency-Key: 8f14e45f-ea3b-4d29-9a1c-2b3c4d5e6f70

The server stores this key, and when the same key arrives again, it does not create a new one but returns the 202 of the job it first created as is. The client makes the key, but it must not change between retries, so you either derive it deterministically from the request content or generate it once at the start of the request and keep it.

A retention period of about 24 hours is common. If you keep it longer, the store grows, and if shorter, slow retries are not caught.

What you will do in the next lab

You build a job submission API that returns 202 and Location precisely, have a worker transition the state, regulate polling with Retry-After, make submission idempotent, and finally attach a completion webhook.