Skip to content

25 Async Operations

DodaTech 4 min read

title: Async Operations in REST API Design — Complete Guide weight: 35 date: 2026-06-28 lastmod: 2026-06-28 description: Design asynchronous REST API operations using 202 Accepted, status polling, webhooks, and callback URLs for long-running tasks that exceed typical request timeouts. tags: [api-development, rest]


Async operations in REST APIs handle long-running tasks by returning 202 Accepted immediately with a status polling endpoint, allowing clients to check progress without blocking on the initial request.

```mermaid
flowchart LR
  A[Client] -->|POST /reports| B[Server]
  B -->|202 Accepted + Location| A
  A -->|GET /operations/42| C[Server]
  C -->|200 + status: pending| A
  A -->|GET /operations/42| D[Server]
  D -->|200 + status: completed| A
  A -->|GET /reports/42| E[Server]
  E -->|200 + Report Data| A
  style A fill:#e1f5fe
  style B fill:#fff9c4
  style E fill:#c8e6c9

When a client sends a request that takes too long to complete synchronously, the server accepts it (202 Accepted) and returns a Location header pointing to a status resource. The client polls this resource until the status changes to completed or failed. Alternatively, the server can call a webhook URL provided by the client.

Think of async operations like ordering a custom cake. You do not wait at the bakery (synchronous). You place the order (202 Accepted), get a pickup time (status URL), and come back later when the cake is ready (status: completed).

Example: Starting an Async Operation

import requests

# Start a long-running report generation
response = requests.post(
    "https://api.example.com/reports",
    json={"type": "sales_summary", "date_range": "2026-01-01..2026-06-28"}
)
print(f"Status: {response.status_code}")
status_url = response.headers.get("Location")
print(f"Poll at: {status_url}")

Expected output:

Status: 202
Poll at: /operations/op_abc123

Example: Polling the Status

import requests
import time

def poll_operation(status_url, max_retries=30, interval=2):
    for attempt in range(max_retries):
        response = requests.get(f"https://api.example.com{status_url}")
        status = response.json()
        print(f"Attempt {attempt+1}: {status['status']}")
        if status['status'] == 'completed':
            return status['result_url']
        elif status['status'] == 'failed':
            raise Exception(f"Operation failed: {status['error']}")
        time.sleep(interval)
    raise Exception("Operation timed out")

status_url = "/operations/op_abc123"
result_url = poll_operation(status_url)
print(f"Result at: {result_url}")

Expected output:

Attempt 1: pending
Attempt 2: processing
Attempt 3: completed
Result at: /reports/sales_summary_2026.pdf

Example: Webhook Callback Pattern

import requests

# Client provides a webhook URL
webhook_data = {
    "type": "sales_summary",
    "date_range": "2026-01-01..2026-06-28",
    "webhook_url": "https://myapp.com/webhooks/report-complete"
}
response = requests.post(
    "https://api.example.com/reports/async",
    json=webhook_data
)
print(f"Status: {response.status_code}")
print(f"Operation ID: {response.json()['operation_id']}")

# Server will POST to webhook_url when done:
# POST https://myapp.com/webhooks/report-complete
# Body: {status: "completed", result_url: "/reports/op_abc123"}

Expected output:

Status: 202
Operation ID: op_abc123

Common Mistakes

  1. Returning 200 with a polling delay message — Use 202 Accepted for accepted operations, not 200. 200 implies the request completed.
  2. Not providing a status endpoint — Clients should not have to re-send the original request to check status. Provide a dedicated status URL.
  3. Polling with too short intervals — Clients polling every 100ms waste server resources. Recommend a minimum interval of 1-2 seconds in documentation.
  4. Not implementing webhook retries — Webhooks can fail. Implement retry logic with exponential backoff and a dead-letter queue.
  5. Missing operation timeouts — Long-running operations should have a maximum execution time. Return status: failed with a timeout reason if exceeded.

Practice Questions

  1. What status code should you return for an accepted async operation?
  2. How does a client check the progress of an async operation?
  3. What is the advantage of webhooks over polling?
  4. How do you handle webhook delivery failures?
  5. Challenge: Implement an async operation system in Python with a task queue, status polling endpoint, webhook callback, and operation timeout. The system should support concurrent operations and provide estimated completion times.

FAQ

When should I use async operations instead of synchronous?

Use async when the operation takes more than a few seconds, requires significant server resources, or depends on external services with unpredictable response times.

How long should status URLs remain valid?

Status URLs should remain valid for at least 24 hours after completion. Provide a retention policy in documentation so clients can retrieve results.

Should I use webhooks or polling?

Webhooks are more efficient for the server (push instead of poll). Polling is simpler for clients and works through firewalls. Support both.

What information should a status response include?

Include status (pending/processing/completed/failed), estimated completion time, progress percentage, and result URL or error message.

How do I handle async operation cancellation?

Provide a DELETE endpoint for the operation status URL. The server cancels processing and returns a confirmation.

Mini Project

Build an async task processing system in Python. The system should accept tasks via POST (returning 202), provide a status polling endpoint, support webhook callbacks, implement retry logic for failed webhooks, and include operation timeout. Create a CLI client that submits tasks and polls for completion.

What's Next

Now start the API design project and explore further with Building REST APIs with Node.js.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro