HTTP 204 is No Content: the request succeeded and there is deliberately no body. RFC 9110 section 15.3.5 says a 204 ends with its headers and “cannot contain content or trailers”. It also tells a browser to stay where it is: a link or form that gets a 204 back leaves the current page in place.
What it’s for
DELETE /orders/4182 HTTP/1.1
HTTP/1.1 204 No Content
- A
DELETEthat worked. - A
PUTorPATCHthat saved, when the client already holds the new state. Send the newETagwith it: headers on a 204 still describe the resource. - Beacons and analytics endpoints whose answer nobody reads.
- CORS preflights: the
corsmiddleware for Express answersOPTIONSwith 204 by default.
When the client needs the result back, send 200 with it. When the work will happen later, 202.
Gotchas
response.json()throws on a 204. There’s nothing to parse. Checkresponse.status === 204before reading the body.- No
Content-Length. RFC 9110 section 8.6 forbids it on a 204. A body sent anyway breaks framing: on a reused HTTP/1.1 connection the client can read those bytes as the start of the next response. - Express hides the mistake.
res.status(204).json(data)strips the body andContent-Type, so the client gets nothing and no error is logged. - An empty search result isn’t a 204.
GET /orders?status=lostwith no matches is a 200 with[]; clients then handle one shape. - Not for pages. Google treats a 204 as no content and may report the URL as a soft 404 in Search Console.
Caching
204 is one of the codes RFC 9110 section 15.1 makes heuristically cacheable, so a cache may store a GET that answered 204 without any Cache-Control. If an empty answer can change soon, say so with Cache-Control: no-cache or no-store.
In your stack
res.status(204).end(); // Express; res.sendStatus(204) works too
location = /beacon { return 204; }
return "", 204 # Flask
return Response(status_code=204) # FastAPI
return HttpResponse(status=204) # Django
w.WriteHeader(http.StatusNoContent) // a Write after this returns http.ErrBodyNotAllowed