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.
Bulk operations
Section titled “Bulk operations”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.1Content-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:
Long-running (async) work
Section titled “Long-running (async) work”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" } The job itself becomes a resource: POST /exports creates it, GET /exports/{id} reports progress, and the final response links to the produced result.
Common mistakes
Section titled “Common mistakes”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 /reportsreturns 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\}/dispatchesreturns 204, with status tracked through the run endpointStripe 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