HTTP Status Codes Reference
Every HTTP status code with its meaning, RFC citation, cacheability and handler snippets — searchable instantly, entirely on this device.
All 66 status codes shown
1xx — Informational
| Code | Name | Summary | Copy |
|---|---|---|---|
| 100 | Continue | Interim reply to Expect: 100-continue — the headers look acceptable, keep sending the body. | |
| 101 | Switching Protocols | The server accepts the client's Upgrade offer and is switching the connection to that protocol. | |
| 102 | Processing | WebDAV interim reply: the server took the request and is still working on it. | |
| 103 | Early Hints | Interim reply carrying Link headers so the browser can start preloading before the final response. |
The same 1xx codes in full — each row above links to its entry.
100 Continue
What it means
The server has read the request headers and is willing to accept the body that follows. It answers a request that carried Expect: 100-continue, letting a client confirm the server will take a large upload before actually transmitting it.
It is an interim response: a final status code always follows on the same exchange. Browsers and most HTTP libraries handle the handshake internally, so application code almost never observes it.
When to use it
- Acknowledge Expect: 100-continue before reading a large request body you intend to accept.
- Spare clients from streaming a big upload to an endpoint that would reject the request on its headers alone.
When not to use it
- Never as a final response — a definitive status must still follow.
- Not for requests that did not send Expect: 100-continue; unsolicited interim replies are noise.
Cacheability
Not applicable: interim 1xx responses are never stored by caches — RFC 9111 governs final responses only.
Idempotency context
Interim responses carry no idempotency weight of their own; what matters is the method of the request still in flight.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(100).end(); // 100 Continue
});from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/api/resource")
def handler(): # 100 Continue
return Response(status_code=100)location /api/resource {
return 100; # Continue
}// Interim responses never reach fetch(): the browser consumes
// 100 Continue and resolves with the final response that follows.
const res = await fetch("/api/resource");
console.log(res.status); // the final status, never 100curl -i https://example.com/api/resource
# HTTP/1.1 100 Continue101 Switching Protocols
What it means
Sent when the server agrees to a client's Upgrade header and changes what the current connection speaks — the classic case is the WebSocket handshake. The response's own Upgrade header names the protocol now in effect.
After the 101 header block, the connection is no longer HTTP/1.1: whatever follows belongs to the new protocol.
When to use it
- Complete a WebSocket handshake against an HTTP/1.1 endpoint.
- Switch to any protocol both peers negotiated through the Upgrade mechanism.
When not to use it
- HTTP/2 and HTTP/3 never use it — protocol selection happens via ALPN in the TLS handshake instead.
- Not unless the request actually offered Upgrade and you really switch; a 101 with no follow-through strands the connection.
Cacheability
Not applicable: interim 1xx responses are never stored by caches — RFC 9111 governs final responses only.
Idempotency context
Interim responses carry no idempotency weight of their own; what matters is the method of the request still in flight.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.set("Upgrade", 'websocket');
res.set("Connection", 'Upgrade');
res.status(101).end(); // 101 Switching Protocols
});from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/api/resource")
def handler(): # 101 Switching Protocols
return Response(status_code=101, headers={"Upgrade": "websocket", "Connection": "Upgrade"})location /api/resource {
add_header Upgrade 'websocket' always;
add_header Connection 'Upgrade' always;
return 101; # Switching Protocols
}// Interim responses never reach fetch(): the browser consumes
// 101 Switching Protocols and resolves with the final response that follows.
// Its headers (Upgrade, Connection) are handled at the protocol layer.
const res = await fetch("/api/resource");
console.log(res.status); // the final status, never 101curl -i https://example.com/api/resource
# HTTP/1.1 101 Switching Protocols
# Upgrade: websocket
# Connection: Upgrade102 Processing
Semi-standard
What it means
A WebDAV server sends 102 while a long-running request (a deep COPY or MOVE, say) is underway, so the client's timeout does not fire before the final answer arrives.
It never carries a result — a final status always follows once the work completes.
When to use it
- Keep a WebDAV client from timing out during an operation that legitimately takes a while.
When not to use it
- New designs — answer slow work with 202 plus a status resource, or send 103 for the preload case.
- Anywhere outside WebDAV; general HTTP clients may not understand it.
Cacheability
Not applicable: interim 1xx responses are never stored by caches — RFC 9111 governs final responses only.
Idempotency context
Interim responses carry no idempotency weight of their own; what matters is the method of the request still in flight.
Defined in
Scope: WebDAV only. Defined in RFC 2518 and dropped from the RFC 4918 revision for lack of implementations — still IANA-registered, so it earns semi-standard standing here.
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(102).end(); // 102 Processing
});from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/api/resource")
def handler(): # 102 Processing
return Response(status_code=102)location /api/resource {
return 102; # Processing
}// Interim responses never reach fetch(): the browser consumes
// 102 Processing and resolves with the final response that follows.
const res = await fetch("/api/resource");
console.log(res.status); // the final status, never 102curl -i https://example.com/api/resource
# HTTP/1.1 102 Processing103 Early Hints
What it means
While the origin is still composing the real response, 103 ships probable headers early — above all Link: rel=preload — so the browser can begin fetching stylesheets, scripts and fonts it will almost certainly need.
The final response follows and its headers are authoritative; hints that turn out wrong are simply discarded.
When to use it
- Buy back latency when the origin needs real time to render but already knows the page's critical assets.
- In front of slow application servers, emitted by the CDN or reverse proxy on their behalf.
When not to use it
- When the final response is fast anyway — the hint saves nothing.
- For headers that must be authoritative (cookies, redirects); hints are advisory only.
Cacheability
Not applicable: interim 1xx responses are never stored by caches — RFC 9111 governs final responses only.
Idempotency context
Interim responses carry no idempotency weight of their own; what matters is the method of the request still in flight.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.set("Link", '</styles.css>; rel=preload; as=style');
res.status(103).end(); // 103 Early Hints
});from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/api/resource")
def handler(): # 103 Early Hints
return Response(status_code=103, headers={"Link": "</styles.css>; rel=preload; as=style"})location /api/resource {
add_header Link '</styles.css>; rel=preload; as=style' always;
return 103; # Early Hints
}// Interim responses never reach fetch(): the browser consumes
// 103 Early Hints and resolves with the final response that follows.
// Its headers (Link) are handled at the protocol layer.
const res = await fetch("/api/resource");
console.log(res.status); // the final status, never 103curl -i https://example.com/api/resource
# HTTP/1.1 103 Early Hints
# Link: </styles.css>; rel=preload; as=style2xx — Successful
| Code | Name | Summary | Copy |
|---|---|---|---|
| 200 | OK | The request succeeded; what the body holds depends on the method. | |
| 201 | Created | The request created a new resource; Location says where it lives. | |
| 202 | Accepted | The request was accepted for processing, but the processing has not completed — and might still fail. | |
| 203 | Non-Authoritative Information | The request succeeded, but a transforming proxy altered what the origin sent. | |
| 204 | No Content | The request succeeded and there is deliberately nothing in the body. | |
| 205 | Reset Content | Success — and the client should reset the view (typically a form) that produced the request. | |
| 206 | Partial Content | The server is delivering only the byte range(s) the Range header asked for. | |
| 207 | Multi-Status | WebDAV batch answer: the XML body carries a separate status for each affected resource. | |
| 208 | Already Reported | WebDAV: this resource appeared earlier in the same multistatus body and is not repeated. | |
| 226 | IM Used | The body is a delta — the result of instance manipulations applied to a prior version the client holds. |
The same 2xx codes in full — each row above links to its entry.
200 OK
What it means
The workhorse of HTTP: the request was understood and fulfilled. For GET the body is the requested resource, for POST it is the result of the action, for HEAD there is no body at all.
Precisely because it is the default success, a 200 wrapped around an error message in the body ('200 with {"error": …}') is an anti-pattern — it defeats caches, monitoring and every client that trusts the status line.
When to use it
- Any request that completed and has a result to return in the body.
- GET responses serving the current representation of a resource.
When not to use it
- When a more precise 2xx exists: 201 for creation, 202 for accepted-but-pending, 204 for no body.
- Never for failures dressed up in a successful envelope — pick the honest 4xx or 5xx.
Cacheability
Cacheable by default: this is one of the status codes caches may store and reuse even without explicit freshness information (RFC 9111 heuristic caching).
Idempotency context
Idempotency belongs to the request method, not the status code: retrying a GET, PUT or DELETE is safe by definition, while resending a POST may repeat its side effects.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(200).send("OK");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=200,
content={"detail": "OK"},
)location /api/resource {
return 200; # OK
}const res = await fetch("/api/resource");
if (res.status === 200) {
// 200 OK
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 200 OK201 Created
What it means
One or more resources now exist that did not before — the typical answer to a POST that added something, or a PUT that wrote to a fresh URI. The Location header (or, absent one, the request URI) identifies the primary new resource.
The body conventionally describes the created resource, often including its representation and an ETag for later conditional requests.
When to use it
- POST endpoints that make a new row, document or job — return 201 with Location pointing at it.
- PUT requests that created (rather than replaced) the target resource.
When not to use it
- Actions that merely update existing state — that is 200 or 204.
- Asynchronous creation that has not happened yet; that is 202.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Creation via POST is not idempotent — resending can create a duplicate; creation via PUT to a client-chosen URI is, which is why idempotency keys exist for POST.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.set("Location", '/new/location');
res.status(201).send("Created");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=201,
content={"detail": "Created"},
headers={"Location": "/new/location"},
)location /api/resource {
add_header Location '/new/location' always;
return 201; # Created
}const res = await fetch("/api/resource");
if (res.status === 201) {
// 201 Created
const location = res.headers.get("Location");
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 201 Created
# Location: /new/location202 Accepted
What it means
HTTP's fire-and-acknowledge answer: the server took the request into its queue and makes no promise about the outcome. It exists so a server can accept work without holding the connection open until the work is done.
A good 202 body tells the client how to follow up — a status URL to poll, a webhook that will fire, or an estimate of when to check back.
When to use it
- Batch jobs, exports, video transcodes — anything queued for later execution.
- Webhook receivers that validate quickly and process afterwards.
When not to use it
- Work you complete synchronously — answer 200, 201 or 204 with the actual outcome.
- As a way to dodge error reporting; if validation already failed, say so with a 4xx now.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
A 202 acknowledges receipt, not completion — a retry may enqueue the same work twice unless the endpoint deduplicates by idempotency key.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(202).send("Accepted");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=202,
content={"detail": "Accepted"},
)location /api/resource {
return 202; # Accepted
}const res = await fetch("/api/resource");
if (res.status === 202) {
// 202 Accepted
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 202 Accepted203 Non-Authoritative Information
What it means
The response is a 200 in substance, except an intermediary — a transforming proxy, an image-compressing gateway — changed the representation on the way through, so the client should not treat it as the origin's exact bytes.
In practice it is rare; the modern Cache-Control: no-transform directive exists so origins can forbid the behavior entirely.
When to use it
- Proxies that deliberately transform payloads and want to disclose it honestly.
When not to use it
- Origin servers — by definition the origin's own answer is authoritative.
- When the transformation changes meaning rather than encoding; then the proxy should not transform at all.
Cacheability
Cacheable by default: this is one of the status codes caches may store and reuse even without explicit freshness information (RFC 9111 heuristic caching).
Idempotency context
Idempotency belongs to the request method, not the status code: retrying a GET, PUT or DELETE is safe by definition, while resending a POST may repeat its side effects.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(203).send("Non-Authoritative Information");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=203,
content={"detail": "Non-Authoritative Information"},
)location /api/resource {
return 203; # Non-Authoritative Information
}const res = await fetch("/api/resource");
if (res.status === 203) {
// 203 Non-Authoritative Information
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 203 Non-Authoritative Information204 No Content
What it means
Success with an intentionally empty body: the action worked and the server has nothing to add. The client should keep its current view — a browser stays on the page rather than navigating.
It is the natural answer to a DELETE, a PUT that updated in place, or a settings toggle where echoing the resource back would be wasted bytes.
When to use it
- DELETE requests that removed the resource and have nothing further to report.
- PUT/PATCH updates where the client already holds the current representation.
When not to use it
- When the client needs the resulting state (new ETag aside) — return 200 with a body.
- Never with a body attached; intermediaries are entitled to drop or reject it.
Cacheability
Cacheable by default: this is one of the status codes caches may store and reuse even without explicit freshness information (RFC 9111 heuristic caching).
Idempotency context
Commonly follows idempotent methods (PUT, DELETE), so a retry usually lands harmlessly — a second DELETE of the same resource typically earns a 404.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(204).end(); // 204 No Content
});from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/api/resource")
def handler(): # 204 No Content
return Response(status_code=204)location /api/resource {
return 204; # No Content
}const res = await fetch("/api/resource");
if (res.status === 204) {
// 204 No Content
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 204 No Content205 Reset Content
What it means
Like 204 the body is empty, but 205 adds an instruction: clear the document view that sent the request. The intended use is form submissions where the user will enter another record immediately.
Browser support was never strong, so it survives mostly in APIs whose own clients honor the convention.
When to use it
- Data-entry endpoints where the natural next step is a blank form for another submission.
When not to use it
- General web forms relying on browsers to do the resetting — handle it in your own client code instead.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Idempotency belongs to the request method, not the status code: retrying a GET, PUT or DELETE is safe by definition, while resending a POST may repeat its side effects.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(205).end(); // 205 Reset Content
});from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/api/resource")
def handler(): # 205 Reset Content
return Response(status_code=205)location /api/resource {
return 205; # Reset Content
}const res = await fetch("/api/resource");
if (res.status === 205) {
// 205 Reset Content
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 205 Reset Content206 Partial Content
What it means
The answer to a successful range request: Content-Range declares which slice of the full representation this body carries. It is what makes resumable downloads and video seeking work.
Multiple ranges arrive as a multipart/byteranges body; a range the server cannot satisfy earns 416 instead.
When to use it
- Serving Range requests for large files — installers, media, archives.
- Video/audio streaming endpoints where players seek by byte offset.
When not to use it
- When you ignore the Range header — then send the full 200 and clients will cope.
- For application-level pagination; Range is about bytes of one representation, not collections.
Cacheability
Cacheable by default: this is one of the status codes caches may store and reuse even without explicit freshness information (RFC 9111 heuristic caching).
Idempotency context
Range requests are GETs, so retrying is safe; a well-behaved client revalidates with If-Range so a changed file restarts cleanly instead of splicing mismatched halves.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.set("Content-Range", 'bytes 0-1023/4096');
res.status(206).send("Partial Content");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=206,
content={"detail": "Partial Content"},
headers={"Content-Range": "bytes 0-1023/4096"},
)location /api/resource {
add_header Content-Range 'bytes 0-1023/4096' always;
return 206; # Partial Content
}const res = await fetch("/api/resource");
if (res.status === 206) {
// 206 Partial Content
const contentRange = res.headers.get("Content-Range");
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 206 Partial Content
# Content-Range: bytes 0-1023/4096207 Multi-Status
What it means
One request touched several resources and they did not all fare the same, so the response wraps a per-resource status inside a multistatus XML body. A PROPFIND across a folder tree is the canonical producer.
The 207 itself says nothing about success or failure — the truth is inside the body, which clients must parse.
When to use it
- WebDAV operations (PROPFIND, COPY, MOVE, DELETE on collections) with mixed per-resource outcomes.
When not to use it
- JSON APIs doing batch operations — model per-item results in your own response shape under a 200 instead.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
The batch inherits the method's semantics per resource; retrying a partially failed batch re-runs the parts that already succeeded, so check the body before resending.
Defined in
Scope: WebDAV. Outside DAV-speaking clients, expect nothing to understand the multistatus body.
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(207).send("Multi-Status");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=207,
content={"detail": "Multi-Status"},
)location /api/resource {
return 207; # Multi-Status
}const res = await fetch("/api/resource");
if (res.status === 207) {
// 207 Multi-Status
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 207 Multi-Status208 Already Reported
What it means
Inside a 207 body, when bindings make the same resource reachable twice, 208 marks the later occurrences instead of duplicating their contents — preventing exponentially bloated answers over looped bindings.
When to use it
- WebDAV servers implementing RFC 5842 bindings, inside multistatus bodies only.
When not to use it
- As a top-level status for ordinary requests — it only means something inside a 207.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Idempotency belongs to the request method, not the status code: retrying a GET, PUT or DELETE is safe by definition, while resending a POST may repeat its side effects.
Defined in
Scope: WebDAV bindings extension (RFC 5842) — rare even within WebDAV.
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(208).send("Already Reported");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=208,
content={"detail": "Already Reported"},
)location /api/resource {
return 208; # Already Reported
}const res = await fetch("/api/resource");
if (res.status === 208) {
// 208 Already Reported
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 208 Already Reported226 IM Used
What it means
Part of RFC 3229 delta encoding: the client said it already has an older instance (A-IM/If-None-Match), and the server answered with just the difference. 226 signals that the body is not the full representation but the output of the negotiated instance manipulation.
Deployment never took off; you will meet it in niche sync protocols more often than on the open web.
When to use it
- Sync endpoints where shipping diffs against a client-held baseline saves real bandwidth and both sides implement RFC 3229.
When not to use it
- Anywhere clients have not negotiated delta encoding — they would treat the diff as the whole document.
Cacheability
Cacheable only with explicit freshness information, and caches must understand instance manipulations to reuse it correctly (RFC 3229 §10.4.1).
Idempotency context
Idempotency belongs to the request method, not the status code: retrying a GET, PUT or DELETE is safe by definition, while resending a POST may repeat its side effects.
Defined in
Scope: Delta-encoding extension (RFC 3229); registered but rarely deployed.
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(226).send("IM Used");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=226,
content={"detail": "IM Used"},
)location /api/resource {
return 226; # IM Used
}const res = await fetch("/api/resource");
if (res.status === 226) {
// 226 IM Used
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 226 IM Used3xx — Redirection
| Code | Name | Summary | Copy |
|---|---|---|---|
| 300 | Multiple Choices | More than one representation fits; the body lists the alternatives for the client to pick. | |
| 301 | Moved Permanently | The resource lives at the Location URI now and forever; clients and search engines should update their references. | |
| 302 | Found | The resource is temporarily somewhere else; keep using the original URI next time. | |
| 303 | See Other | Deliberately fetch a different URI with GET — the Post/Redirect/Get pattern's status code. | |
| 304 | Not Modified | The cached copy is still good — the conditional request matched, so no body is sent. | |
| 305 | Use Proxy | Deprecated: the request was supposed to be repeated through the proxy in Location. | |
| 306 | Switch Proxy | Reserved and unused — a draft-era proxy code that never made it into the standard. | |
| 307 | Temporary Redirect | Temporary redirect that forbids method rewriting — a POST stays a POST at the new URI. | |
| 308 | Permanent Redirect | Permanent redirect that forbids method rewriting — 301's semantics with the method guaranteed. |
The same 3xx codes in full — each row above links to its entry.
300 Multiple Choices
What it means
The target resource has several representations — different formats, languages or versions — and the server declines to choose. The body enumerates them; a Location header may nominate a preferred one.
Real deployments almost always negotiate proactively (Accept, Accept-Language) and pick server-side, which is why 300 is rarely seen in the wild.
When to use it
- Genuine agent-driven negotiation, where the human or client must make the choice.
When not to use it
- When the server can decide from request headers — answer 200 with Vary instead.
Cacheability
Cacheable by default: this is one of the status codes caches may store and reuse even without explicit freshness information (RFC 9111 heuristic caching).
Idempotency context
Idempotency belongs to the request method, not the status code: retrying a GET, PUT or DELETE is safe by definition, while resending a POST may repeat its side effects.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(300).send("Multiple Choices");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=300,
content={"detail": "Multiple Choices"},
)location /api/resource {
return 300; # Multiple Choices
}const res = await fetch("/api/resource");
if (res.status === 300) {
// 300 Multiple Choices
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 300 Multiple Choices301 Moved Permanently
What it means
The permanent redirect: links, bookmarks and search indexes should replace the old URI with the one in Location. It is the workhorse of domain migrations and URL restructuring, and it transfers SEO signals to the new address.
One historical wart: clients are allowed to change the method — a POST redirected with 301 usually gets replayed as a GET. When the method must survive the hop, use 308.
When to use it
- Permanent URL moves — renamed pages, http→https at the domain level, retired URL schemes.
- Canonicalization redirects (trailing slash, www vs apex) meant to be remembered.
When not to use it
- Anything temporary — caches will keep sending users to Location long after you change your mind; that is 302/307 territory.
- Redirecting POST/PUT/DELETE where the method must be preserved — use 308.
Cacheability
Cacheable by default — permanently, in effect: browsers remember 301s aggressively even without explicit freshness, which is exactly why a mistaken one is painful to undo (RFC 9111).
Idempotency context
The redirect itself is harmless to retry; the caveat is method rewriting — clients may replay the redirected request as GET, so a non-idempotent original can silently change shape.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.set("Location", '/new/location');
res.status(301).send("Moved Permanently");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=301,
content={"detail": "Moved Permanently"},
headers={"Location": "/new/location"},
)location /api/resource {
add_header Location '/new/location' always;
return 301; # Moved Permanently
}const res = await fetch("/api/resource");
if (res.status === 301) {
// 301 Moved Permanently
const location = res.headers.get("Location");
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 301 Moved Permanently
# Location: /new/location302 Found
What it means
The temporary redirect everyone actually ships: go to Location for now, but do not update bookmarks or indexes. Originally named 'Moved Temporarily' in HTTP/1.0; HTTP/1.1 renamed it 'Found'.
Like 301 it carries the historical method-rewrite tolerance — most clients turn a redirected POST into a GET. 307 exists precisely to forbid that.
When to use it
- Short-lived detours: A/B routing, geo-based hops, 'log in first' bounces where the original URL stays canonical.
When not to use it
- Permanent moves — search engines keep the old URL indexed; use 301/308.
- When the method must be preserved across the hop; that is 307.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Clients may replay the redirected request as GET (the historical tolerance), so a POST that hits a 302 can lose its method — use 307/308 when that matters.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.set("Location", '/new/location');
res.status(302).send("Found");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=302,
content={"detail": "Found"},
headers={"Location": "/new/location"},
)location /api/resource {
add_header Location '/new/location' always;
return 302; # Found
}const res = await fetch("/api/resource");
if (res.status === 302) {
// 302 Found
const location = res.headers.get("Location");
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 302 Found
# Location: /new/location303 See Other
What it means
Unlike 301/302's accidental method rewriting, 303 orders it: whatever method the request used, retrieve the resource named in Location with GET. It decouples an action from the page that shows its result.
It is the correct engine of Post/Redirect/Get: the form POST returns 303, the browser GETs the result page, and a refresh re-runs only the harmless GET.
When to use it
- After a successful form POST, redirecting to a result or confirmation page.
- Pointing a client at a status resource after starting an async job (often paired with 202 patterns).
When not to use it
- When the client should repeat the SAME request elsewhere — that is 307/308.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Designed to make retries safe at the UX level: after the 303 hop, refreshing repeats the GET of the result page, not the original POST.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.set("Location", '/new/location');
res.status(303).send("See Other");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=303,
content={"detail": "See Other"},
headers={"Location": "/new/location"},
)location /api/resource {
add_header Location '/new/location' always;
return 303; # See Other
}const res = await fetch("/api/resource");
if (res.status === 303) {
// 303 See Other
const location = res.headers.get("Location");
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 303 See Other
# Location: /new/location304 Not Modified
What it means
The answer to a conditional GET (If-None-Match against an ETag, or If-Modified-Since against a date) when the stored response is still current: no body, just headers, and the client keeps using its cached copy.
It is the cheapest useful response in HTTP — validation for the price of a round trip — and the backbone of browser revalidation.
When to use it
- Any endpoint that emits ETag or Last-Modified and receives the matching conditional headers back.
- High-traffic assets where saving body bytes on revalidation matters.
When not to use it
- Requests that were not conditional — an unconditional GET must get the full 200.
- Never with a body; by definition there is nothing to send.
Cacheability
A 304 is not stored itself: it instructs the cache to refresh the stored 200 response it validated and keep serving that (RFC 9111 §4.3.4).
Idempotency context
Conditional GETs are safe by construction — retrying costs another round trip and nothing else.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.set("ETag", '"33a64df5"');
res.status(304).end(); // 304 Not Modified
});from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/api/resource")
def handler(): # 304 Not Modified
return Response(status_code=304, headers={"ETag": '"33a64df5"'})location /api/resource {
add_header ETag '"33a64df5"' always;
return 304; # Not Modified
}const res = await fetch("/api/resource");
if (res.status === 304) {
// 304 Not Modified
const etag = res.headers.get("ETag");
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 304 Not Modified
# ETag: "33a64df5"305 Use Proxy
Deprecated
What it means
HTTP/1.1 originally let an origin redirect a client through a specific proxy. Letting a remote server reconfigure the client's proxy turned out to be a security hole, and the code was deprecated.
Modern clients ignore it; proxy configuration belongs to the client side (PAC files, system settings), never to a response.
When to use it
- Never in new systems — it exists in this reference so you can recognize it in old traffic.
When not to use it
- Anywhere. If you control routing, do it in infrastructure configuration, not status codes.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Idempotency belongs to the request method, not the status code: retrying a GET, PUT or DELETE is safe by definition, while resending a POST may repeat its side effects.
Defined in
Scope: Deprecated by RFC 9110 §15.4.6 'due to security concerns regarding in-band configuration of a proxy'. Recognize, never emit.
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(305).send("Use Proxy");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=305,
content={"detail": "Use Proxy"},
)location /api/resource {
return 305; # Use Proxy
}const res = await fetch("/api/resource");
if (res.status === 305) {
// 305 Use Proxy
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 305 Use Proxy306 Switch Proxy
Deprecated
What it means
A previous draft of HTTP/1.1 defined 306 ('Switch Proxy'); the idea was abandoned alongside 305's deprecation, and the code point is now reserved. RFC 9110 lists it only as '(Unused)'.
No conforming implementation sends it; seeing one in the wild means something exotic or broken upstream.
When to use it
- Never — the code point is reserved precisely so nothing emits it.
When not to use it
- Any response. If you need proxy redirection semantics, solve it in client configuration.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Idempotency belongs to the request method, not the status code: retrying a GET, PUT or DELETE is safe by definition, while resending a POST may repeat its side effects.
Defined in
Scope: Reserved. Named 'Switch Proxy' only in an expired draft; RFC 9110 §15.4.7 lists the code point as '(Unused)'.
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(306).send("Switch Proxy");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=306,
content={"detail": "Switch Proxy"},
)location /api/resource {
return 306; # Switch Proxy
}const res = await fetch("/api/resource");
if (res.status === 306) {
// 306 Switch Proxy
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 306 Switch Proxy307 Temporary Redirect
What it means
Semantically 302 with the loophole closed: the client must repeat the request at the Location URI with the same method and body. It exists because deployed clients had turned 302-redirected POSTs into GETs for decades.
It is what an API should send when a write endpoint is temporarily somewhere else and the write must still happen.
When to use it
- Temporarily rerouting requests whose method and body must survive the hop — API writes, upload endpoints.
- HSTS-style internal upgrade redirects where the request should be replayed exactly.
When not to use it
- Permanent moves — that is 308.
- After a form POST when you WANT the client to GET a result page — that is 303.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
The method is preserved, so the retry semantics of the original request carry over unchanged — a redirected POST is still a POST with all its side effects.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.set("Location", '/new/location');
res.status(307).send("Temporary Redirect");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=307,
content={"detail": "Temporary Redirect"},
headers={"Location": "/new/location"},
)location /api/resource {
add_header Location '/new/location' always;
return 307; # Temporary Redirect
}const res = await fetch("/api/resource");
if (res.status === 307) {
// 307 Temporary Redirect
const location = res.headers.get("Location");
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 307 Temporary Redirect
# Location: /new/location308 Permanent Redirect
What it means
The permanent counterpart of 307: update all references to the Location URI, and repeat the request there with the same method and body. Defined in RFC 7538 and folded into RFC 9110 as the fourth core redirect.
Choose along two axes — permanent vs temporary, method-preserving vs not: 301/302 are the lenient legacy pair, 308/307 the strict modern one.
When to use it
- Permanent moves of API endpoints that accept writes — the POST/PUT must not degrade to GET.
- Domain or path migrations where you also serve non-GET traffic.
When not to use it
- Temporary detours — like 301, a cached 308 is hard to take back.
- Very old client populations that predate RFC 7538, if any still matter to you.
Cacheability
Cacheable by default: this is one of the status codes caches may store and reuse even without explicit freshness information (RFC 9111 heuristic caching).
Idempotency context
Method-preserving like 307: the replayed request keeps its original idempotency, so redirected non-idempotent writes still deserve idempotency keys.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.set("Location", '/new/location');
res.status(308).send("Permanent Redirect");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=308,
content={"detail": "Permanent Redirect"},
headers={"Location": "/new/location"},
)location /api/resource {
add_header Location '/new/location' always;
return 308; # Permanent Redirect
}const res = await fetch("/api/resource");
if (res.status === 308) {
// 308 Permanent Redirect
const location = res.headers.get("Location");
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 308 Permanent Redirect
# Location: /new/location4xx — Client errors
| Code | Name | Summary | Copy |
|---|---|---|---|
| 400 | Bad Request | The server cannot or will not process this — the request itself is malformed or invalid. | |
| 401 | Unauthorized | Authentication is required and missing or invalid — despite the name, it means unauthenticated. | |
| 402 | Payment Required | Reserved for future use — in practice: pay, top up or upgrade before this request can succeed. | |
| 403 | Forbidden | The server understood the request and refuses to authorize it — identity is known, permission is denied. | |
| 404 | Not Found | The server found no current representation for the target URI — or is not saying whether one exists. | |
| 405 | Method Not Allowed | The URI exists, but not for this method — Allow lists the ones that work. | |
| 406 | Not Acceptable | Nothing the server can produce satisfies the request's Accept headers. | |
| 407 | Proxy Authentication Required | 401's twin for proxies: the intermediary wants credentials before it will forward the request. | |
| 408 | Request Timeout | The server gave up waiting for the rest of the request and is closing the connection. | |
| 409 | Conflict | The request collides with the resource's current state — resolve the conflict, then resend. | |
| 410 | Gone | The resource existed, was deliberately removed, and is not coming back — stop asking. | |
| 411 | Length Required | The server refuses the request without a Content-Length header. | |
| 412 | Precondition Failed | An If-Match / If-Unmodified-Since precondition evaluated false — the resource changed under you. | |
| 413 | Content Too Large | The request body exceeds what the server will process — renamed from 'Payload Too Large' in RFC 9110. | |
| 414 | URI Too Long | The request target is longer than the server is willing to interpret. | |
| 415 | Unsupported Media Type | The request body's format is not one this endpoint accepts — Content-Type (or the content itself) is wrong. | |
| 416 | Range Not Satisfiable | None of the requested byte ranges overlap the resource — e.g. resuming past the end of the file. | |
| 417 | Expectation Failed | The Expect header asks for something this server cannot honor. | |
| 418 | I'm a Teapot | An April Fools' classic from HTCPCP: a teapot, asked to brew coffee, politely refuses. | |
| 420 | Enhance Your Calm | Twitter API v1's folk code for clients it wanted to slow down; retired, still met in the wild. | |
| 421 | Misdirected Request | This request reached a server that is not configured to answer for its target authority. | |
| 422 | Unprocessable Content | The body parses fine but its meaning fails validation — well-formed, semantically wrong. | |
| 423 | Locked | WebDAV: the resource is under a lock the request does not hold. | |
| 424 | Failed Dependency | WebDAV: this action failed because an earlier action it depended on failed. | |
| 425 | Too Early | The server won't process a request that arrived in TLS early data — replay risk; send it again after the handshake. | |
| 426 | Upgrade Required | The server refuses to talk over this protocol version — switch to the one named in Upgrade first. | |
| 428 | Precondition Required | The server insists this request be conditional — send it again with If-Match. | |
| 429 | Too Many Requests | The client exceeded a rate limit; Retry-After says how long to back off. | |
| 431 | Request Header Fields Too Large | The header section — one field or all of them together — exceeds what the server will accept. | |
| 451 | Unavailable For Legal Reasons | Access is denied because of a legal demand — censorship, court order or policy, disclosed honestly. | |
| 499 | Client Closed Request | nginx's log-only code: the client hung up before the server finished responding. |
The same 4xx codes in full — each row above links to its entry.
400 Bad Request
What it means
The generic client error: bad syntax, an unparsable body, invalid parameters, framing the server refuses to honor. The fault is in the request, and resending it unchanged will fail identically.
Because it is the catch-all, a useful 400 carries a body explaining WHAT was wrong; a bare 400 turns every client-side bug into archaeology.
When to use it
- Unparsable JSON, invalid query parameters, violated request schemas — with a body naming the offending field.
- As the honest fallback when no more specific 4xx fits.
When not to use it
- When a sharper code exists: 401/403 for auth, 404 for absence, 422 for well-formed-but-semantically-wrong, 415 for the wrong media type.
- Server-side faults — a handler crash on valid input is a 500, not a 400.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Retrying an unchanged request is pointless by definition — the request must be fixed before it can succeed.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(400).send("Bad Request");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=400,
content={"detail": "Bad Request"},
)location /api/resource {
return 400; # Bad Request
}const res = await fetch("/api/resource");
if (res.status === 400) {
// 400 Bad Request
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 400 Bad Request401 Unauthorized
What it means
The request lacks valid credentials for the target resource. The name is HTTP's most famous misnomer: 401 is about WHO YOU ARE (authentication), while 403 is about WHAT YOU MAY DO (authorization).
A conforming 401 carries a WWW-Authenticate header naming at least one acceptable scheme — Bearer for token APIs, Basic for the classic browser prompt.
When to use it
- Missing, expired or malformed credentials — tell the client to (re)authenticate and retry.
- Token-based APIs when the access token fails validation; the client's cue to refresh it.
When not to use it
- Valid credentials without sufficient rights — that is 403.
- Hiding a resource's existence from unauthenticated eyes; many APIs deliberately answer 404 for that.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Safe to retry once credentials are fixed — the original request was never executed.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.set("WWW-Authenticate", 'Bearer realm="api"');
res.status(401).send("Unauthorized");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=401,
content={"detail": "Unauthorized"},
headers={"WWW-Authenticate": 'Bearer realm="api"'},
)location /api/resource {
add_header WWW-Authenticate 'Bearer realm="api"' always;
return 401; # Unauthorized
}const res = await fetch("/api/resource");
if (res.status === 401) {
// 401 Unauthorized
const wwwAuthenticate = res.headers.get("WWW-Authenticate");
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 401 Unauthorized
# WWW-Authenticate: Bearer realm="api"402 Payment Required
What it means
RFC 9110 still labels 402 'reserved for future use' — the imagined micropayment web never arrived. The code point is standard; a standardized meaning is not.
APIs have colonized it anyway: exhausted quotas, expired subscriptions and billing failures commonly answer 402, and clients treat it as 'money, then retry'.
When to use it
- API billing boundaries: plan limits reached, trial expired, payment method declined — with a body linking to the fix.
When not to use it
- Interoperable surfaces where clients need standardized semantics — 403 with an explanatory body travels better.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Nothing was executed; retry after the billing state changes — retrying before that yields the same answer.
Defined in
Scope: Officially 'reserved for future use' (RFC 9110 §15.5.3); its billing usage is convention, not standard.
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(402).send("Payment Required");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=402,
content={"detail": "Payment Required"},
)location /api/resource {
return 402; # Payment Required
}const res = await fetch("/api/resource");
if (res.status === 402) {
// 402 Payment Required
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 402 Payment Required403 Forbidden
What it means
Authentication succeeded or is irrelevant; authorization failed. Re-authenticating as the same principal will not help, which is exactly the line between 403 and 401.
Servers that prefer not to reveal WHY (or that the resource exists at all) are explicitly allowed to answer 404 instead — private repositories on code-hosting platforms famously do.
When to use it
- A known user lacking rights: role checks, ownership checks, IP allowlists, WAF policy denials.
- Writes rejected by policy even with valid credentials — read-only mode, immutable resources.
When not to use it
- Missing or bad credentials — that is 401, with WWW-Authenticate.
- When even confirming existence leaks information; answer 404 deliberately.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Retrying unchanged is futile — the denial is a policy decision, not a transient state; only a permission change unblocks it.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(403).send("Forbidden");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=403,
content={"detail": "Forbidden"},
)location /api/resource {
return 403; # Forbidden
}const res = await fetch("/api/resource");
if (res.status === 403) {
// 403 Forbidden
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 403 Forbidden404 Not Found
What it means
The most famous status code in HTTP: nothing lives at this URI right now. It makes no claim about the past or future — the resource may never have existed, may have been deleted, or may be deliberately hidden.
That ambiguity is a feature: 404 is the standard way to deny existence without confirming anything, which is why access-controlled systems return it where a 403 would leak.
When to use it
- Requests for resources that genuinely are not there — broken links, deleted records, typo'd IDs.
- Deliberately masking a resource's existence from callers without rights to know it.
When not to use it
- Known-permanent removals where you can say so — 410 communicates 'gone for good' more usefully.
- As a lazy stand-in for 400 when the path was valid but parameters were not.
Cacheability
Cacheable by default: this is one of the status codes caches may store and reuse even without explicit freshness information (RFC 9111 heuristic caching).
Idempotency context
Safe to retry, pointless to expect a different answer until the resource comes into existence.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(404).send("Not Found");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=404,
content={"detail": "Not Found"},
)location /api/resource {
return 404; # Not Found
}const res = await fetch("/api/resource");
if (res.status === 404) {
// 404 Not Found
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 404 Not Found405 Method Not Allowed
What it means
The target resource is real, but the request method is not in its repertoire — a POST to a read-only document, a DELETE where deletion is not offered. The response MUST carry an Allow header listing the supported methods.
It is a more precise answer than 404 (the resource exists) and than 501 (the server understands the method fine — this resource just does not offer it).
When to use it
- Any routed endpoint hit with an unsupported method — frameworks usually emit it (with Allow) automatically.
When not to use it
- Methods the server does not implement at all — that is 501.
- Authorization-based method denial (the method exists, this caller may not use it) — that is 403.
Cacheability
Cacheable by default: this is one of the status codes caches may store and reuse even without explicit freshness information (RFC 9111 heuristic caching).
Idempotency context
Nothing was executed; the fix is switching to a method the Allow header names, not retrying the same one.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.set("Allow", 'GET, HEAD');
res.status(405).send("Method Not Allowed");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=405,
content={"detail": "Method Not Allowed"},
headers={"Allow": "GET, HEAD"},
)location /api/resource {
add_header Allow 'GET, HEAD' always;
return 405; # Method Not Allowed
}const res = await fetch("/api/resource");
if (res.status === 405) {
// 405 Method Not Allowed
const allow = res.headers.get("Allow");
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 405 Method Not Allowed
# Allow: GET, HEAD406 Not Acceptable
What it means
Proactive content negotiation failed: the client's Accept, Accept-Language or Accept-Encoding constraints exclude every representation the server has. A helpful body lists what IS available.
In practice most servers ignore mismatched Accept headers and send their default representation anyway — RFC 9110 explicitly permits that — so a real 406 usually comes from strict APIs.
When to use it
- Strict APIs that refuse to serve a format the client did not ask for (e.g. client demands XML, server only has JSON).
When not to use it
- When serving your default representation anyway is kinder — the spec allows it and most of the web does it.
- Unsupported REQUEST body formats — that is 415.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Idempotency belongs to the request method, not the status code: retrying a GET, PUT or DELETE is safe by definition, while resending a POST may repeat its side effects.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(406).send("Not Acceptable");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=406,
content={"detail": "Not Acceptable"},
)location /api/resource {
return 406; # Not Acceptable
}const res = await fetch("/api/resource");
if (res.status === 406) {
// 406 Not Acceptable
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 406 Not Acceptable407 Proxy Authentication Required
What it means
A proxy on the path — corporate egress, a paid gateway — demands authentication. The Proxy-Authenticate header names the scheme, and the client answers with Proxy-Authorization, exactly mirroring the 401/WWW-Authenticate pair one hop earlier.
When to use it
- Authenticating proxies challenging clients that have not presented Proxy-Authorization.
When not to use it
- Origin servers — an origin demanding credentials sends 401.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
The request never crossed the proxy; retrying with Proxy-Authorization is safe and expected.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.set("Proxy-Authenticate", 'Basic realm="proxy"');
res.status(407).send("Proxy Authentication Required");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=407,
content={"detail": "Proxy Authentication Required"},
headers={"Proxy-Authenticate": 'Basic realm="proxy"'},
)location /api/resource {
add_header Proxy-Authenticate 'Basic realm="proxy"' always;
return 407; # Proxy Authentication Required
}const res = await fetch("/api/resource");
if (res.status === 407) {
// 407 Proxy Authentication Required
const proxyAuthenticate = res.headers.get("Proxy-Authenticate");
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 407 Proxy Authentication Required
# Proxy-Authenticate: Basic realm="proxy"408 Request Timeout
What it means
The client opened a request but did not finish sending it within the server's patience — a stalled upload, a connection held half-open. The server typically closes the connection along with the response.
Its mirror image is nginx's non-standard 499, where the CLIENT is the one who walked away first.
When to use it
- Reaping connections whose request never completed within your read timeout.
When not to use it
- Upstream timeouts while the server itself waits on a backend — that is 504.
- Slow processing of a fully received request; pick 202 (accept and continue) or let it finish.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Explicitly retry-friendly: the request was never fully received, so clients — browsers included — may repeat it automatically, even for non-idempotent methods.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(408).send("Request Timeout");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=408,
content={"detail": "Request Timeout"},
)location /api/resource {
return 408; # Request Timeout
}const res = await fetch("/api/resource");
if (res.status === 408) {
// 408 Request Timeout
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 408 Request Timeout409 Conflict
What it means
The request was well-formed but cannot be applied to the resource as it stands: a version edit based on stale state, a duplicate of a uniqueness-constrained value, a delete of something already changed underneath.
Unlike most 4xx codes, 409 is an invitation: the body should describe the conflict well enough for the client to reconcile and try again.
When to use it
- Optimistic-concurrency version collisions (often alongside ETag/If-Match machinery).
- Uniqueness violations — 'that name is taken' — where the client can choose differently and resubmit.
When not to use it
- Failed If-Match/If-Unmodified-Since preconditions specifically — HTTP reserves 412 for those.
- Validation failures unrelated to current state — that is 400 or 422.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Do not retry blind — the point of a 409 is that state moved; re-read the resource, reconcile, then resend the corrected request.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(409).send("Conflict");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=409,
content={"detail": "Conflict"},
)location /api/resource {
return 409; # Conflict
}const res = await fetch("/api/resource");
if (res.status === 409) {
// 409 Conflict
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 409 Conflict410 Gone
What it means
404 with a memory: the server confirms the resource USED to live here and the absence is intentional and likely permanent. It invites clients to delete their references.
Search engines treat it as a stronger removal signal than 404, which is exactly why retired content and sunset API versions deserve it.
When to use it
- Retired content and API versions you want crawlers and clients to forget.
- Expired one-time resources — used invitation links, closed listings, lapsed promotions.
When not to use it
- Absence that might be temporary or unexplained — plain 404 promises less and is safer.
- Hiding access-controlled resources; 410 CONFIRMS something existed, which may itself leak.
Cacheability
Cacheable by default: this is one of the status codes caches may store and reuse even without explicit freshness information (RFC 9111 heuristic caching).
Idempotency context
Safe to retry and permanently futile — the answer is designed never to change.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(410).send("Gone");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=410,
content={"detail": "Gone"},
)location /api/resource {
return 410; # Gone
}const res = await fetch("/api/resource");
if (res.status === 410) {
// 410 Gone
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 410 Gone411 Length Required
What it means
The server will not accept this request's body without knowing its size up front — usually a rejection of chunked transfer where the deployment cannot or will not buffer unbounded input.
Clients fix it by resending with a valid Content-Length; HTTP/2 and HTTP/3 frame data differently, so it is mostly an HTTP/1.1 phenomenon.
When to use it
- Endpoints or intermediaries that must know body size before accepting it (quota precheck, storage allocation).
When not to use it
- When you can stream and enforce limits as you read — cutting off at the cap with 413 is friendlier.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Nothing was processed; resending with Content-Length is safe for any method.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(411).send("Length Required");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=411,
content={"detail": "Length Required"},
)location /api/resource {
return 411; # Length Required
}const res = await fetch("/api/resource");
if (res.status === 411) {
// 411 Length Required
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 411 Length Required412 Precondition Failed
What it means
The conditional machinery working as designed: the client said 'only apply this if my version is still current' (If-Match with an ETag, If-Unmodified-Since with a date) and it was not, so the server refused to execute.
This is the response that makes optimistic concurrency real over HTTP — the lost-update problem answered at the protocol level.
When to use it
- Conditional writes whose precondition failed — the client must re-fetch, re-apply changes and resend with the new validator.
When not to use it
- State conflicts detected WITHOUT protocol preconditions — that is 409.
- Failed If-None-Match on a GET — that path answers 304, not 412.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
The refused request had no effect; the correct retry re-reads the resource and carries the fresh ETag — never just resend the stale one.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(412).send("Precondition Failed");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=412,
content={"detail": "Precondition Failed"},
)location /api/resource {
return 412; # Precondition Failed
}const res = await fetch("/api/resource");
if (res.status === 412) {
// 412 Precondition Failed
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 412 Precondition Failed413 Content Too Large
What it means
The body outweighs the server's limits and the request is refused; the server may close the connection to stop the upload. RFC 9110 renamed it from 'Payload Too Large' (and HTTP/1.1's original 'Request Entity Too Large') — all three phrasings survive in error pages and older stacks.
If the condition is temporary — a full queue, a momentary quota — the server can include Retry-After.
When to use it
- Upload endpoints enforcing size caps — with the limit stated in the body so clients can act on it.
- Reverse proxies rejecting oversized bodies before they reach application servers.
When not to use it
- Too-long URLs — that is 414.
- Oversized header sections — that is 431.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Retrying the identical body will fail identically; shrink the content or negotiate a chunked/resumable path first.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(413).send("Content Too Large");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=413,
content={"detail": "Content Too Large"},
)location /api/resource {
return 413; # Content Too Large
}const res = await fetch("/api/resource");
if (res.status === 413) {
// 413 Content Too Large
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 413 Content Too Large414 URI Too Long
What it means
Usually the symptom of state crammed into a query string — filters, tokens, whole documents — until a proxy or server's URL buffer gives out. Common limits sit around 4–8 KB per hop, and the lowest one on the path wins.
The durable fix is moving bulk data into a request body (POST) or a stored server-side reference, not raising limits hop by hop.
When to use it
- Refusing URLs beyond your parser's or infrastructure's documented limit.
When not to use it
- Oversized bodies (413) or header sections (431) — each direction of excess has its own code.
Cacheability
Cacheable by default: this is one of the status codes caches may store and reuse even without explicit freshness information (RFC 9111 heuristic caching).
Idempotency context
Nothing was executed; the request must be restructured — typically into a POST body — before retrying.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(414).send("URI Too Long");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=414,
content={"detail": "URI Too Long"},
)location /api/resource {
return 414; # URI Too Long
}const res = await fetch("/api/resource");
if (res.status === 414) {
// 414 URI Too Long
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 414 URI Too Long415 Unsupported Media Type
What it means
The server understood the request but refuses the body's media type: XML sent to a JSON-only API, an image format the upload endpoint does not take, or a Content-Encoding it cannot undo.
The response can name the supported types via Accept (for the method) so the client knows what to send instead.
When to use it
- API endpoints enforcing a request Content-Type contract (application/json and nothing else, say).
- Upload endpoints restricted to specific file formats.
When not to use it
- A parsable, supported format whose CONTENT is invalid — that is 400 (syntax) or 422 (semantics).
- Response-side negotiation failures — that is 406.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Nothing was processed; convert the body to a supported type and resend safely.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(415).send("Unsupported Media Type");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=415,
content={"detail": "Unsupported Media Type"},
)location /api/resource {
return 415; # Unsupported Media Type
}const res = await fetch("/api/resource");
if (res.status === 415) {
// 415 Unsupported Media Type
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 415 Unsupported Media Type416 Range Not Satisfiable
What it means
The Range header asked for slices the current representation cannot supply — a resume offset beyond the file's length is the classic case, often after the file changed between download sessions.
For byte ranges the response should carry Content-Range: bytes */LENGTH so the client learns the actual size and can restart sensibly.
When to use it
- Range-serving endpoints rejecting out-of-bounds or degenerate range sets.
When not to use it
- Ranges you could partially satisfy — serve the satisfiable part as 206.
- When ignoring Range and sending the whole 200 is acceptable; the spec allows that too.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Safe to retry after correcting the range against the length Content-Range reported.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.set("Content-Range", 'bytes */4096');
res.status(416).send("Range Not Satisfiable");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=416,
content={"detail": "Range Not Satisfiable"},
headers={"Content-Range": "bytes */4096"},
)location /api/resource {
add_header Content-Range 'bytes */4096' always;
return 416; # Range Not Satisfiable
}const res = await fetch("/api/resource");
if (res.status === 416) {
// 416 Range Not Satisfiable
const contentRange = res.headers.get("Content-Range");
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 416 Range Not Satisfiable
# Content-Range: bytes */4096417 Expectation Failed
What it means
The request's Expect header carries an expectation the server (or a proxy on the path) will not meet. Since 100-continue is the only standard expectation, a 417 almost always means some hop does not support that handshake.
Clients recover by resending without the Expect header — most HTTP libraries do it automatically.
When to use it
- Servers or intermediaries that genuinely cannot support 100-continue on a path where a client demands it.
When not to use it
- Rejecting the request's CONTENT — expectation failure is strictly about the Expect header.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
The body was never accepted; resending without Expect is the standard, safe recovery.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(417).send("Expectation Failed");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=417,
content={"detail": "Expectation Failed"},
)location /api/resource {
return 417; # Expectation Failed
}const res = await fetch("/api/resource");
if (res.status === 417) {
// 417 Expectation Failed
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 417 Expectation Failed418 I'm a Teapot
Semi-standard
What it means
Born in RFC 2324 (the Hyper Text Coffee Pot Control Protocol, dated 1998-04-01): 'any attempt to brew coffee with a teapot should result in the error code 418 I'm a teapot.' It is a joke — and one of the best-loved artifacts of internet culture.
RFC 9110 pointedly does NOT define 418: the code point is marked unused/reserved in the IANA registry after a 2017 community campaign ('Save 418') kept frameworks from repurposing it. Many real servers still serve it as an easter egg.
When to use it
- Easter eggs and demo endpoints, where whimsy is the point and no client needs to machine-interpret the answer.
When not to use it
- Production APIs — clients and monitoring treat it as an unknown 4xx, which per HTTP's fallback rule means they handle it as a generic 400.
- As a bot-blocking response; 403 or 429 say what you actually mean.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
The teapot remains a teapot on retry — short and stout, and entirely idempotent about it.
Defined in
Scope: Semi-standard: defined by the April Fools' RFC 2324, marked '(Unused)' by RFC 9110 §15.5.19, yet deliberately reserved in the IANA registry and widely implemented as an easter egg.
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(418).send("I'm a Teapot");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=418,
content={"detail": "I'm a Teapot"},
)location /api/resource {
return 418; # I'm a Teapot
}const res = await fetch("/api/resource");
if (res.status === 418) {
// 418 I'm a Teapot
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 418 I'm a Teapot420 Enhance Your Calm
Non-standard
What it means
Never part of HTTP: Twitter's first API answered over-eager clients with 420 Enhance Your Calm before RFC 6585 gave the world a standard code. Twitter itself moved to 429 in API v1.1.
You will still meet it in old client libraries, mocks and tutorials — treat any surviving sender the way you would treat a 429: back off.
When to use it
- Recognizing it in legacy traffic and third-party logs — never for emitting.
When not to use it
- Your own services — send the standard 429 with Retry-After; every client understands it.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Treat like 429: back off before retrying, whatever the method.
Defined in
No formal spec — Twitter API v1 (historical)
Scope: Non-standard folk code. Twitter API v1 only, superseded there by 429; documented here because it still surfaces in logs and older tooling.
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(420).send("Enhance Your Calm");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=420,
content={"detail": "Enhance Your Calm"},
)location /api/resource {
return 420; # Enhance Your Calm
}const res = await fetch("/api/resource");
if (res.status === 420) {
// 420 Enhance Your Calm
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 420 Enhance Your Calm421 Misdirected Request
What it means
The request arrived at a server unable to produce an authoritative response for the URI's origin — the signature failure of HTTP/2 connection reuse, where a client coalesces two hostnames onto one connection because they share a certificate and IP, but the far end only serves one of them.
Clients recover by retrying on a fresh connection to the correct authority; 421 is defined as retryable that way.
When to use it
- SNI/virtual-host mismatches: the TLS connection's server does not host the :authority the request names.
When not to use it
- Ordinary routing errors inside an application you do control — fix the routing or answer 404.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Defined as safe to retry over a new connection — even for non-idempotent methods, since the misdirected server never processed it.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(421).send("Misdirected Request");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=421,
content={"detail": "Misdirected Request"},
)location /api/resource {
return 421; # Misdirected Request
}const res = await fetch("/api/resource");
if (res.status === 421) {
// 421 Misdirected Request
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 421 Misdirected Request422 Unprocessable Content
What it means
The server understood the content type and the syntax, yet cannot process the instructions inside: a date that ends before it starts, a reference to a nonexistent ID, a field that violates a business rule. RFC 9110 renamed it from WebDAV's 'Unprocessable Entity' and promoted it into core HTTP.
The working division of labor: 400 for 'could not parse it', 422 for 'parsed it, and it does not make sense' — the natural home of field-level validation errors.
When to use it
- Schema-valid JSON failing domain validation — with per-field errors in the body.
- REST APIs distinguishing malformed requests from semantically invalid ones.
When not to use it
- Unparsable bodies or broken syntax — that is 400.
- Conflicts with current resource state — that is 409.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
The request must change before a retry can succeed; resending the same body reproduces the same validation failure.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(422).send("Unprocessable Content");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=422,
content={"detail": "Unprocessable Content"},
)location /api/resource {
return 422; # Unprocessable Content
}const res = await fetch("/api/resource");
if (res.status === 422) {
// 422 Unprocessable Content
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 422 Unprocessable Content423 Locked
What it means
The source or destination of the operation is locked — WebDAV's explicit checkout/checkin machinery at work — and the request did not present the right lock token. The body can say whose lock stands in the way.
When to use it
- WebDAV servers enforcing LOCK/UNLOCK semantics against writers without the token.
When not to use it
- Application-level locking in ordinary REST APIs — 409 with a descriptive body is the conventional answer there.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Retry after the lock is released or with the correct Lock-Token — until then the answer will not change.
Defined in
Scope: WebDAV. General HTTP has no lock model; outside DAV use 409.
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(423).send("Locked");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=423,
content={"detail": "Locked"},
)location /api/resource {
return 423; # Locked
}const res = await fetch("/api/resource");
if (res.status === 423) {
// 423 Locked
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 423 Locked424 Failed Dependency
What it means
Inside multi-step WebDAV operations (PROPPATCH's all-or-nothing property sets, chained MOVEs), 424 marks the steps that never ran because a prerequisite step failed — usually inside a 207 body next to the actual failure.
When to use it
- WebDAV servers reporting cascade failures within compound operations.
When not to use it
- General APIs describing upstream service failure — that is 502/503 territory, not a dependency between the client's own steps.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Fix the failing prerequisite first; the dependent steps were never executed at all.
Defined in
Scope: WebDAV. Meaningful mainly inside 207 Multi-Status bodies.
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(424).send("Failed Dependency");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=424,
content={"detail": "Failed Dependency"},
)location /api/resource {
return 424; # Failed Dependency
}const res = await fetch("/api/resource");
if (res.status === 424) {
// 424 Failed Dependency
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 424 Failed Dependency425 Too Early
What it means
TLS 1.3's 0-RTT mode lets clients send application data before the handshake completes — data an attacker can capture and replay. 425 is the server's refusal to process a possibly-replayed request, telling the client to retry once the handshake is done.
It is infrastructure-level plumbing: browsers and TLS-terminating proxies handle it; application code rarely sees it.
When to use it
- TLS terminators and origins refusing non-idempotent or sensitive requests carried in 0-RTT early data.
When not to use it
- Any 'come back later' outside the TLS-early-data context — that is 429 or 503 with Retry-After.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
The whole point: the request MIGHT be an attacker's replay, so the server refuses side effects; the client retries safely after the handshake, exactly once.
Defined in
Scope: Tied to TLS 1.3 0-RTT early data (RFC 8470); not a general-purpose 'too soon'.
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(425).send("Too Early");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=425,
content={"detail": "Too Early"},
)location /api/resource {
return 425; # Too Early
}const res = await fetch("/api/resource");
if (res.status === 425) {
// 425 Too Early
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 425 Too Early426 Upgrade Required
What it means
The server declines to perform the request on the current protocol but will after the client upgrades — the response's Upgrade header names what it wants (a newer HTTP, or a protocol like WebSocket over the same port).
It is the demand-side sibling of 101: 101 accepts an upgrade the client offered; 426 demands one the client has not made.
When to use it
- Endpoints that require a protocol the client is not speaking on this connection.
When not to use it
- Steering clients from http:// to https:// — that is a 301/308 redirect to the https URI, not an Upgrade.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Nothing was processed; retry after switching to the protocol the Upgrade header names.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.set("Upgrade", 'HTTP/2.0');
res.status(426).send("Upgrade Required");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=426,
content={"detail": "Upgrade Required"},
headers={"Upgrade": "HTTP/2.0"},
)location /api/resource {
add_header Upgrade 'HTTP/2.0' always;
return 426; # Upgrade Required
}const res = await fetch("/api/resource");
if (res.status === 426) {
// 426 Upgrade Required
const upgrade = res.headers.get("Upgrade");
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 426 Upgrade Required
# Upgrade: HTTP/2.0428 Precondition Required
What it means
A guard against the lost-update problem: the origin requires state-changing requests to carry a precondition (typically If-Match with a current ETag), and this one arrived bare. RFC 6585 added it so servers can MANDATE the optimistic-concurrency discipline that 412 enforces.
The pair works as a protocol: 428 teaches clients to send preconditions; 412 tells them when their precondition lost the race.
When to use it
- APIs that require If-Match on every PUT/PATCH/DELETE to prevent blind overwrites.
When not to use it
- When a precondition WAS sent and merely failed — that is 412, not 428.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Nothing executed; fetch the resource, capture its ETag, and resend the request conditionally.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(428).send("Precondition Required");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=428,
content={"detail": "Precondition Required"},
)location /api/resource {
return 428; # Precondition Required
}const res = await fetch("/api/resource");
if (res.status === 428) {
// 428 Precondition Required
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 428 Precondition Required429 Too Many Requests
What it means
The standard rate-limiting response (RFC 6585): the client sent more traffic in a window than this server allows. The body should describe the limit, and Retry-After — seconds or an HTTP date — tells the client when trying again is welcome.
Well-behaved clients treat it as a scheduling instruction, not an error to hammer through: honor Retry-After when present, otherwise back off exponentially with jitter. Many APIs supplement it with RateLimit-* headers so clients can pace themselves before hitting the wall.
When to use it
- API gateways and services enforcing per-client quotas — with Retry-After set to something honest.
- Login and password endpoints throttling brute-force attempts.
When not to use it
- Whole-service overload independent of any one client — that is 503.
- Permanent plan-level caps where 'later' will not help; 402 or 403 with an explanatory body is more truthful.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
The request was rejected before execution, so retrying is side-effect-safe — but only AFTER the Retry-After delay; immediate retries typically extend the penalty.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.set("Retry-After", '120');
res.status(429).send("Too Many Requests");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=429,
content={"detail": "Too Many Requests"},
headers={"Retry-After": "120"},
)location /api/resource {
add_header Retry-After '120' always;
return 429; # Too Many Requests
}const res = await fetch("/api/resource");
if (res.status === 429) {
// 429 Too Many Requests
const retryAfter = res.headers.get("Retry-After");
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 429 Too Many Requests
# Retry-After: 120431 Request Header Fields Too Large
What it means
The server refuses to process the request because its header fields are too big, individually or in total (RFC 6585). In the wild it is almost always cookie bloat: years of accumulated cookies on one domain crossing a proxy's header buffer.
The response can name the offending field; clearing cookies is the classic user-side fix, trimming what you set is the durable server-side one.
When to use it
- Servers and proxies enforcing header-size limits — ideally saying which field overflowed.
When not to use it
- Oversized bodies (413) or URLs (414); 431 is strictly about the header section.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Nothing was processed; shrink the headers (usually cookies) and resend safely.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(431).send("Request Header Fields Too Large");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=431,
content={"detail": "Request Header Fields Too Large"},
)location /api/resource {
return 431; # Request Header Fields Too Large
}const res = await fetch("/api/resource");
if (res.status === 431) {
// 431 Request Header Fields Too Large
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 431 Request Header Fields Too Large451 Unavailable For Legal Reasons
What it means
The server is refusing access as a consequence of a legal demand: a court order, a government block, a DMCA takedown, a geo-fenced regulatory regime. RFC 7725 encourages a body that names the demand and a Link header with rel="blocked-by" identifying who is doing the blocking.
The number is a deliberate literary reference to Ray Bradbury's Fahrenheit 451 — transparency about censorship built into the protocol.
When to use it
- Content blocked in specific jurisdictions, takedown-compliant removals, sanctioned-region blocks — where honesty about the WHY is possible.
When not to use it
- Ordinary policy denials of your own choosing — that is 403.
- When the legal demand itself gags disclosure; operators in that position typically fall back to 403 or 404.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Retrying does not help from the same vantage point — the block is jurisdictional or injunctive, not transient.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(451).send("Unavailable For Legal Reasons");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=451,
content={"detail": "Unavailable For Legal Reasons"},
)location /api/resource {
return 451; # Unavailable For Legal Reasons
}const res = await fetch("/api/resource");
if (res.status === 451) {
// 451 Unavailable For Legal Reasons
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 451 Unavailable For Legal Reasons499 Client Closed Request
Non-standard
What it means
Not an HTTP status at all in the wire sense: nginx records 499 in its access log when the client closed the connection while nginx was still waiting on the response — nothing is ever SENT, because there is no one left to send to.
A burst of 499s reads like impatience made visible: client timeouts shorter than upstream latency, users refreshing away, or load balancers reaping connections. The upstream request may well have completed after the client left — which matters for non-idempotent writes.
When to use it
- Reading nginx logs — it marks abandoned requests and is a latency smell worth graphing.
When not to use it
- Emitting from applications — it is nginx's internal bookkeeping, not a response you can meaningfully send.
Cacheability
Not applicable: nothing is transmitted to the client, so there is nothing for any cache to store (nginx log artifact, not a wire response).
Idempotency context
The dangerous case: the abandoned request may have finished server-side after the client left, so a client retry can double-execute a POST — idempotency keys are the cure.
Defined in
nginx source — ngx_http_request.h
Scope: nginx-only, log-only. Never appears on the wire; other proxies log the same event differently (HAProxy's CQ/CC flags, Envoy's DOWNSTREAM_CONNECTION_TERMINATION).
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(499).send("Client Closed Request");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=499,
content={"detail": "Client Closed Request"},
)location /api/resource {
return 499; # Client Closed Request
}const res = await fetch("/api/resource");
if (res.status === 499) {
// 499 Client Closed Request
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 499 Client Closed Request5xx — Server errors
| Code | Name | Summary | Copy |
|---|---|---|---|
| 500 | Internal Server Error | The server hit an unexpected condition it has no better answer for — the generic server-side failure. | |
| 501 | Not Implemented | The server does not support the functionality — typically the request method — needed to fulfil this. | |
| 502 | Bad Gateway | A gateway or proxy got an invalid response from the upstream server it asked on your behalf. | |
| 503 | Service Unavailable | The server is temporarily unable to serve — overloaded or down for maintenance; Retry-After hints when to return. | |
| 504 | Gateway Timeout | A gateway or proxy gave up waiting for the upstream server to answer in time. | |
| 505 | HTTP Version Not Supported | The server refuses to support the major HTTP version the request used. | |
| 506 | Variant Also Negotiates | Transparent content negotiation is misconfigured: the chosen variant is itself a negotiator, a loop. | |
| 507 | Insufficient Storage | WebDAV: the server cannot store what this request needs stored — the allocation would not fit. | |
| 508 | Loop Detected | WebDAV: the server aborted because it found itself in an infinite loop while processing. | |
| 510 | Not Extended | A relic of RFC 2774's HTTP Extension Framework: the request lacked a mandatory extension declaration. | |
| 511 | Network Authentication Required | A captive portal is in the way: the NETWORK wants a login before it will carry your traffic anywhere. | |
| 999 | Request Denied | A folk code — LinkedIn most famously — for refusing automated traffic; outside HTTP's real 1xx–5xx grid. |
The same 5xx codes in full — each row above links to its entry.
500 Internal Server Error
What it means
Something went wrong on the server and no more specific 5xx applies: an unhandled exception, a null dereference, a database driver throwing mid-request. The client's request may have been perfectly valid.
A disciplined 500 says little to the client (internals belong in your logs, not the body) but carries a correlation ID so support and telemetry can meet in the middle.
When to use it
- Unhandled exceptions surfaced by your framework's last-resort error handler.
- Invariant violations where continuing would corrupt state — fail loudly, log richly.
When not to use it
- Conditions with truthful specific codes: 502 for a bad upstream answer, 503 for overload, 504 for upstream timeout.
- Client mistakes — a 500 on bad input teaches clients nothing and pages your on-call for their typo.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Retry with care: the failure point is unknown, so a POST may have half-executed before dying — idempotent methods retry safely, writes want idempotency keys.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(500).send("Internal Server Error");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=500,
content={"detail": "Internal Server Error"},
)location /api/resource {
return 500; # Internal Server Error
}const res = await fetch("/api/resource");
if (res.status === 500) {
// 500 Internal Server Error
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 500 Internal Server Error501 Not Implemented
What it means
The server does not recognize or cannot support the request method for ANY resource — the whole capability is missing, not merely disallowed on this URI (that narrower case is 405 with Allow).
You meet it from minimal servers and proxies facing methods they never learned: a static file server given PATCH, an old gateway given a WebDAV verb.
When to use it
- Servers refusing methods they genuinely do not implement at all.
- Honest scaffolding for declared-but-unbuilt protocol features.
When not to use it
- A supported method that this particular resource does not offer — that is 405.
- Unbuilt application features behind normal routes — prefer 404 or 403; 501 speaks of protocol capability.
Cacheability
Cacheable by default: this is one of the status codes caches may store and reuse even without explicit freshness information (RFC 9111 heuristic caching).
Idempotency context
Retrying changes nothing until the server grows the capability; nothing was executed.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(501).send("Not Implemented");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=501,
content={"detail": "Not Implemented"},
)location /api/resource {
return 501; # Not Implemented
}const res = await fetch("/api/resource");
if (res.status === 501) {
// 501 Not Implemented
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 501 Not Implemented502 Bad Gateway
What it means
The responder is an intermediary — reverse proxy, load balancer, CDN edge — reporting that the server BEHIND it answered garbage: a connection refused or reset, a truncated or unparsable response, an upstream TLS failure.
Diagnosis therefore starts behind the proxy: is the app process up, is the upstream port right, did the backend crash mid-response? The proxy is the messenger, not (usually) the culprit.
When to use it
- Proxies relaying upstream connection failures and malformed upstream responses.
When not to use it
- A healthy upstream that answered slowly — that is 504.
- Deliberate refusal to serve (maintenance, overload) — that is 503.
- Origin servers with no upstream; an origin's own failure is a 500.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Whether the upstream executed anything before failing is unknowable from outside — retry idempotent requests freely, writes only with idempotency keys.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(502).send("Bad Gateway");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=502,
content={"detail": "Bad Gateway"},
)location /api/resource {
return 502; # Bad Gateway
}const res = await fetch("/api/resource");
if (res.status === 502) {
// 502 Bad Gateway
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 502 Bad Gateway503 Service Unavailable
What it means
A deliberately temporary answer: the service exists and will return, but right now it is overloaded, draining, restarting or under scheduled maintenance. Retry-After — seconds or a date — turns it from a shrug into a schedule.
It is also load-shedding's status of choice: refusing excess work fast with a 503 keeps queues short and recovery quick, which is kinder than timing out slowly.
When to use it
- Planned maintenance windows — with Retry-After and, for user-facing pages, an honest maintenance page.
- Overload protection: shedding traffic a health-checked instance cannot take, circuit breakers holding a failing dependency open.
When not to use it
- Permanent shutdowns — that is 410 for the resource or DNS-level retirement, not eternal 503.
- Per-client rate limiting — that is 429; 503 says WE are the bottleneck, 429 says YOU are.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
The request was refused, not half-done — retry after Retry-After (with jitter, so a thundering herd does not re-create the overload).
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.set("Retry-After", '120');
res.status(503).send("Service Unavailable");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=503,
content={"detail": "Service Unavailable"},
headers={"Retry-After": "120"},
)location /api/resource {
add_header Retry-After '120' always;
return 503; # Service Unavailable
}const res = await fetch("/api/resource");
if (res.status === 503) {
// 503 Service Unavailable
const retryAfter = res.headers.get("Retry-After");
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 503 Service Unavailable
# Retry-After: 120504 Gateway Timeout
What it means
502's slower sibling: the intermediary reached its upstream but the response did not arrive within the proxy's timeout — a slow query, a deadlocked worker, a dependency chain stacking latencies past the edge's budget.
The crucial operational subtlety: the upstream may STILL be working after the proxy quit waiting, so the work might complete after the client has already seen an error.
When to use it
- Proxies enforcing upstream response deadlines (nginx proxy_read_timeout, load-balancer idle timeouts).
When not to use it
- Clients that were too slow SENDING their request — that is 408, on the other side of the exchange.
- Upstreams that answered but answered badly — that is 502.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
The most retry-treacherous 5xx: the timed-out work may still finish, so a blind retry of a POST is a classic double-write — align timeout budgets and use idempotency keys.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(504).send("Gateway Timeout");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=504,
content={"detail": "Gateway Timeout"},
)location /api/resource {
return 504; # Gateway Timeout
}const res = await fetch("/api/resource");
if (res.status === 504) {
// 504 Gateway Timeout
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 504 Gateway Timeout505 HTTP Version Not Supported
What it means
The server does not (or will not) speak the protocol major version of the request. Vanishingly rare in practice: version negotiation happens in ALPN and connection preambles long before application code, so a real 505 usually means something archaic or misconfigured on one end.
When to use it
- Servers that genuinely refuse a request's major HTTP version and want to say so explicitly.
When not to use it
- Demanding an upgrade mid-conversation — 426 with an Upgrade header carries that request.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Idempotency belongs to the request method, not the status code: retrying a GET, PUT or DELETE is safe by definition, while resending a POST may repeat its side effects.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(505).send("HTTP Version Not Supported");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=505,
content={"detail": "HTTP Version Not Supported"},
)location /api/resource {
return 505; # HTTP Version Not Supported
}const res = await fetch("/api/resource");
if (res.status === 505) {
// 505 HTTP Version Not Supported
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 505 HTTP Version Not Supported506 Variant Also Negotiates
What it means
From RFC 2295's experimental transparent content negotiation: the server picked a variant to end the negotiation, but that variant turns out to negotiate too — a circular configuration that can never resolve.
The extension saw almost no deployment, so 506 survives mainly as a registry entry and a very specific misconfiguration report.
When to use it
- Servers implementing RFC 2295 that detect a negotiation loop in their variant configuration.
When not to use it
- Anything outside transparent content negotiation — ordinary negotiation failures are 406.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Idempotency belongs to the request method, not the status code: retrying a GET, PUT or DELETE is safe by definition, while resending a POST may repeat its side effects.
Defined in
Scope: Experimental extension (RFC 2295); registered, essentially undeployed.
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(506).send("Variant Also Negotiates");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=506,
content={"detail": "Variant Also Negotiates"},
)location /api/resource {
return 506; # Variant Also Negotiates
}const res = await fetch("/api/resource");
if (res.status === 506) {
// 506 Variant Also Negotiates
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 506 Variant Also Negotiates507 Insufficient Storage
What it means
The method could not complete because the server cannot allocate the space the operation requires — a WebDAV PUT into a full quota, a COPY that would overflow the destination collection.
It is the server-side counterpart of 413: 413 says YOUR upload is too big; 507 says MY disk (or your quota here) is too full.
When to use it
- WebDAV and storage-backed APIs reporting exhausted quotas or full volumes on write.
When not to use it
- Refusing oversized uploads regardless of free space — that is 413.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Retrying without freeing space fails identically; after space is freed, the method's own idempotency applies.
Defined in
Scope: WebDAV origin (RFC 4918); occasionally borrowed by storage APIs for quota-full conditions.
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(507).send("Insufficient Storage");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=507,
content={"detail": "Insufficient Storage"},
)location /api/resource {
return 507; # Insufficient Storage
}const res = await fetch("/api/resource");
if (res.status === 507) {
// 507 Insufficient Storage
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 507 Insufficient Storage508 Loop Detected
What it means
While executing a Depth: infinity operation, the server discovered a binding loop — a collection that contains itself, however indirectly — and terminated rather than recurse forever (RFC 5842).
Its 2xx cousin 208 marks loop-avoidance that SUCCEEDED inside a multistatus body; 508 is the failure case where processing had to stop.
When to use it
- WebDAV servers with the bindings extension detecting cyclic hierarchies mid-operation.
When not to use it
- Application-level cycles (circular imports, recursive templates) — that is a 500 with a good log line.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Deterministically reproducible — retrying re-walks the same cycle until the binding graph is fixed.
Defined in
Scope: WebDAV bindings extension (RFC 5842).
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(508).send("Loop Detected");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=508,
content={"detail": "Loop Detected"},
)location /api/resource {
return 508; # Loop Detected
}const res = await fetch("/api/resource");
if (res.status === 508) {
// 508 Loop Detected
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 508 Loop Detected510 Not Extended
Semi-standard
What it means
RFC 2774 proposed a framework for mandatory HTTP extensions; 510 was its way of saying 'this resource requires an extension your request did not declare.' The framework never caught on and the RFC is classified Historic.
The code remains registered, so it appears in references like this one — and almost nowhere else.
When to use it
- Realistically: recognizing it in a reference. The framework it belongs to is dead.
When not to use it
- New APIs signalling missing parameters — 400 or 422 with a clear body is the living convention.
Cacheability
Not cacheable by default: caches may store this response only when explicit freshness information — Cache-Control or Expires — permits it (RFC 9111).
Idempotency context
Idempotency belongs to the request method, not the status code: retrying a GET, PUT or DELETE is safe by definition, while resending a POST may repeat its side effects.
Defined in
Scope: Semi-standard: RFC 2774 is Experimental, reclassified Historic; the code point stays IANA-registered.
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(510).send("Not Extended");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=510,
content={"detail": "Not Extended"},
)location /api/resource {
return 510; # Not Extended
}const res = await fetch("/api/resource");
if (res.status === 510) {
// 510 Not Extended
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 510 Not Extended511 Network Authentication Required
What it means
Defined (RFC 6585) for captive portals — hotel, café and airport networks that intercept traffic until you accept terms or log in. The 511 body should contain a link to the portal, and the code exists precisely so machines can tell 'the network is blocking me' apart from 'the site denied me'.
Before 511, portals answered intercepted requests with fake 200s — poisoning caches with login pages, a failure mode the RFC recounts with feeling. It is emitted by network infrastructure, never by origin servers.
When to use it
- Captive-portal gateways intercepting traffic from unauthenticated network clients.
When not to use it
- Origin servers, ever — an origin wanting credentials sends 401; a proxy sends 407. 511 belongs to the network in between.
Cacheability
Explicitly MUST NOT be stored by caches (RFC 6585 §6) — the entire point is that this response is not from the origin.
Idempotency context
The original request never reached its destination; after portal login, retry it unchanged with a clear conscience.
Defined in
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(511).send("Network Authentication Required");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=511,
content={"detail": "Network Authentication Required"},
)location /api/resource {
return 511; # Network Authentication Required
}const res = await fetch("/api/resource");
if (res.status === 511) {
// 511 Network Authentication Required
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 511 Network Authentication Required999 Request Denied
Non-standard
What it means
Not HTTP: no RFC or registry defines any 9xx class. LinkedIn became the famous emitter, answering suspected scrapers and unauthenticated crawlers with 999 Request Denied; a scattering of CDNs and anti-bot layers have copied the habit.
Because 999 falls outside the spec's class system, standards-following clients cannot even apply the 'treat unknown codes as x00' fallback meaningfully — which is arguably the point: it is a bouncer's shrug, not a protocol message.
When to use it
- Recognizing bot-defense walls in scraping logs and third-party integrations — the practical reading is 'blocked, probably permanently, do not hammer'.
When not to use it
- Your own services — 403 (refusal) and 429 (rate limiting) say the same things in a language every client speaks.
Cacheability
Undefined by any spec — caches have no rules for out-of-grid codes; assume nothing is stored and nothing should be.
Idempotency context
Retrying usually digs the hole deeper — anti-bot systems read persistence as confirmation; back off entirely.
Defined in
No formal spec — folk usage (LinkedIn)
Scope: Non-standard folk code with no defining document; sits outside the real 1xx–5xx grid and is grouped with 5xx here only for layout (statusClass is clamped to 5 — see the pack invariant).
Related codes
Handler snippets
app.use("/api/resource", (req, res) => {
res.status(999).send("Request Denied");
});from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/api/resource")
def handler():
return JSONResponse(
status_code=999,
content={"detail": "Request Denied"},
)location /api/resource {
return 999; # Request Denied
}const res = await fetch("/api/resource");
if (res.status === 999) {
// 999 Request Denied
// handle it here
}curl -i https://example.com/api/resource
# HTTP/1.1 999 Request DeniedNo such status code — here's how ranges work
Status codes are grouped by their first digit — the class carries the meaning even when the exact code is unfamiliar.
| Class | Range | What it covers |
|---|---|---|
| 1xx — Informational | 100–199 | Interim responses: the request was received and the exchange is continuing — a final status always follows. |
| 2xx — Successful | 200–299 | The request was received, understood and accepted. |
| 3xx — Redirection | 300–399 | Further action — usually following a Location header — is needed to complete the request. |
| 4xx — Client error | 400–499 | The request appears to be at fault; repeating it unchanged will not help. |
| 5xx — Server error | 500–599 | The server failed to fulfil an apparently valid request; the fault is on its side. |
A client that receives a code it doesn't recognize treats it as the x00 of its class — an unknown 4xx is handled as 400 (RFC 9110 §15).
Do it offline:$ curl -i https://example.com/
How to use it
- Filter as you type — a code (429), the start of one (40), a class (4xx), a reason phrase, or plain words: too many requests lands on 429. The 1xx–5xx chips narrow the query to one class; / focuses the filter box.
- Expand any code. Each row is a native disclosure — it opens even with JavaScript disabled — covering meaning, when to use it, when a neighbour fits better, caching, and retry implications.
- Copy at the granularity the job needs: the row button takes code plus reason phrase (404 Not Found); every handler snippet — Express, FastAPI, nginx, fetch, curl — has its own button.
- Send a colleague straight to one code: Copy link on a panel appends the code as a fragment, so /http-status-codes#429 opens with 429 expanded and announced — ordinary anchors, working in every browser.
Every status code the web actually uses is already on this page, served as plain HTML — find-in-page works before any script loads. Type a number, a name, or a phrase like too many requests into the filter; open a code for its meaning, cacheability and handler snippets; copy exactly what you need. Nothing is fetched and nothing you type leaves the page.
How it works
The first digit of a status code is a contract on its own. RFC 9110 §15 defines the five classes and requires a client that meets an unrecognised code to treat it as the x00 of its class: an unregistered 471 is handled as a 400, an unregistered 599 as a 500. That fallback lets new codes enter the protocol without breaking deployed software — and it is what this page's teaching block explains when a search asks for a code that never existed.
The authoritative list is the IANA HTTP Status Code Registry, changed only through the IETF review process RFC 9110 §16.2 sets out. The dataset here is a versioned snapshot of that registry, cut in CI, never edited by hand; each entry cites the section defining it, and the snapshot's review date is printed below.
Informational 1xx responses are interim: they arrive before the final status and are consumed at the protocol layer. fetch() resolves once, with the final response — response.status is never 100, 101 or 103, even when a 103 Early Hints started preloading long before the HTML arrived. They are documented here because they turn up in curl -i transcripts, proxy logs and server debugging.
The definitions have moved house twice: HTTP/1.1 began as RFC 2616 in 1999, the 2014 rewrite split it across RFC 7230–7235 with 7231 carrying the status codes, and RFC 9110 consolidated everything in 2022. 306 sits in the registry as unused and wears a Deprecated chip here; 418, born of a 1998 April-Fools joke, is implemented widely enough to document as semi-standard.
Examples
teapot418 I'm a Teapot — Semi-standard — RFC 2324 §2.3.2A free-text search: teapot is one of 418's aliases, so the filter narrows to one record. The chip is honest about standing — a joke RFC, not HTTP's own spec — and the citation names the defining section, as every panel does.
#301301 Moved Permanently — The resource lives at the Location URI now and forever; clients and search engines should update their references.
302 Found — The resource is temporarily somewhere else; keep using the original URI next time.
308 Permanent Redirect — Permanent redirect that forbids method rewriting — 301's semantics with the method guaranteed.Opening the page with #301 expands that panel, the alternatives one click away in its related row. Clients historically rewrote POST to GET following 301 and 302; 308 and 307 forbid that rewrite. Decide permanence first, method behaviour second.
5xx12 codes in the 5xx class: 500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511, 999A class query selects a whole section; the count is computed from the dataset, never typed into prose. The odd entry out is 999 — a folk code shelved here for layout, with a scope note that says so.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
420, 999 and 499 are folk codes, labelled as such
None of the three is registered, yet all show up in real logs, so each gets a full entry — Non-standard chip, scope note naming its owner: 499 is nginx noting the client hung up early, 420 was Twitter's old rate-limit reply, 999 is LinkedIn refusing automation.
306 is a reserved ghost
Earmarked for a proxy-switching mechanism in an expired draft, the idea died and RFC 9110 §15.4.7 lists the code point as unused. Its entry wears a Deprecated chip; a 306 in the wild is a bug in whatever sent it.
207 and 423 belong to WebDAV
Both come from RFC 4918, the WebDAV extension, not core HTTP: 207 Multi-Status wraps several per-resource results in one XML body; 423 Locked reports a resource under a DAV lock. Their scope notes keep both out of plain REST handlers.
You will never see a 1xx in fetch()
Interim responses are consumed below JavaScript, so the promise resolves with the final status only. Observe them with curl -i, which prints every status line. One caveat — curl -I switches to a HEAD request, and some servers answer HEAD differently from GET, so diagnose with -i.
Unknown codes fall back to their class
Search for 599 and instead of an empty result you get the rule that governs the live web: an unrecognised code is treated as the x00 of its class (RFC 9110 §15), the nearest documented neighbours offered — an unfamiliar number usually sits one digit from a real code.
Common errors
No such status code — here's how ranges work- The finder's no-match state for a three-digit query in the 100–599 range that matches nothing in the snapshot. Deliberately not a dead end: it lays out the five classes, states the x00 fallback, and offers the nearest documented codes on either side of the number you typed.
curl: (22) The requested URL returned error: 404- Not a transport failure: the server answered, and the answer was 404. curl exits this way only under -f/--fail, which its manual defines as turning any status of 400 or above into exit code 22 and suppressing the body. Drop the flag to see the response itself.
502 Bad Gateway — nginx- nginx's stock page for a proxied request whose upstream returned garbage — a refused connection, a reset, a malformed reply. Its sibling 504 means the upstream never answered inside the proxy's deadline. 502: check the app behind the proxy is up and speaking HTTP. 504: it is alive but too slow.
FAQ
How do HTTP status codes work?
The three-digit verdict on an HTTP exchange, sent in the response's first line: the first digit picks one of five classes, the remaining two the meaning within it. RFC 9110 §15 defines the semantics; the IANA registry records what officially exists.
When do I use 301, 302 or 308 for a redirect?
Two questions decide it. Permanent move? 301 or 308 — browsers and search engines shift to the new address; temporary, 302 or 307. Must the method survive? Legacy clients turned POST into GET after 301 and 302; 308 and 307 forbid that. For a moved API route, choose 308.
Is 418 I'm a Teapot a real status code?
Real enough to meet in production, official never. Born in RFC 2324, the 1998 April Fools' coffee-pot protocol; HTTP's registry keeps the code point reserved, not adopted. Frameworks shipped it anyway — marked Semi-standard here: documented because you will meet it, flagged because no spec blesses it.
Why do my nginx logs show 499?
nginx's private bookkeeping, logged when the client closed the connection before the response was ready — a browser navigated away, or an upstream caller hit its own timeout. It never travels on the wire. A cluster of 499s is a latency signal: responses are slower than the callers' patience.
Which status codes can be cached?
Any response carrying explicit freshness information such as Cache-Control. Without it, RFC 9110 §15.1 lists the heuristically cacheable codes — 200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414 and 501 — which a cache may store on its own initiative. Each panel's cacheability line states which case applies under RFC 9111.
What happens on a code the client doesn't know, like 599?
Fall back to the class: an unrecognised code is treated as the x00 of its range — 599 as 500 — so the request still gets a sensible outcome (RFC 9110 §15). Search an unregistered number here and that rule is explained, nearest real codes offered.
Does a 4xx always mean the client is at fault?
It means the request, as received, could not be served — subtly different from blame. A 404 can be the server's fault for breaking a published URL; a 429 often punishes a polite client for a shared quota. Fix the request or the expectation; only 5xx invites retrying unchanged.
Keyboard shortcuts
- /
- Focus the filter box