< HTTP/1.1 201 Created201 Created
Resource successfully created. Learn when to use 201 Created, proper response format, and best practices for creation endpoints.
- Cacheable
- Only with explicit freshness
- Client action
- Read Location for the new resource
- Usually sent by
- Origin app (after POST or PUT)
- Spec
- RFC 9110 §15.3.2
On this page
TL;DR: 201 Created means the request succeeded and a new resource now exists. Return its URL in the
Locationheader and, usually, a representation of it in the body.
What it means
The HTTP exchanges on this page are illustrative. The first response creates user 42 and gives the client both its URL and its current representation.
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 40
{"name":"Ada","email":"ada@example.com"}
HTTP/1.1 201 Created
Location: https://api.example.com/api/users/42
Content-Type: application/json
ETag: "v1"
{"id":42,"name":"Ada","email":"ada@example.com"}
RFC 9110 section 15.3.2: the new resource is identified by the Location header, or by the request URL if there is none. The resource must exist when the response is sent; if creation is queued, use 202 instead. Location may be relative. Resolve it against the request URL; an absolute URL must use the public scheme and host, especially behind a reverse proxy.
What you see in your client
A search for “201 error code” usually leads to a success response. Check the status predicate in your application before changing the server: status === 200 rejects valid creation responses.
- Axios: 201 resolves with
response.status === 201under the defaultvalidateStatus. A custom predicate that accepts only 200 producesAxiosError: Request failed with status code 201. Restore the 200–299 range if the endpoint creates resources. See Axios’s defaults and rejection message. - fetch: the promise resolves and
response.okistrue. Readresponse.statuswhen your UI needs to distinguish creation from another successful result. These properties follow the Fetch Standard. - Python requests:
response.raise_for_status()returns normally;response.okisTrue. Its implementation raisesHTTPErrorfor 4xx and 5xx, so there is no built-in201 Client Errormessage. - curl
-f: 201 passes the HTTP failure check. Use-ito see the headers; curl’s manual sets the failure threshold at 400. - .NET:
EnsureSuccessStatusCode()returns the response. The success check accepts 200–299. - Spring:
WebClient.retrieve()usesWebClientResponseExceptionfor 4xx/5xx by default.RestTemplate’s default error handler likewise leaves 201 on the normal response path.
Keep Location separate from redirect handling. A 201 identifies what you just created; it leaves the client on the original request rather than initiating a 301/302 navigation. Read the body when present, or issue a GET to the resource URL if you need its representation.
When to use it
- POST that creates a resource at a server-chosen URL (
POST /usersgives/users/42). - PUT to a URL that did not exist yet: 201 on create, 200 or 204 on replace. This is the standard “upsert” signal.
- Not for: updates (200/204), work that has not finished (202), or “already exists” (409, or 200 with the existing resource for idempotent creates).
201 vs 200, 202 and 204
| Code | Use when |
|---|---|
| 200 | Success; response describes the result of the request |
| 201 | A new resource exists now; Location points to it |
| 202 | Accepted for processing, not complete (return a status URL) |
| 204 | Success, no body (updates, deletes) |
Implementation
Express
app.post('/api/users', async (req, res) => {
const user = await db.users.create(req.body)
res.status(201).location(`/api/users/${user.id}`).json(user)
})
Django REST framework
class UserCreate(generics.CreateAPIView):
serializer_class = UserSerializer
# CreateModelMixin returns 201; it sets Location only if the serialized data has a 'url' key
Go net/http
w.Header().Set("Location", "/api/users/"+id)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(user)
Spring
@PostMapping("/api/users")
ResponseEntity<User> create(@RequestBody User u) {
User saved = service.save(u);
URI location = URI.create("/api/users/" + saved.getId());
return ResponseEntity.created(location).body(saved);
}
Common causes by stack
Express: res.status(201).location('/api/users/42').json(user) explicitly selects creation; res.json(user) alone leaves the response’s status at its existing value. Check the route’s final response call when a successful insert returns the wrong code. The response API documents status, location and json.
FastAPI: @app.post('/users', status_code=201) sets the response status. The default is 200, even when your Python function inserts a row. Set 201 on the path operation that completes creation; queueing work calls for 202.
Django REST framework: CreateAPIView uses CreateModelMixin, whose successful create() response is 201. Its creation behavior sets Location when the representation contains a url key. If you need that header, inspect the serializer output rather than assuming every model serializer includes it.
Spring MVC / Spring Boot: ResponseEntity.created(location).body(saved) builds a 201 and sets Location. Check the controller return value when a saved entity arrives as 200: returning the entity alone skips the created builder.
ASP.NET Core: CreatedAtAction(nameof(GetById), new { id = product.Id }, product) returns 201 with a generated resource URL. Microsoft’s controller return-type guide shows the matching GetById route. Verify that route and its parameter names when the generated URL points somewhere unexpected.
PUT, CalDAV and an empty body
A PUT that creates the resource at its target URL returns 201; replacing an existing representation uses 200 or 204. This distinction comes from RFC 9110 section 9.3.4. For a calendar client uploading a new resource with PUT, inspect whether that URL already existed before interpreting 201 as a failure.
An empty 201 body is valid. Check Content-Type and the endpoint contract before calling a JSON decoder. A successful creation and a JSON parse failure are separate events: repair the decoder or return the promised JSON rather than retrying the write. The response can identify the resource through the request URL when Location is absent.
Common mistakes
- Missing
Location. Spring’sRestTemplate.postForLocationreturns this header’s value. Supply it when the new resource lives at a different URL. - Treating 201 as retry protection. A repeated POST can create another resource. If your API supports an idempotency key, retain the same key for retries of the same operation.
- 201 before the resource exists, for example before an async job inserts the row. Use 202.
- Returning 201 on a duplicate that was silently merged. Return 409 or the existing resource with 200.
- Caching sensitive creation responses. 201 is not heuristically cacheable. Choose an explicit cache policy for the data you return; an
ETagcan support laterIf-Matchupdates.
Reproduce with curl
curl -si -X POST https://api.example.com/api/users \
-H 'Content-Type: application/json' \
-d '{"name":"Ada","email":"ada@example.com"}'
Inspect the response status and location: line. The HTTP version depends on the connection. Use -D - -o /dev/null to print headers without the body, then filter the header you need:
curl -sS -D - -o /dev/null https://api.example.com/api/users \
-H 'Content-Type: application/json' \
-d '{"name":"Ada","email":"ada@example.com"}' \
| grep -i '^location:'
Add -w '%{redirect_url}\n' to print the resolved Location URL. Curl records that header even without following it; the header command above shows the value as sent by the server.
Related
See also the comparison 200 vs 201 vs 204: choosing the right success code for create, read, update and delete.
Frequently asked questions
What does 201 Created mean?
A 201 response means the request succeeded and created a new resource. Location identifies the primary resource when it differs from the request URL; a response body can return its representation.
What is the difference between 200 and 201?
200 means the request succeeded. 201 specifically means a new resource was created. Use 201 for a completed creation, including PUT to a previously unused URL; use 200 when returning the result of another successful operation.
Should 201 include a response body?
A body is optional. Returning the created resource saves a follow-up GET request. Include Location when the new resource has a different URL from the request target.
When should I use 201 vs 204?
Use 201 when creating a resource, with or without a response body. Use 204 when the operation succeeds but there is nothing to return, like a DELETE request.
Sources
Related
200 vs 201 vs 204: Which Success Code to Return
Choose between 200 OK, 201 Created and 204 No Content for REST APIs: what each means, the Location and body rules, raw responses for POST, PUT, PATCH and DELETE.
HTTP 200 OK: What It Means and When to Use It
200 OK means the request succeeded and the response carries the result. What a 200 should contain, and when 201, 204 or 206 is the better answer for your API.
HTTP 404 Not Found: What It Means and How to Fix It
404 Not Found means no resource exists at that URL. Find which layer sent it, then fix routing, deploys, rewrites, CDN cache and soft 404s, with curl checks.
REST API Design with HTTP Semantics
Choose HTTP methods and status codes for a REST API: 201, 202, 204, 400 vs 422, 409, ETag with If-Match, pagination, problem+json and idempotency keys.
202 Accepted
The request was accepted for processing but not completed yet. Learn when to use 202 for asynchronous operations.