Utilo

HTTP 201 Created – Free, Online, Private

201 Created means a new resource exists, and Location should point at it.

What HTTP 201 means

201 says the request created a resource. The Location header carries the URI of that resource. The body, if any, describes the resource. POST is the usual method. PUT can also create. A 200 or 204 after a create forces the client to guess the new URL from the request, which fails when the server assigned the id.

201 Created belongs to the 2xx successful class. Clients that only look at the class will group it with its neighbors. Clients that care about the exact code should use the narrower rule on this page, because the class is only the first digit.

When you see HTTP 201

When you see HTTP 201 Created, stop on that exact status line. 201 Created means a new resource exists, and Location should point at it. The trace that usually produces it: POST /items created a row and Location is /items/42. The 2xx successful class only tells you the first digit. A neighbor in that class is a different fault, and the fix on this page does not transfer to it.

Return 201 when a resource was actually created, including the Location header. If the resource already existed and PUT replaced it, 200 or 204 is more honest. If the work was only queued, use 202. Do not return 201 for an update.

How to fix HTTP 201

To fix HTTP 201, change the condition that made the server choose Created. Follow Location, or read the body if the API documents a body and you already trust that API. Do not assume the new resource is at the URL you posted to. Retrying the POST can create a second resource. Use an idempotency key if the client may retry. PUT to a client-chosen URL is already idempotent. If the log line is a different code, leave this page. The mix-up people make with 201 is this one: 200 OK hides the fact that something was created. 202 Accepted hides the fact that it was not created yet. 204 throws away the new URL.

  • PUT to a new URL created the resource the client named.
  • A handler returned 201 on an update because the framework's default for POST never changes.
  • Location was omitted, so the client could not find the resource it was told exists.

An access log that only stores 201 is not enough to tell these apart. Keep the method, the path, and a request id. Do not keep the Authorization header or a request body that might be a token. The status explains the outcome. It is not a place to echo secrets back to the client either.

Example response

HTTP 201 CreatedHTTP/1.1 201 Created
Location: /items/42
Content-Type: application/json

{"id":42}

Location may be relative. Clients resolve it against the request URI. The body can repeat the id, and many APIs do, but Location is the header that matters. A 201 without Location is incomplete.

What the client should do

Follow Location, or read the body if the API documents a body and you already trust that API. Do not assume the new resource is at the URL you posted to.

A 201 is not a representation you cache in place of a later GET, unless you are implementing a cache carefully. Clients usually GET the Location.

Retrying the POST can create a second resource. Use an idempotency key if the client may retry. PUT to a client-chosen URL is already idempotent.

Codes people confuse with HTTP 201

200 OK hides the fact that something was created. 202 Accepted hides the fact that it was not created yet. 204 throws away the new URL.

Frequently asked questions

Is Location required?

RFC 9110 says the 201 response contains a Location header. Send it.

Can the body be empty?

Yes. Location is enough. A body is a convenience.

What if the resource already existed?

That is not 201. Return 200 or 204 for a replacement, or 409 if the conflict is the point.

Is it safe to retry POST?

Not by default. A second POST can create a duplicate. Send an idempotency key if you retry.

What should I do first when I see HTTP 201?

Read the status as 201 Created specifically. 201 Created means a new resource exists, and Location should point at it. Then check the cause that matches the request you sent, starting with: POST /items created a row and Location is /items/42.