HTTP 201 Created

HTTP 201 Created indicates that the request has been fulfilled and a new resource has been created. The server SHOULD return a Location header with the URI of the newly created resource and include a representation of the resource (or a reference to it) in the response body. This status code is fundamental to RESTful API design — it distinguishes successful creation from other success operations, enabling clients to immediately navigate to or reference the new resource.

Debug HTTP 201 live
Analyze real 201 behavior — headers, caching, CORS, redirects
Open Inspector →

Try it (live endpoint)

Response includes the status code, standard headers (including Content-Type), and a small diagnostic JSON body describing the request and returned status.

Simulator URL (copy in the app after load — not a normal link):

https://httpstatus.com/api/status/201

Example request:

curl -i "https://httpstatus.com/api/status/201"
Try in playground

Meaning

The request has been fulfilled and resulted in a new resource being created.

What it guarantees
  • The server accepted the request and produced a final response.
  • A new resource was created as a result of the request.
What it does NOT guarantee
  • The underlying business operation is correct across all downstream systems.
  • The response is cacheable unless headers explicitly allow it.

When to use this status

  • POST creates a new resource and returns its identifier or representation.
  • Provisioning flows where the server assigns a stable ID at creation time.
  • PUT creates a new resource at a known URI (idempotent create).

When NOT to use this status (common misuses)

Returning 200 for partial failures or errors embedded only in the body.
Clients and monitors treat it as success; failures become silent and harder to alert on.
Returning 200 for creation instead of 201 with Location.
Clients lose a reliable created-resource identifier; SDK behavior becomes inconsistent.
Returning 200 for async acceptance instead of 202.
Clients assume the work is complete and proceed incorrectly.

Critical headers that matter

Content-Type
Defines how clients parse the body.
Clients mis-parse payloads; SDKs and browsers apply wrong decoding.
Cache-Control
Controls cacheability and revalidation.
CDNs/browsers cache dynamic data or fail to cache static content.
ETag / Last-Modified
Enables conditional requests and revalidation.
Unnecessary bandwidth; poor cache consistency.
Location
Points to the newly created resource.
Clients can’t reliably discover the created resource.

Tool interpretation

Browsers
Treats as success; caches/revalidates based on headers and validators.
API clients
Deserializes per Content-Type; conditional requests use validators when implemented.
Crawlers / SEO tools
Indexes depending on headers and canonical stability; caches behavior via validators and cache directives.
Uptime monitors
Typically marks success; advanced checks may flag header anomalies or latency.
CDNs / reverse proxies
Caches/revalidates based on Cache-Control, ETag, and Vary; compression and content-type affect behavior.

Inspector preview (read-only)

On this code, Inspector focuses on semantics, headers, and correctness warnings that commonly affect clients and caches.

Signals it will highlight
  • Status semantics vs method and body expectations
  • Header sanity (Content-Type, Cache-Control, Vary) and evidence completeness
Correctness warnings
  • Missing Location reduces discoverability of created resources.

Guided Lab outcome

  • Reproduce HTTP 201 Created using a controlled endpoint and capture the full exchange.
  • Practice distinguishing status semantics from transport issues (redirects, caching, proxies).

Technical deep dive

201 Created (RFC 7231 Section 6.3.2) is the correct response for POST requests that create a new resource, and for PUT requests that create a resource at the specified URI when it didn't previously exist. The response SHOULD include a Location header pointing to the new resource's canonical URI and an ETag for the new representation. Key detail: 201 responses are NOT cacheable by default (unlike 200). The response body typically contains the created resource with server-generated fields (id, timestamps, computed defaults) so the client doesn't need an additional GET. For idempotent creation patterns, combine 201 with an Idempotency-Key header to prevent duplicate resource creation on retries.

Real-world examples

REST API user registration
POST /api/users with name and email returns 201 Created. The response includes Location: /api/users/42, the full user object with server-assigned id, timestamps, and default settings. The client uses the Location header or response body id to navigate to the new user's profile.
File upload service
POST /api/documents uploads a PDF. The server stores it, generates a thumbnail, extracts metadata, and returns 201 with Location: /api/documents/abc123. The response body includes the document metadata, download URL, and processing status.
Idempotent order creation
POST /api/orders with Idempotency-Key: req-7f3a. The first request creates the order and returns 201. A retry with the same Idempotency-Key returns the cached 201 response without creating a duplicate order, ensuring exactly-once semantics.

Framework behavior

