Introduction to Asynchronous Operations
Some operations cannot complete within a single request/response cycle: video transcoding, report generation, bulk imports, or third-party provisioning. Holding a connection open for minutes is fragile, ties up server resources, and hits proxy timeouts.
The solution is to acknowledge the request immediately, do the work in the background, and give the client a way to learn the outcome later. HTTP has well-established patterns for exactly this.
1. 202 Accepted
When the server accepts work for later processing, it responds with 202 Accepted. This status explicitly means “the request is valid and queued, but not yet complete.”
POST /reports HTTP/1.1Content-Type: application/json
{ "type": "annual-revenue", "year": 2025 }HTTP/1.1 202 AcceptedLocation: /jobs/8f3aRetry-After: 5Two headers do the heavy lifting: Location points to a resource the client can poll, and Retry-After suggests how long to wait before checking.
2. Job Resources
Model the background task as a first-class job resource with its own URI and lifecycle. This makes progress observable and gives you somewhere to attach errors and results.
GET /jobs/8f3a HTTP/1.1{ "id": "8f3a", "status": "running", "progress": 0.6, "created_at": "2026-07-10T09:00:00Z"}Define a clear status enum and treat it as part of your contract: queued, running, succeeded, failed, and optionally cancelled.
3. Polling for Completion
The client polls the job resource until it reaches a terminal status. Honour Retry-After to avoid hammering the server, and use caching headers so unchanged status responses stay cheap.
When the job succeeds, point the client to the result rather than inlining it. A 303 See Other redirect to the finished resource is idiomatic:
{ "id": "8f3a", "status": "succeeded", "result": { "href": "/reports/8f3a/download" }}A failed job should return the error detail in the body (see error handling) rather than making the poll itself fail.
4. Retry-After Semantics
Retry-After accepts either a number of seconds or an HTTP date. Use it consistently so clients can implement backoff without guessing.
| Situation | Suggested value |
|---|---|
| Job just queued | Short delay (a few seconds) |
| Job running, slow work | Longer delay, possibly increasing |
Rate limited (429) |
Time until the window resets |
Pair server-suggested delays with client-side exponential backoff and jitter, as covered in the idempotency guide.
5. Webhooks vs Polling
Polling is simple but wasteful; most polls return “still running.” Webhooks invert the flow: the server calls the client when the job finishes.
| Polling | Webhooks | |
|---|---|---|
| Complexity | Client only | Client needs a public endpoint |
| Latency | Bounded by poll interval | Near real-time |
| Wasted traffic | High | Minimal |
| Reliability | Client controls retries | Needs delivery retries + signing |
Offer both when you can: webhooks for efficiency, polling as a fallback for clients that cannot expose an endpoint. See the webhooks guide for signing and delivery guarantees.
6. The Prefer Header
The Prefer: respond-async request header (RFC 7240) lets a client explicitly ask for asynchronous handling of an operation that could run synchronously. The server confirms with Preference-Applied: respond-async and returns 202.
POST /imports HTTP/1.1Prefer: respond-async, wait=10The optional wait parameter asks the server to attempt synchronous completion for up to N seconds before falling back to async. This gives clients a single endpoint that adapts to how long the work actually takes, rather than forcing separate sync and async APIs.