skip to content

Which success status codes are appropriate for POST, PUT and DELETE responses, and what does the Location header mean in that context?

level: seniorimportance: should knowfreq 50%

answer

  1. POST create → 201 + Location (+ ETag)
  2. 202 = queued, must include a status handle
  3. PUT create → 201; replace → 200 or 204 + ETag
  4. DELETE → 204 usually; 202 if async
  5. 204 = no body, ever; Location means created vs redirect by status

basics

~20 s

POST that creates returns 201 with Location pointing at the new resource; other POSTs return 200 with a result, 202 if queued, or 204 if nothing to return. PUT returns 201 when it created the resource, otherwise 200 with the representation or 204. DELETE returns 204, 200 with a result, or 202 if asynchronous.

solid answer

~60 s

**POST**: if it created a resource, **201 Created** with a `Location` header giving the new resource's URI (and normally the created representation in the body). If it processed something and has a result, **200 OK**. If the work was queued and the outcome is unknown, **202 Accepted**, ideally with a link to a status resource. If there is nothing to return, **204 No Content**. **PUT**: if the PUT brought a new resource into existence at the request URI, **201 Created** — no `Location` is needed since the client chose the URI. If it replaced an existing resource, **200 OK** with the new representation, or **204 No Content** if you return nothing. Returning the stored representation plus a fresh `ETag` saves the client a round trip and enables the next conditional write. **DELETE**: **204 No Content** is the usual answer; **200 OK** if you return a result or the final representation; **202 Accepted** if deletion is asynchronous. A key nuance: `Location` on a 201 identifies the **created resource**, whereas on a 3xx it names a redirect target — same header, different jobs.

code

http · 11 lines
http
POST /api/invoices HTTP/1.1
Content-Type: application/json

{"customer":"c-12","amount":4200}

HTTP/1.1 201 Created
Location: /api/invoices/inv-778
ETag: "1"
Content-Type: application/json

{"id":"inv-778","customer":"c-12","amount":4200,"status":"DRAFT"}

go deeper

for a junior

Recall the mapping: create → 201 with Location, nothing to return → 204, ordinary success with a body → 200.

for a middle

Add PUT's create-versus-replace split, when 202 applies, and that 204 must carry no body.

for a senior

Discuss returning ETags to enable the next conditional write, 202 with a status resource, and a consistent policy for repeated DELETEs against retrying clients.

for a principal

Standardise the response contract across services so SDKs, gateways and dashboards interpret creation, asynchrony and idempotent retries identically without per-endpoint special cases.

## Why the codes matter Status codes are the machine-readable part of the response, and generic clients — SDK layers, gateways, dashboards, caches — act on them without reading your body. Getting them right per method is what lets a client distinguish "created", "done, nothing to say", and "accepted, ask later" without parsing JSON. ## POST POST means "process this representation", so its success code depends on what the processing did. - **201 Created** — one or more resources came into existence. RFC 9110 says the primary resource created is identified by the `Location` header field, or by the request URI when no `Location` is given. Send the created representation in the body too; clients then avoid an immediate follow-up GET. Adding an `ETag` lets the client make its next update conditional. - **200 OK** — processing produced a result that is not a new resource: a computed total, a validation report, a search result set. - **202 Accepted** — the request was accepted but processing is **not complete**, and may not even succeed. 202 is a promise to try, nothing more, so it must come with a way to find out: a `Location` (or a body link) pointing at a status/job resource the client can poll. Returning 202 with no handle is a common design bug — the client is told "maybe" with no way to resolve it. - **204 No Content** — the action succeeded and there is deliberately nothing to send back. A subtlety worth mentioning: 201 responses are the natural place for `Location`, and clients should not have to guess the new id from a body field whose name varies per endpoint. ## PUT PUT targets a URI the **client** already chose, which changes the reporting: - **201 Created** — the target did not exist and the PUT created it. `Location` is unnecessary because the request URI *is* the resource's URI; sending it is harmless but redundant. - **200 OK** — an existing resource was replaced and you are returning the resulting representation. This is the friendliest option: the client sees exactly what the server stored, including server-computed fields (normalised values, timestamps, version). - **204 No Content** — replaced successfully, nothing returned. Efficient, and fine when the client already knows the full state. With either 200 or 204, return the new **`ETag`** so the client can immediately issue its next conditional write with `If-Match` without re-reading. What PUT should *not* return: 202 for a synchronous replace (it makes clients poll for nothing) or 200 with a body that silently differs from what was sent without explanation. ## DELETE - **204 No Content** — the default and most common: deleted, nothing to say. - **200 OK** — you are returning something useful, such as the deleted representation or a summary ("3 child records removed"). - **202 Accepted** — deletion was queued (large cascades, background purges, eventual consistency across regions). Again, provide a status handle. The recurring debate is **deleting something already gone**. Two consistent policies exist. Report **404** (or **410** if you keep tombstones), which is literal and helps catch client bugs; or report **204** on the grounds that the desired end state — resource absent — holds, which makes client retries painless. Either is defensible; what matters is picking one and applying it uniformly, and documenting it, because a retrying client behind a flaky network *will* hit this. ## Location: two different jobs `Location` appears in two very different contexts: 1. On **201 Created**, it identifies the **newly created resource**. 2. On **3xx redirects**, it identifies the **target to go to instead**. 3. On **202 Accepted**, it conventionally points at a resource representing the *status of the request*, not the eventual result. Same header, three readings, disambiguated entirely by the status code. Note also the contrast with **`Content-Location`**, which tells the client the URI of the representation *in this message* — useful on a POST or PUT response to say "this body is also retrievable at that URI". ## Response bodies and caching A 204 must have **no body** — sending one is a protocol violation that some clients and intermediaries handle badly. 200 and 201 may carry bodies. State-changing responses should generally not be cached; when a shared cache sees an unsafe method it invalidates stored responses for the effective request URI, but explicit `Cache-Control: no-store` on sensitive responses is still the safe habit. ## Putting it together The answer an interviewer wants is a small table plus the reasoning: creation → 201 + `Location`; asynchronous → 202 + status handle; result to return → 200; nothing to return → 204; and a stated, consistent policy for repeated DELETEs.

  • What must accompany a 202 Accepted to make it usable?
    A way to find out what happened: a Location header or body link pointing at a status resource the client can poll, plus a documented lifetime for that resource. 202 promises only that the request was accepted, not that it will succeed, so without a handle the client can never learn the outcome. Many designs also return the eventual resource's URI once the job completes.
  • A client repeats a DELETE after a timeout and the resource is already gone. What should the server return?
    Either 204 — arguing the desired end state holds, which makes retries harmless — or 404/410 — arguing the specific target no longer exists, which surfaces client bugs. Both are used in production; the failure is inconsistency across endpoints. Pick one, document it, and make sure client SDKs treat the chosen code as success for retry purposes.
  • Does a 201 always need a Location header?
    Not strictly: RFC 9110 says the created resource is identified by Location, or by the effective request URI when no Location is present. For a POST to a collection, the URI is server-chosen, so Location is the only machine-readable way to convey it and should always be sent. For a PUT that created a resource at the client-chosen URI, Location is redundant.

saying these in an interview costs you the question

  • Returning 200 with an id buried in the body instead of 201 with Location after a create
  • Sending a body with 204 No Content
  • Using 202 for work that actually completed synchronously, or 202 without any status handle
  • Inconsistent DELETE-already-deleted behaviour across endpoints
  • Confusing Location on 201 (the created resource) with Location on 3xx (the redirect target)

context