Express.js (Node)
Express requires explicit 201: res.status(201).location('/api/users/' + user.id).json(user). Common mistake: forgetting to set the Location header or returning 200 instead of 201 for creation endpoints.
Django / DRF (Python)
Django REST Framework: return Response(serializer.data, status=status.HTTP_201_CREATED, headers={'Location': f'/api/users/{user.id}'}). DRF's CreateModelMixin automatically returns 201 for successful create operations.
Spring Boot (Java)
Spring: return ResponseEntity.created(URI.create("/api/users/" + user.getId())).body(user); This sets both 201 status and Location header. Alternatively: @PostMapping @ResponseStatus(HttpStatus.CREATED) public User create(@RequestBody User user) { ... }
FastAPI (Python)
FastAPI: @app.post('/api/users', status_code=201, response_model=UserResponse). To include Location: return JSONResponse(status_code=201, content=user_dict, headers={'Location': f'/api/users/{user.id}'}).

Debugging guide

  1. Verify the Location header is present and points to a valid URI — many API implementations forget this required header
  2. Check that the response body contains server-generated fields (id, createdAt) so clients don't need an extra GET
  3. If seeing 200 instead of 201, check the framework's default status — most default to 200 and require explicit 201
  4. For duplicate creation errors, check if the API supports Idempotency-Key headers for safe retries
  5. Verify CORS preflight includes 201 in Access-Control-Allow-Methods if your client is cross-origin

Code snippets

Node.js
app.post('/api/users', async (req, res) => {
  try {
    const user = await db.createUser(req.body);
    res.status(201)
       .location(`/api/users/${user.id}`)
       .json({
         id: user.id,
         name: user.name,
         email: user.email,
         createdAt: user.createdAt
       });
  } catch (err) {
    if (err.code === 'DUPLICATE_EMAIL') {
      return res.status(409).json({ error: 'Email already exists' });
    }
    res.status(500).json({ error: 'Internal server error' });
  }
});
Python
from fastapi import FastAPI
from fastapi.responses import JSONResponse

@app.post('/api/users', status_code=201)
async def create_user(user: UserCreate):
    db_user = await db.create_user(user)
    return JSONResponse(
        status_code=201,
        content=db_user.dict(),
        headers={'Location': f'/api/users/{db_user.id}'}
    )
Java (Spring)
@PostMapping("/api/users")
public ResponseEntity<User> createUser(
        @Valid @RequestBody CreateUserRequest req) {
    User user = userService.create(req);
    URI location = URI.create("/api/users/" + user.getId());
    return ResponseEntity.created(location).body(user);
}
Go
func createUserHandler(w http.ResponseWriter, r *http.Request) {
	var req CreateUserRequest
	json.NewDecoder(r.Body).Decode(&req)

	user, err := db.CreateUser(req)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	w.Header().Set("Location", "/api/users/"+user.ID)
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated) // 201
	json.NewEncoder(w).Encode(user)
}

FAQ

Should 201 Created include the full resource or just the ID?
Best practice is to return the full resource representation. This eliminates the need for a follow-up GET request (the 'POST-then-GET' anti-pattern). Include server-generated fields like id, timestamps, computed defaults, and canonical URLs. For large resources where the full representation is expensive, return a minimal representation with a Location header.
Can PUT return 201 Created?
Yes. PUT returns 201 when it creates a new resource at the specified URI (the resource didn't exist before) and 200 when it updates an existing resource. Example: PUT /api/users/42 creates user 42 if it doesn't exist (201) or replaces it if it does (200). This makes PUT idempotent for both creation and updates.
How do I handle duplicate creation attempts?
Return 409 Conflict when a unique constraint is violated (e.g., duplicate email). For network retry safety, implement idempotency keys: the client sends an Idempotency-Key header, and the server caches the response for that key. Retries with the same key return the cached 201 response without creating duplicates.
Is the Location header in 201 mandatory?
RFC 7231 says the server SHOULD (not MUST) provide a Location header. In practice, it's strongly recommended for REST APIs because it tells clients where to find the new resource. Omitting it forces clients to extract the URI from the response body, which requires knowing the API's URL structure.

Client expectation contract

Client can assume
  • A final HTTP response was produced and processed by the server.
Client must NOT assume
  • The change is durable across all downstream systems.
Retry behavior
Retries are generally unnecessary; treat as final unless domain rules require revalidation.
Monitoring classification
Success
Use payload and header checks to avoid false positives; cacheability depends on Cache-Control/ETag/Vary.

Related status codes

200 OK
The request has succeeded.
202 Accepted
The request has been accepted for processing, but processing is not complete. Often used for asynchronous operations or batch processing.
204 No Content
The server successfully processed the request and is not returning any content.
409 Conflict
The request could not be processed because of conflict in the request.

Explore more

Related guides
Related tools
Related utilities