Skip to content

Bulk & Async Operations

Real APIs sometimes need to change many resources at once, or kick off work that takes longer than a request should wait. Both stretch the simple one-request-one-resource model, so they need deliberate design.

To act on many items in one call, POST to a batch endpoint with a list, and return a per-item result so callers learn exactly what succeeded:

POST /articles/batch HTTP/1.1
Content-Type: application/json
{ "items": [ { "title": "A" }, { "title": "" } ] }

The response reports each item’s outcome rather than a single overall status — partial success is the norm for batches:

JavaScript

When an operation cannot finish within a request (a big export, a video transcode), do not hold the connection open. Accept the work and return 202 Accepted with a link to a status resource the client can poll:

sequenceDiagram
  participant C as Client
  participant S as API
  C->>S: POST /exports
  S-->>C: 202 Accepted + Location: /exports/job-1
  C->>S: GET /exports/job-1
  S-->>C: 200 OK { status: "running" }
  C->>S: GET /exports/job-1
  S-->>C: 200 OK { status: "done", result: "/files/x" }
202 Accepted plus a pollable status resource

The job itself becomes a resource: POST /exports creates it, GET /exports/{id} reports progress, and the final response links to the produced result.

A bulk API that’s all-fail or all-succeed Symptom:

  • Send 100 records in a batch — one bad record fails the entire batch
  • The client can’t tell which records succeeded and which didn’t
  • Return partial results: { "succeeded": [...], "failed": [...] }

An async job with no status endpoint Symptom:

  • POST /reports returns 202 with no way to check status
  • The client can’t tell whether the job finished, or failed
  • Always return { "jobId": "...", "statusUrl": "/jobs/\{id\}" }

💡 Real-world examples

GitHub API:

  • Bulk operations via the GraphQL API — bundle several mutations into one call
  • Async workflows via the Actions API — POST /repos/\{owner\}/\{repo\}/actions/workflows/\{id\}/dispatches returns 204, with status tracked through the run endpoint

Stripe API:

  • Bulk events via webhooks — Stripe delivers batches of events to the consumer’s endpoint
  • Async payouts — 202 Accepted, status tracked via GET /payouts/\{id\}

Sources: GitHub Actions API — workflow dispatch, Stripe API — payouts

What should a bulk endpoint return for a mixed batch?
Which status fits accepting long-running work to process later?
How does a client track async work after a 202?