SiteError.comYour friendly guide to HTTP status codes
Status CodesBlog
  1. Home
  2. Blog
  3. Understanding HTTP 201 Created: The Location Header and Doing Creation Right

Understanding HTTP 201 Created: The Location Header and Doing Creation Right

July 23, 202610 min read
2xxSuccess

201 Created is the success code almost nobody bothers to use. Most APIs return 200 for everything that works — reads, updates, deletes, and creations — and technically nothing breaks. But 201 carries information 200 can't: a new resource now exists that didn't before, and here's its address. That second half is the part everyone forgets — the Location header is what turns 201 from a vanity status code into something clients can act on. This post covers what 201 actually promises, how the Location header works, the surprisingly interesting interaction between 201 and PUT idempotency, and when 200, 202, or 204 is the better fit.

What Is a 201?

A 201 means the request succeeded and a new resource was created as a result — the canonical response to a successful POST /users or a PUT to a URI that didn't exist yet.

The 201 (Created) status code indicates that the request has been fulfilled and has resulted in one or more new resources being created. The primary resource created by the request is identified by either a Location header field in the response or, if no Location field is received, by the effective request URI. — RFC 7231, Section 6.3.2

In plain English: "I successfully created the new resource you requested" — and the response should include a Location header pointing at the newly created resource's URL. (The current spec, RFC 9110, Section 15.3.2, carries the same definition with lightly modernized wording — "effective request URI" became "target URI.")

Note the two-part contract baked into the definition. A 201 asserts:

  1. Something new exists. Not "your request was fine" — a resource was brought into being.
  2. The client can find it. Either the Location header says where, or — if there's no Location — the request's own URI is the new resource (which is exactly what happens with a creating PUT).

That second clause is what distinguishes 201 from a generic thumbs-up, so let's start there.

The Location Header: 201's Whole Point

When a client POSTs to a collection — POST /users — it doesn't know what URL the new resource will live at. The server assigns the ID. The Location header closes that loop:

HTTP/1.1 201 Created
Location: /users/123
Content-Type: application/json
 
{"id": 123, "created": true}

Without Location, the client has to parse your response body, find the ID field, and know your URL structure to reconstruct /users/123 itself — coupling every client to your routing conventions. With it, the client just follows the header. That's the difference between an API that hypertext-driven clients can navigate and one where every consumer hardcodes URL templates.

Details worth knowing:

  • It's a SHOULD, not a MUST. RFC 9110 says the origin server SHOULD send a Location for POST-created resources. Omitting it is legal — the spec then presumes the request URI identifies the resource — but for a POST to a collection that presumption is wrong (/users is not the new user), so in practice: always send it.
  • Relative or absolute both work. Location: /users/123 and Location: https://api.example.com/users/123 are both valid; relative references resolve against the request URI.
  • The body should describe the new resource. Convention (and client convenience) says: echo back the created representation, including its server-assigned fields — id, timestamps, defaults the server filled in. A client shouldn't need a follow-up GET to learn what it just made.
  • Validators may come along. The response can carry an ETag for the new representation, so the client can immediately do conditional requests (If-Match) against the resource it just created.

201 and Idempotency: POST vs PUT

201 is where HTTP's creation semantics get subtle, because two methods can create resources and they behave differently on repeat.

  • POST /users — "create a user; you pick the URL." Every successful POST creates a fresh resource, so every one returns 201 (with a new Location each time). POST is not idempotent; firing it twice makes two users.
  • PUT /users/don — "put this representation at this exact URL." PUT is idempotent: the first request may create the resource (return 201), and repeating the identical request merely updates it to the same state (return 200 or 204).

That gives PUT a genuinely useful property: the status code tells the client whether their PUT created or replaced. Same request, same outcome state, different history — and 201 vs 200/204 is how the server reports which one happened. If you implement upsert-style PUT endpoints, preserving that distinction is a small courtesy that makes clients smarter: "201 → we just onboarded this record; 200 → it already existed and we refreshed it."

The heuristic to remember: 201 reports a state transition (nothing → something), not a request outcome. If no new resource came into existence during this request, 201 is the wrong code — no matter how creation-flavored the endpoint is.

201 vs 200 vs 202 vs 204

Four success codes routinely fight over the same endpoints. They answer different questions:

CodeMeaningUse when
200 OKGeneric success, with bodyReads, updates, and any success where you're returning a representation but nothing new was created
201 CreatedNew resource existsA POST or PUT brought a resource into existence — pair with Location
202 AcceptedQueued, not doneCreation is asynchronous: the request is valid and accepted, but the resource doesn't exist yet
204 No ContentSuccess, nothing to sayUpdates or deletes where the client needs no body back

The 201-vs-202 line matters most in practice. 201 is a receipt of completion: the resource exists as of this response, and GETting the Location will find it. If your "create" endpoint actually enqueues a background job — video processing, account provisioning, anything that finishes later — returning 201 is a lie that clients will discover the moment they follow your Location header into a 404. Return 202 with a way to check progress instead, and save 201 for when the thing is really there.

When to Use 201

The everyday cases, straight from the canonical examples:

  1. A new account was created. POST /users succeeded — return 201, Location: /users/{id}, and the created user representation.
  2. New content was posted. A blog article, a comment, an order — any collection insert where the server assigned the identity.
  3. A file was uploaded. The upload completed and the file is now addressable at some URL — 201 with that URL in Location.
  4. A PUT created (rather than updated) the target. First PUT /users/don → 201; subsequent identical PUTs → 200/204.

