201 vs 202: Created vs Accepted
201 confirms that a resource was created right now. 202 confirms the request was accepted but processing will happen later.
| Aspect | HTTP 201 — Created | HTTP 202 — Accepted |
|---|---|---|
| RFC | RFC 9110, Section 15.3.2 | RFC 9110, Section 15.3.3 |
| Processing complete? | Yes — resource was created synchronously | No — processing is deferred |
| Resource available? | Yes — immediately accessible at Location URL | Not yet — may not be available for minutes or hours |
| Location header | Required — points to the new resource | Optional but recommended — points to a status endpoint |
| Response body | The newly created resource (or its ID) | Job ID, status URL, estimated time |
| Cacheable | Yes, with appropriate headers | No |
| Idempotency | POST is not idempotent; PUT 201 can be idempotent | Varies by queue implementation |
201 Created: Synchronous Resource Creation
201 Created is the response to a POST or PUT that successfully creates a new resource. The creation happened during this request — by the time the response is sent, the resource exists and is accessible. The Location header contains its URL, and the response body typically contains the full resource representation including server-generated fields like ID, timestamps, and computed properties.
POST /api/users HTTP/1.1
Content-Type: application/json
{"name": "Ced", "email": "ced@example.com"}
HTTP/1.1 201 Created
Location: /api/users/u_001
Content-Type: application/json
{
"id": "u_001",
"name": "Ced",
"email": "ced@example.com",
"createdAt": "2024-04-25T10:00:00Z"
}
The client can immediately follow the Location URL to GET the new resource, link to it, or display it. No polling needed; the resource is there.
202 Accepted: Asynchronous Processing
202 Accepted is appropriate when the operation cannot complete synchronously. Video transcoding, batch imports, report generation, and email sending all fit here. The server accepts the request and queues it, but cannot return the result in this response because the work has not been done yet.
POST /api/exports HTTP/1.1
Content-Type: application/json
{"type": "full-archive", "format": "zip"}
HTTP/1.1 202 Accepted
Location: /api/exports/job_7x9b
Content-Type: application/json
{
"jobId": "job_7x9b",
"status": "queued",
"statusUrl": "/api/exports/job_7x9b",
"estimatedMinutes": 5
}
The client polls the statusUrl or waits for a webhook. The resource (the completed archive) will not be available until the job finishes. RFC 9110 explicitly notes that 202 is “intentionally non-committal” — the server makes no guarantee the operation will succeed.
Decision Rule
Return 201 when the resource exists by the time you send the response. Return 202 when the operation requires work that extends beyond the HTTP request/response cycle. If your POST handler does a database insert in under 100ms and returns the new record, use 201. If it enqueues a job that will run for seconds to minutes, use 202.
A common mistake is returning 201 and immediately returning a resource that is “pending” — technically the database row exists but the resource is not yet usable. If the resource has a pending state, 202 better signals that clients should wait before attempting to use it.
FAQ
Can I return 202 for a POST that creates a resource?
Yes, if the resource creation happens asynchronously. A POST that queues a job to create a resource should return 202 with a job status URL. Once the job completes, a subsequent GET on the status URL can include the new resource’s URL.
Should 202 include a body?
Yes, always include enough information for the client to track the operation: a job ID, a status URL, and ideally an estimated completion time. An empty 202 response is a dead end for the client.
What does the Location header mean on a 202?
On a 202, Location typically points to a status/polling endpoint for the job, not the eventual resource. This differs from 201 where Location is the URL of the created resource. Document this difference clearly in your API.