And when not to:

  • The operation is async → 202. The resource doesn't exist yet.
  • You updated something that already existed → 200 or 204.
  • The "creation" is a side effect the client can't address — a log entry, a metric increment. If there's no meaningful URL for what was created, a plain 200/204 is more honest.

Returning 201 Correctly

Express / Node.js

res.location() sets the header and chains cleanly with status and body:

app.post("/api/users", async (req, res) => {
  const user = await createUser(req.body);
 
  res
    .status(201)
    .location(`/api/users/${user.id}`)
    .json(user);
});

The body is the full created representation — server-assigned id and all — so the client never needs an immediate follow-up GET.

Next.js App Router

In a route handler, pass the status and Location header through the second argument of NextResponse.json:

// app/api/users/route.ts
import { NextResponse } from "next/server";
 
export async function POST(request: Request) {
  const body = await request.json();
  const user = await createUser(body);
 
  return NextResponse.json(user, {
    status: 201,
    headers: { Location: `/api/users/${user.id}` },
  });
}

REST API JSON body

The full-dress version of a well-behaved creation response — status, address, validator, and representation:

HTTP/1.1 201 Created
Location: /api/users/123
ETag: "33a64df551425fcc"
Content-Type: application/json
 
{
  "id": 123,
  "email": "don@example.com",
  "createdAt": "2026-07-15T09:30:00Z",
  "links": {
    "self": "/api/users/123"
  }
}

Everything the client could want in one round trip: where the resource lives (Location), how to make conditional requests against it (ETag), and what it currently looks like (the body).

(No NGINX or Apache section here — 201 is an application-level statement that your code created something, which is not a judgment a web server or proxy can make on its own.)

201 and SEO

Almost a non-topic, pleasantly: 201 is a response to POST and PUT, and search crawlers discover content with GET. Googlebot should essentially never see a 201 in normal crawling.

If a crawler does receive one, Google's crawling documentation treats all 2xx responses the same way — the content is passed along and considered for indexing. There's no special penalty or bonus. The only real SEO note is a design smell: if GET requests to your indexable pages are somehow returning 201, something upstream is misrouting, and you should fix the response code to a plain 200.

Common Pitfalls

  1. Returning 200 for creations. It works, but it throws information away — clients can't distinguish "created" from "already existed," and there's no conventional place for the new resource's URL. Creation endpoints should say 201.
  2. Omitting the Location header. A 201 from POST /users with no Location forces every client to parse your body and hardcode your URL scheme. The header is one line; send it.
  3. Returning 201 before the resource exists. If creation is queued rather than completed, 201 is false advertising — the client follows Location and gets a 404. Async creation is 202's job.
  4. Pointing Location at the collection. Location: /users after creating user 123 tells the client nothing it didn't know. It must identify the primary resource created — /users/123.
  5. Returning 201 on every PUT. An idempotent PUT should return 201 only when it actually created the target; identical repeats are updates and deserve 200 or 204. Blanket-201 PUTs erase the created-vs-replaced signal.
  6. An empty 201 body with no useful headers. Legal, but now the client knows a resource exists somewhere and nothing else. Either return the representation or make the Location + ETag pair carry the weight.
  7. 201 for batch creations with mixed results. If 3 of 5 items were created and 2 failed, a bare 201 misleads. Either make the batch itself a resource you can 201 (with per-item results in the body) or use 200/207 with explicit per-item statuses.

Wrapping Up

201 is a small code with a precise contract. The rules of thumb:

  • 201 = a new resource exists as of this response — not "the request went fine" (200) and not "we'll get to it" (202)
  • Always send Location pointing at the newly created resource, and return its representation in the body
  • POST-create returns 201 every time; PUT-create returns 201 only on first creation, then 200/204 on repeats — the code tells clients created-vs-replaced
  • If following your own Location header would 404, you wanted 202
  • Include the ETag when you can — the client can go straight to conditional requests

For more, see our pages on 201 Created, 200 OK, 202 Accepted, and 204 No Content. For the other side of the "just return 200 for everything" habit, our understanding 200 OK post covers what that code does — and doesn't — promise.

Full Reference

201 Created

The request succeeded and a new resource was created as a result.

Related Status Codes

✅200OK🎉201Created📋202Accepted📢203Non-Authoritative Information🫥204No Content🔄205Reset Content🍕206Partial Content📊207Multi-Status📝208Already Reported♻️226IM Used
Back to Blog

Popular Status Codes

  • 200 OK
  • 301 Moved Permanently
  • 302 Found
  • 400 Bad Request
  • 401 Unauthorized
  • 403 Forbidden
  • 404 Not Found
  • 500 Internal Server Error
  • 502 Bad Gateway
  • 503 Service Unavailable

Compare Codes

  • 401 vs 403
  • 301 vs 302
  • 404 vs 410
  • 500 vs 502
  • Compare any codes →

Categories

  • Informational
  • Success
  • Redirection
  • Client Error
  • Server Error
  • NGINX
  • Cloudflare
  • AWS ELB
  • Microsoft IIS

Tools

  • Cheat Sheet
  • Status Code Quiz
  • URL Checker
  • API Playground
  • Blog

© 2026 SiteError.com. All rights reserved.