Free tool
HTTP status codes
An HTTP status code is the three-digit number a server sends with every response to say how the request went: 2xx worked, 3xx redirects, 4xx is a client error and 5xx is a server error. Below is every code in the IANA registry, plus the nginx, Cloudflare and AWS codes you meet in logs, each with when to use it, common causes and the spec. Search by number, name, symptom or Go/Python constant, or switch to the compact list to scan them all.
Free · no sign-up · runs in your browser, nothing is uploaded
The request was received and the server is still working on it. These are interim responses before the final one.
- 100
Continue
The server got the request headers and the client should go ahead and send the body.
When to use: Sent automatically by servers when a client sends Expect: 100-continue before a large upload.
Common causes: You rarely see it: HTTP clients handle it and wait for the final response.
RFC 9110 §15.2.1 ↗Go: http.StatusContinuePython: HTTPStatus.CONTINUE[send it in code]
- Node.js
res.writeContinue(); // usually automatic
- Node.js
- 101
Switching Protocols
The server agrees to switch to the protocol named in the Upgrade header.
When to use: Answer a WebSocket handshake (Upgrade: websocket) or another protocol upgrade you accept.
Common causes: Normal for WebSocket connections. If a WebSocket fails instead, a proxy may be dropping the Upgrade and Connection headers.
- 102
Processing
WebDAV interim response: the server accepted the request and is still working on it.
When to use: Rarely used today. It was dropped from the later WebDAV spec (RFC 4918).
- 103
Early Hints
Interim response with Link headers so the browser can preload assets while the server prepares the page.
When to use: Send Link: </style.css>; rel=preload before a slow HTML response. Browser support for each hint type varies, so treat it as a speed-up, not a requirement.
RFC 8297 ↗Go: http.StatusEarlyHintsPython: HTTPStatus.EARLY_HINTS[send it in code]
- Node.js
res.writeEarlyHints({ link: '</style.css>; rel=preload; as=style' }); - Go
w.Header().Add("Link", "</style.css>; rel=preload; as=style"); w.WriteHeader(http.StatusEarlyHints)
- Node.js
Interim response telling the client the server supports resumable uploads for this request.
Temporary IANA registration for the resumable uploads draft, set to expire on 2026-11-13 unless renewed.
The request was received, understood and accepted.
The request succeeded. The body holds the result: the resource for GET, the outcome for POST.
When to use: The default success response for reads and for actions that return data.
Common causes: An error page served with 200 (a "soft 404") hides failures from clients and search engines. Send the real error code.
RFC 9110 §15.3.1 ↗Go: http.StatusOKPython: HTTPStatus.OK[send it in code]
- Express
res.status(200).json(data); - Next.js
return Response.json(data, { status: 200 }); - Go
w.WriteHeader(http.StatusOK) - Flask
return data, HTTPStatus.OK
- Express
- 201
Created
The request created a new resource.
When to use: After a POST (or PUT) that creates something. Put the new resource URL in the Location header.
RFC 9110 §15.3.2 ↗Go: http.StatusCreatedPython: HTTPStatus.CREATED[send it in code]
- Express
res.status(201).json(data); - Next.js
return Response.json(data, { status: 201 }); - Go
w.WriteHeader(http.StatusCreated) - Flask
return data, HTTPStatus.CREATED
- Express
- 202
Accepted
The request was accepted for processing, but the work is not done yet.
When to use: Queued or async jobs. Return a URL where the client can check the job status.
RFC 9110 §15.3.3 ↗Go: http.StatusAcceptedPython: HTTPStatus.ACCEPTED[send it in code]
- Express
res.status(202).json(data); - Next.js
return Response.json(data, { status: 202 }); - Go
w.WriteHeader(http.StatusAccepted) - Flask
return data, HTTPStatus.ACCEPTED
- Express
Success, but a transforming proxy changed the content from the origin's 200 response.
When to use: Only proxies that modify responses should send it.
RFC 9110 §15.3.4 ↗Go: http.StatusNonAuthoritativeInfoPython: HTTPStatus.NON_AUTHORITATIVE_INFORMATION[send it in code]
- Express
res.status(203).json(data); - Next.js
return Response.json(data, { status: 203 }); - Go
w.WriteHeader(http.StatusNonAuthoritativeInfo) - Flask
return data, HTTPStatus.NON_AUTHORITATIVE_INFORMATION
- Express
The request succeeded and there is no body to send.
When to use: DELETE, PUT or PATCH that returns nothing, and CORS preflight responses. A 204 must not have a body.
Common causes: Calling response.json() on a 204 throws in JavaScript. Check the status before parsing.
RFC 9110 §15.3.5 ↗Go: http.StatusNoContentPython: HTTPStatus.NO_CONTENT[send it in code]
- Express
res.sendStatus(204); - Next.js
return new Response(null, { status: 204 }); - Go
w.WriteHeader(http.StatusNoContent) - Flask
return "", HTTPStatus.NO_CONTENT
- Express
- 205
Reset Content
Success: the client should reset the document view, for example clear a form.
When to use: Rare. A form submission where the user is expected to enter another one.
RFC 9110 §15.3.6 ↗Go: http.StatusResetContentPython: HTTPStatus.RESET_CONTENT[send it in code]
- Express
res.sendStatus(205); - Next.js
return new Response(null, { status: 205 }); - Go
w.WriteHeader(http.StatusResetContent) - Flask
return "", HTTPStatus.RESET_CONTENT
- Express
The server is sending only the byte range the client asked for in the Range header.
When to use: Video streaming, resumable downloads. Include Content-Range.
RFC 9110 §15.3.7 ↗Go: http.StatusPartialContentPython: HTTPStatus.PARTIAL_CONTENT[send it in code]
- Express
res.status(206).json(data); - Next.js
return Response.json(data, { status: 206 }); - Go
w.WriteHeader(http.StatusPartialContent) - Flask
return data, HTTPStatus.PARTIAL_CONTENT
- Express
- 207
Multi-Status
WebDAV: the body is an XML document with a separate status for each resource.
When to use: Batch operations where some parts can succeed and others fail.
RFC 4918 ↗Go: http.StatusMultiStatusPython: HTTPStatus.MULTI_STATUS[send it in code]
- Express
res.status(207).json(data); - Next.js
return Response.json(data, { status: 207 }); - Go
w.WriteHeader(http.StatusMultiStatus) - Flask
return data, HTTPStatus.MULTI_STATUS
- Express
- 208
Already Reported
WebDAV: members of this binding were already listed earlier in the same 207 response.
When to use: Inside a 207 body, to avoid listing the same resource twice.
RFC 5842 ↗Go: http.StatusAlreadyReportedPython: HTTPStatus.ALREADY_REPORTED[send it in code]
- Express
res.status(208).json(data); - Next.js
return Response.json(data, { status: 208 }); - Go
w.WriteHeader(http.StatusAlreadyReported) - Flask
return data, HTTPStatus.ALREADY_REPORTED
- Express
- 226
IM Used
The server applied delta encoding (instance manipulations) to the response.
When to use: Almost never seen in practice.
RFC 3229 ↗Go: http.StatusIMUsedPython: HTTPStatus.IM_USED[send it in code]
- Express
res.status(226).json(data); - Next.js
return Response.json(data, { status: 226 }); - Go
w.WriteHeader(http.StatusIMUsed) - Flask
return data, HTTPStatus.IM_USED
- Express
The client has to take another step, usually follow the Location header, to finish the request.
There are several representations of the resource and the client should pick one.
When to use: Rare. Most servers pick a representation through content negotiation instead.
RFC 9110 §15.4.1 ↗Go: http.StatusMultipleChoicesPython: HTTPStatus.MULTIPLE_CHOICES[send it in code]
- Express
res.status(300).json({ error: 'Multiple Choices' }); - Next.js
return Response.json({ error: 'Multiple Choices' }, { status: 300 }); - Go
http.Error(w, http.StatusText(300), http.StatusMultipleChoices) - Flask
return {"error": "Multiple Choices"}, HTTPStatus.MULTIPLE_CHOICES
- Express
The resource has a new permanent URL, given in the Location header.
When to use: Permanent URL changes and HTTP to HTTPS redirects for GET pages. Search engines move ranking signals to the new URL.
Common causes: Browsers may turn a POST into a GET when following a 301. Use 308 when the method must stay the same. Browsers cache 301s, so test with care.
RFC 9110 §15.4.2 ↗Go: http.StatusMovedPermanentlyPython: HTTPStatus.MOVED_PERMANENTLY[send it in code]
- Express
res.redirect(301, '/new-url'); - Next.js
return Response.redirect(new URL('/new-url', request.url), 301); - Go
http.Redirect(w, r, "/new-url", http.StatusMovedPermanently) - Flask
return redirect("/new-url", code=301)
- Express
- 302
Found
The resource is temporarily at another URL, given in the Location header.
When to use: Temporary redirects, like sending a logged-out user to /login.
Common causes: Like 301, browsers usually switch POST to GET. Use 307 to keep the method, or 303 to force GET.
RFC 9110 §15.4.3 ↗Go: http.StatusFoundPython: HTTPStatus.FOUND[send it in code]
- Express
res.redirect(302, '/new-url'); - Next.js
return Response.redirect(new URL('/new-url', request.url), 302); - Go
http.Redirect(w, r, "/new-url", http.StatusFound) - Flask
return redirect("/new-url", code=302)
- Express
- 303
See Other
Go GET a different URL to see the result of this request.
When to use: After a form POST, redirect to a result page (the Post/Redirect/Get pattern) so a reload does not resubmit.
RFC 9110 §15.4.4 ↗Go: http.StatusSeeOtherPython: HTTPStatus.SEE_OTHER[send it in code]
- Express
res.redirect(303, '/new-url'); - Next.js
return Response.redirect(new URL('/new-url', request.url), 303); - Go
http.Redirect(w, r, "/new-url", http.StatusSeeOther) - Flask
return redirect("/new-url", code=303)
- Express
- 304
Not Modified
The cached copy the client has is still current, so no body is sent.
When to use: Reply to conditional requests (If-None-Match with an ETag, or If-Modified-Since) when nothing changed.
Common causes: Normal and good: it saves bandwidth. Seeing 304 in DevTools means the browser used its cache.
RFC 9110 §15.4.5 ↗Go: http.StatusNotModifiedPython: HTTPStatus.NOT_MODIFIED[send it in code]
- Express
res.sendStatus(304); - Next.js
return new Response(null, { status: 304 }); - Go
w.WriteHeader(http.StatusNotModified) - Flask
return "", HTTPStatus.NOT_MODIFIED
- Express
Deprecated. It told the client to use the proxy in the Location header.
Deprecated for security reasons. Browsers ignore it.
RFC 9110 §15.4.6 ↗Go: http.StatusUseProxyPython: HTTPStatus.USE_PROXY[send it in code]
- Express
res.status(305).json({ error: 'Use Proxy' }); - Next.js
return Response.json({ error: 'Use Proxy' }, { status: 305 }); - Go
http.Error(w, http.StatusText(305), http.StatusUseProxy) - Flask
return {"error": "Use Proxy"}, HTTPStatus.USE_PROXY
- Express
- 306
(Unused)
Reserved. It was used in an earlier draft ("Switch Proxy") and is no longer used.
- 307
Temporary Redirect
Temporarily at another URL, and the client must repeat the same method and body there.
When to use: Temporary redirects of POST, PUT or DELETE requests. Browsers also show an internal 307 for HSTS upgrades to HTTPS.
RFC 9110 §15.4.8 ↗Go: http.StatusTemporaryRedirectPython: HTTPStatus.TEMPORARY_REDIRECT[send it in code]
- Express
res.redirect(307, '/new-url'); - Next.js
return Response.redirect(new URL('/new-url', request.url), 307); - Go
http.Redirect(w, r, "/new-url", http.StatusTemporaryRedirect) - Flask
return redirect("/new-url", code=307)
- Express
Permanently at another URL, and the client must repeat the same method and body there.
When to use: Permanent moves of API endpoints that receive POST or PUT. Next.js and Vercel use 308 for permanent redirects.
RFC 9110 §15.4.9 ↗Go: http.StatusPermanentRedirectPython: HTTPStatus.PERMANENT_REDIRECT[send it in code]
- Express
res.redirect(308, '/new-url'); - Next.js
return Response.redirect(new URL('/new-url', request.url), 308); - Go
http.Redirect(w, r, "/new-url", http.StatusPermanentRedirect) - Flask
return redirect("/new-url", code=308)
- Express
The request is wrong or not allowed. Sending it again unchanged will fail again.
- 400
Bad Request
The server cannot process the request because of a client error, such as malformed syntax.
When to use: Invalid JSON, a missing required parameter, a bad query string. Say what is wrong in the body.
Common causes: Malformed JSON or headers, a body that does not match Content-Type, cookies that are too large, invalid characters in the URL.
RFC 9110 §15.5.1 ↗Go: http.StatusBadRequestPython: HTTPStatus.BAD_REQUEST[send it in code]
- Express
res.status(400).json({ error: 'Bad Request' }); - Next.js
return Response.json({ error: 'Bad Request' }, { status: 400 }); - Go
http.Error(w, http.StatusText(400), http.StatusBadRequest) - Flask
return {"error": "Bad Request"}, HTTPStatus.BAD_REQUEST
- Express
- 401
Unauthorized
The request has no valid credentials. Despite the name, it means "unauthenticated".
When to use: Missing, expired or invalid token or password. You must send a WWW-Authenticate header with it.
Common causes: Expired JWT or session, missing Authorization header, wrong API key, clock skew on token expiry.
RFC 9110 §15.5.2 ↗Go: http.StatusUnauthorizedPython: HTTPStatus.UNAUTHORIZED[send it in code]
- Express
res.status(401).json({ error: 'Unauthorized' }); - Next.js
return Response.json({ error: 'Unauthorized' }, { status: 401 }); - Go
http.Error(w, http.StatusText(401), http.StatusUnauthorized) - Flask
return {"error": "Unauthorized"}, HTTPStatus.UNAUTHORIZED
- Express
- 402
Payment Required
Reserved for future use. Some APIs use it for billing problems.
When to use: Not standardised. Some services send it when a plan quota is used up or a payment failed.
RFC 9110 §15.5.3 ↗Go: http.StatusPaymentRequiredPython: HTTPStatus.PAYMENT_REQUIRED[send it in code]
- Express
res.status(402).json({ error: 'Payment Required' }); - Next.js
return Response.json({ error: 'Payment Required' }, { status: 402 }); - Go
http.Error(w, http.StatusText(402), http.StatusPaymentRequired) - Flask
return {"error": "Payment Required"}, HTTPStatus.PAYMENT_REQUIRED
- Express
- 403
Forbidden
The server understood who you are (or does not care) and refuses the request.
When to use: The user is logged in but lacks permission. Logging in again will not help.
Common causes: Missing role or scope, file permissions on the server, IP or country blocks, WAF or bot rules, a CSRF check failing, S3 bucket policies.
RFC 9110 §15.5.4 ↗Go: http.StatusForbiddenPython: HTTPStatus.FORBIDDEN[send it in code]
- Express
res.status(403).json({ error: 'Forbidden' }); - Next.js
return Response.json({ error: 'Forbidden' }, { status: 403 }); - Go
http.Error(w, http.StatusText(403), http.StatusForbidden) - Flask
return {"error": "Forbidden"}, HTTPStatus.FORBIDDEN
- Express
The server has nothing at this URL.
When to use: Unknown routes and missing records. Also fine for hiding that a private resource exists.
Common causes: A typo or old link, a deleted page, a wrong base path behind a proxy, trailing slash mismatch, a SPA route the server does not know about.
RFC 9110 §15.5.5 ↗Go: http.StatusNotFoundPython: HTTPStatus.NOT_FOUND[send it in code]
- Express
res.status(404).json({ error: 'Not Found' }); - Next.js
return Response.json({ error: 'Not Found' }, { status: 404 }); - Go
http.Error(w, http.StatusText(404), http.StatusNotFound) - Flask
return {"error": "Not Found"}, HTTPStatus.NOT_FOUND
- Express
The URL exists but does not accept this HTTP method.
When to use: A POST to a read-only endpoint, for example. You must send an Allow header listing the methods that work.
Common causes: Calling GET on a POST-only route, a missing route handler for the method, a static host that rejects POST.
RFC 9110 §15.5.6 ↗Go: http.StatusMethodNotAllowedPython: HTTPStatus.METHOD_NOT_ALLOWED[send it in code]
- Express
res.status(405).json({ error: 'Method Not Allowed' }); - Next.js
return Response.json({ error: 'Method Not Allowed' }, { status: 405 }); - Go
http.Error(w, http.StatusText(405), http.StatusMethodNotAllowed) - Flask
return {"error": "Method Not Allowed"}, HTTPStatus.METHOD_NOT_ALLOWED
- Express
- 406
Not Acceptable
The server cannot produce a response matching the Accept headers the client sent.
When to use: Rare. Most servers send their default format instead.
Common causes: A strict Accept header, or a security module (like mod_security) that rejects the request.
RFC 9110 §15.5.7 ↗Go: http.StatusNotAcceptablePython: HTTPStatus.NOT_ACCEPTABLE[send it in code]
- Express
res.status(406).json({ error: 'Not Acceptable' }); - Next.js
return Response.json({ error: 'Not Acceptable' }, { status: 406 }); - Go
http.Error(w, http.StatusText(406), http.StatusNotAcceptable) - Flask
return {"error": "Not Acceptable"}, HTTPStatus.NOT_ACCEPTABLE
- Express
- 407
Proxy Authentication Required
Like 401, but the client has to authenticate with a proxy first.
When to use: Sent by proxies with a Proxy-Authenticate header.
Common causes: A corporate proxy that needs credentials. Set them in your proxy settings or HTTPS_PROXY URL.
RFC 9110 §15.5.8 ↗Go: http.StatusProxyAuthRequiredPython: HTTPStatus.PROXY_AUTHENTICATION_REQUIRED[send it in code]
- Express
res.status(407).json({ error: 'Proxy Authentication Required' }); - Next.js
return Response.json({ error: 'Proxy Authentication Required' }, { status: 407 }); - Go
http.Error(w, http.StatusText(407), http.StatusProxyAuthRequired) - Flask
return {"error": "Proxy Authentication Required"}, HTTPStatus.PROXY_AUTHENTICATION_REQUIRED
- Express
The server gave up waiting for the client to finish sending the request.
When to use: Sent by servers that close idle connections. The client can repeat the request.
Common causes: A slow or stalled upload, a flaky network, or browsers pre-opening connections that they never use.
RFC 9110 §15.5.9 ↗Go: http.StatusRequestTimeoutPython: HTTPStatus.REQUEST_TIMEOUT[send it in code]
- Express
res.status(408).json({ error: 'Request Timeout' }); - Next.js
return Response.json({ error: 'Request Timeout' }, { status: 408 }); - Go
http.Error(w, http.StatusText(408), http.StatusRequestTimeout) - Flask
return {"error": "Request Timeout"}, HTTPStatus.REQUEST_TIMEOUT
- Express
- 409
Conflict
The request conflicts with the current state of the resource.
When to use: Duplicate unique keys (email already registered), edit conflicts, version mismatches.
RFC 9110 §15.5.10 ↗Go: http.StatusConflictPython: HTTPStatus.CONFLICT[send it in code]
- Express
res.status(409).json({ error: 'Conflict' }); - Next.js
return Response.json({ error: 'Conflict' }, { status: 409 }); - Go
http.Error(w, http.StatusText(409), http.StatusConflict) - Flask
return {"error": "Conflict"}, HTTPStatus.CONFLICT
- Express
The resource used to exist and was removed on purpose, with no forwarding address.
When to use: Deleted content you want search engines to drop. It is a stronger signal than 404.
RFC 9110 §15.5.11 ↗Go: http.StatusGonePython: HTTPStatus.GONE[send it in code]
- Express
res.status(410).json({ error: 'Gone' }); - Next.js
return Response.json({ error: 'Gone' }, { status: 410 }); - Go
http.Error(w, http.StatusText(410), http.StatusGone) - Flask
return {"error": "Gone"}, HTTPStatus.GONE
- Express
- 411
Length Required
The server requires a Content-Length header.
When to use: Sent by servers that do not accept chunked request bodies.
Common causes: A POST with no body and no Content-Length: 0, or a streamed upload to a server that needs the size.
RFC 9110 §15.5.12 ↗Go: http.StatusLengthRequiredPython: HTTPStatus.LENGTH_REQUIRED[send it in code]
- Express
res.status(411).json({ error: 'Length Required' }); - Next.js
return Response.json({ error: 'Length Required' }, { status: 411 }); - Go
http.Error(w, http.StatusText(411), http.StatusLengthRequired) - Flask
return {"error": "Length Required"}, HTTPStatus.LENGTH_REQUIRED
- Express
- 412
Precondition Failed
A condition in the request headers (If-Match, If-Unmodified-Since) was false.
When to use: Optimistic locking: the client sent If-Match with an old ETag, so someone else changed the resource.
RFC 9110 §15.5.13 ↗Go: http.StatusPreconditionFailedPython: HTTPStatus.PRECONDITION_FAILED[send it in code]
- Express
res.status(412).json({ error: 'Precondition Failed' }); - Next.js
return Response.json({ error: 'Precondition Failed' }, { status: 412 }); - Go
http.Error(w, http.StatusText(412), http.StatusPreconditionFailed) - Flask
return {"error": "Precondition Failed"}, HTTPStatus.PRECONDITION_FAILED
- Express
- 413
Content Too Large
The request body is bigger than the server allows. It used to be called Payload Too Large.
When to use: Uploads over your size limit. Add Retry-After if the limit is temporary.
Common causes: nginx client_max_body_size (default 1 MB), body parser limits in Express, serverless body limits on hosting platforms.
RFC 9110 §15.5.14 ↗Go: http.StatusRequestEntityTooLargePython: HTTPStatus.REQUEST_ENTITY_TOO_LARGE[send it in code]
- Express
res.status(413).json({ error: 'Content Too Large' }); - Next.js
return Response.json({ error: 'Content Too Large' }, { status: 413 }); - Go
http.Error(w, http.StatusText(413), http.StatusRequestEntityTooLarge) - Flask
return {"error": "Content Too Large"}, HTTPStatus.REQUEST_ENTITY_TOO_LARGE
- Express
The URL is longer than the server will handle.
When to use: Send large data in the body of a POST instead of the query string.
Common causes: Huge query strings, redirect loops that keep adding parameters.
RFC 9110 §15.5.15 ↗Go: http.StatusRequestURITooLongPython: HTTPStatus.REQUEST_URI_TOO_LONG[send it in code]
- Express
res.status(414).json({ error: 'URI Too Long' }); - Next.js
return Response.json({ error: 'URI Too Long' }, { status: 414 }); - Go
http.Error(w, http.StatusText(414), http.StatusRequestURITooLong) - Flask
return {"error": "URI Too Long"}, HTTPStatus.REQUEST_URI_TOO_LONG
- Express
- 415
Unsupported Media Type
The server does not accept the format of the request body.
When to use: A JSON API that receives form data or no Content-Type.
Common causes: Missing Content-Type: application/json, or sending multipart to an endpoint that expects JSON.
RFC 9110 §15.5.16 ↗Go: http.StatusUnsupportedMediaTypePython: HTTPStatus.UNSUPPORTED_MEDIA_TYPE[send it in code]
- Express
res.status(415).json({ error: 'Unsupported Media Type' }); - Next.js
return Response.json({ error: 'Unsupported Media Type' }, { status: 415 }); - Go
http.Error(w, http.StatusText(415), http.StatusUnsupportedMediaType) - Flask
return {"error": "Unsupported Media Type"}, HTTPStatus.UNSUPPORTED_MEDIA_TYPE
- Express
- 416
Range Not Satisfiable
The byte range in the Range header is outside the file.
When to use: Include Content-Range: bytes */{size} so the client knows the real size.
Common causes: Resuming a download of a file that changed or shrank.
RFC 9110 §15.5.17 ↗Go: http.StatusRequestedRangeNotSatisfiablePython: HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE[send it in code]
- Express
res.status(416).json({ error: 'Range Not Satisfiable' }); - Next.js
return Response.json({ error: 'Range Not Satisfiable' }, { status: 416 }); - Go
http.Error(w, http.StatusText(416), http.StatusRequestedRangeNotSatisfiable) - Flask
return {"error": "Range Not Satisfiable"}, HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE
- Express
- 417
Expectation Failed
The server cannot meet the Expect request header.
Common causes: A server or proxy that does not support Expect: 100-continue. Remove the header from the client (curl adds it for large POSTs).
RFC 9110 §15.5.18 ↗Go: http.StatusExpectationFailedPython: HTTPStatus.EXPECTATION_FAILED[send it in code]
- Express
res.status(417).json({ error: 'Expectation Failed' }); - Next.js
return Response.json({ error: 'Expectation Failed' }, { status: 417 }); - Go
http.Error(w, http.StatusText(417), http.StatusExpectationFailed) - Flask
return {"error": "Expectation Failed"}, HTTPStatus.EXPECTATION_FAILED
- Express
- 418
(Unused)
"I'm a teapot", from the 1998 April Fools' RFC 2324. Reserved so it is never given a real meaning.
Many frameworks keep it as a joke (Go StatusTeapot, Python IM_A_TEAPOT). Do not use it for real errors.
- 421
Misdirected Request
The request reached a server that cannot answer for this host name.
Common causes: HTTP/2 connection reuse across host names that share an IP or certificate but are served differently.
RFC 9110 §15.5.20 ↗Go: http.StatusMisdirectedRequestPython: HTTPStatus.MISDIRECTED_REQUEST[send it in code]
- Express
res.status(421).json({ error: 'Misdirected Request' }); - Next.js
return Response.json({ error: 'Misdirected Request' }, { status: 421 }); - Go
http.Error(w, http.StatusText(421), http.StatusMisdirectedRequest) - Flask
return {"error": "Misdirected Request"}, HTTPStatus.MISDIRECTED_REQUEST
- Express
- 422
Unprocessable Content
The request is well formed, but its content fails validation. It used to be called Unprocessable Entity.
When to use: Validation errors: a valid JSON body with an invalid email or a missing field. Rails, Laravel and FastAPI use it for this.
RFC 9110 §15.5.21 ↗Go: http.StatusUnprocessableEntityPython: HTTPStatus.UNPROCESSABLE_ENTITY[send it in code]
- Express
res.status(422).json({ error: 'Unprocessable Content' }); - Next.js
return Response.json({ error: 'Unprocessable Content' }, { status: 422 }); - Go
http.Error(w, http.StatusText(422), http.StatusUnprocessableEntity) - Flask
return {"error": "Unprocessable Content"}, HTTPStatus.UNPROCESSABLE_ENTITY
- Express
- 423
Locked
WebDAV: the resource is locked.
RFC 4918 ↗Go: http.StatusLockedPython: HTTPStatus.LOCKED[send it in code]
- Express
res.status(423).json({ error: 'Locked' }); - Next.js
return Response.json({ error: 'Locked' }, { status: 423 }); - Go
http.Error(w, http.StatusText(423), http.StatusLocked) - Flask
return {"error": "Locked"}, HTTPStatus.LOCKED
- Express
- 424
Failed Dependency
WebDAV: the request failed because another action it depended on failed.
RFC 4918 ↗Go: http.StatusFailedDependencyPython: HTTPStatus.FAILED_DEPENDENCY[send it in code]
- Express
res.status(424).json({ error: 'Failed Dependency' }); - Next.js
return Response.json({ error: 'Failed Dependency' }, { status: 424 }); - Go
http.Error(w, http.StatusText(424), http.StatusFailedDependency) - Flask
return {"error": "Failed Dependency"}, HTTPStatus.FAILED_DEPENDENCY
- Express
The server will not process a request sent in TLS early data (0-RTT), because it could be replayed.
When to use: The client should retry after the TLS handshake is complete.
RFC 8470 ↗Go: http.StatusTooEarlyPython: HTTPStatus.TOO_EARLY[send it in code]
- Express
res.status(425).json({ error: 'Too Early' }); - Next.js
return Response.json({ error: 'Too Early' }, { status: 425 }); - Go
http.Error(w, http.StatusText(425), http.StatusTooEarly) - Flask
return {"error": "Too Early"}, HTTPStatus.TOO_EARLY
- Express
- 426
Upgrade Required
The server refuses the request over the current protocol. The Upgrade header says which one to use.
RFC 9110 §15.5.22 ↗Go: http.StatusUpgradeRequiredPython: HTTPStatus.UPGRADE_REQUIRED[send it in code]
- Express
res.status(426).json({ error: 'Upgrade Required' }); - Next.js
return Response.json({ error: 'Upgrade Required' }, { status: 426 }); - Go
http.Error(w, http.StatusText(426), http.StatusUpgradeRequired) - Flask
return {"error": "Upgrade Required"}, HTTPStatus.UPGRADE_REQUIRED
- Express
- 428
Precondition Required
The server requires a conditional request, such as If-Match, to prevent lost updates.
RFC 6585 ↗Go: http.StatusPreconditionRequiredPython: HTTPStatus.PRECONDITION_REQUIRED[send it in code]
- Express
res.status(428).json({ error: 'Precondition Required' }); - Next.js
return Response.json({ error: 'Precondition Required' }, { status: 428 }); - Go
http.Error(w, http.StatusText(428), http.StatusPreconditionRequired) - Flask
return {"error": "Precondition Required"}, HTTPStatus.PRECONDITION_REQUIRED
- Express
The client sent too many requests in a given time (rate limiting).
When to use: Rate limits. Add a Retry-After header with the seconds to wait.
Common causes: Hitting an API rate limit. Slow down, honour Retry-After, and retry with exponential backoff and jitter.
RFC 6585 ↗Go: http.StatusTooManyRequestsPython: HTTPStatus.TOO_MANY_REQUESTS[send it in code]
- Express
res.status(429).json({ error: 'Too Many Requests' }); - Next.js
return Response.json({ error: 'Too Many Requests' }, { status: 429 }); - Go
http.Error(w, http.StatusText(429), http.StatusTooManyRequests) - Flask
return {"error": "Too Many Requests"}, HTTPStatus.TOO_MANY_REQUESTS
- Express
- 431
Request Header Fields Too Large
One header, or all headers together, are too large.
Common causes: Too many or too large cookies on the domain, or a huge Authorization token. Clearing cookies for the site usually fixes it.
RFC 6585 ↗Go: http.StatusRequestHeaderFieldsTooLargePython: HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE[send it in code]
- Express
res.status(431).json({ error: 'Request Header Fields Too Large' }); - Next.js
return Response.json({ error: 'Request Header Fields Too Large' }, { status: 431 }); - Go
http.Error(w, http.StatusText(431), http.StatusRequestHeaderFieldsTooLarge) - Flask
return {"error": "Request Header Fields Too Large"}, HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE
- Express
- 451
Unavailable For Legal Reasons
The resource is blocked for legal reasons, such as a court order or government censorship.
When to use: Content you must block in some countries. The number refers to the novel Fahrenheit 451.
RFC 7725 ↗Go: http.StatusUnavailableForLegalReasonsPython: HTTPStatus.UNAVAILABLE_FOR_LEGAL_REASONS[send it in code]
- Express
res.status(451).json({ error: 'Unavailable For Legal Reasons' }); - Next.js
return Response.json({ error: 'Unavailable For Legal Reasons' }, { status: 451 }); - Go
http.Error(w, http.StatusText(451), http.StatusUnavailableForLegalReasons) - Flask
return {"error": "Unavailable For Legal Reasons"}, HTTPStatus.UNAVAILABLE_FOR_LEGAL_REASONS
- Express
The request looked valid but the server failed to handle it.
- 500
Internal Server Error
Something went wrong on the server and there is no more specific code.
When to use: Unhandled exceptions. Log the details, but do not send stack traces to clients.
Common causes: A bug or uncaught exception, a missing environment variable, a failed database query, a bad .htaccess file. Check the server logs.
RFC 9110 §15.6.1 ↗Go: http.StatusInternalServerErrorPython: HTTPStatus.INTERNAL_SERVER_ERROR[send it in code]
- Express
res.status(500).json({ error: 'Internal Server Error' }); - Next.js
return Response.json({ error: 'Internal Server Error' }, { status: 500 }); - Go
http.Error(w, http.StatusText(500), http.StatusInternalServerError) - Flask
return {"error": "Internal Server Error"}, HTTPStatus.INTERNAL_SERVER_ERROR
- Express
The server does not support the feature needed to handle the request, such as an unknown method.
When to use: A method the server does not know at all. For a known method on the wrong route, use 405.
RFC 9110 §15.6.2 ↗Go: http.StatusNotImplementedPython: HTTPStatus.NOT_IMPLEMENTED[send it in code]
- Express
res.status(501).json({ error: 'Not Implemented' }); - Next.js
return Response.json({ error: 'Not Implemented' }, { status: 501 }); - Go
http.Error(w, http.StatusText(501), http.StatusNotImplemented) - Flask
return {"error": "Not Implemented"}, HTTPStatus.NOT_IMPLEMENTED
- Express
A proxy or gateway got an invalid response from the upstream server.
Common causes: The app behind nginx or a load balancer crashed, is not listening on the expected port, closed the connection early, or sent a malformed response.
RFC 9110 §15.6.3 ↗Go: http.StatusBadGatewayPython: HTTPStatus.BAD_GATEWAY[send it in code]
- Express
res.status(502).json({ error: 'Bad Gateway' }); - Next.js
return Response.json({ error: 'Bad Gateway' }, { status: 502 }); - Go
http.Error(w, http.StatusText(502), http.StatusBadGateway) - Flask
return {"error": "Bad Gateway"}, HTTPStatus.BAD_GATEWAY
- Express
The server cannot handle the request right now, usually because of overload or maintenance.
When to use: Planned maintenance and load shedding. Add Retry-After. Search engines treat it as temporary.
Common causes: Too much traffic, no healthy instances behind the load balancer, maintenance mode, a dependency that is down.
RFC 9110 §15.6.4 ↗Go: http.StatusServiceUnavailablePython: HTTPStatus.SERVICE_UNAVAILABLE[send it in code]
- Express
res.status(503).json({ error: 'Service Unavailable' }); - Next.js
return Response.json({ error: 'Service Unavailable' }, { status: 503 }); - Go
http.Error(w, http.StatusText(503), http.StatusServiceUnavailable) - Flask
return {"error": "Service Unavailable"}, HTTPStatus.SERVICE_UNAVAILABLE
- Express
A proxy or gateway did not get a response from the upstream server in time.
Common causes: A slow query or API call in the app, a proxy timeout lower than the app's work time (nginx proxy_read_timeout defaults to 60s), serverless function time limits.
RFC 9110 §15.6.5 ↗Go: http.StatusGatewayTimeoutPython: HTTPStatus.GATEWAY_TIMEOUT[send it in code]
- Express
res.status(504).json({ error: 'Gateway Timeout' }); - Next.js
return Response.json({ error: 'Gateway Timeout' }, { status: 504 }); - Go
http.Error(w, http.StatusText(504), http.StatusGatewayTimeout) - Flask
return {"error": "Gateway Timeout"}, HTTPStatus.GATEWAY_TIMEOUT
- Express
- 505
HTTP Version Not Supported
The server does not support the HTTP version used in the request.
RFC 9110 §15.6.6 ↗Go: http.StatusHTTPVersionNotSupportedPython: HTTPStatus.HTTP_VERSION_NOT_SUPPORTED[send it in code]
- Express
res.status(505).json({ error: 'HTTP Version Not Supported' }); - Next.js
return Response.json({ error: 'HTTP Version Not Supported' }, { status: 505 }); - Go
http.Error(w, http.StatusText(505), http.StatusHTTPVersionNotSupported) - Flask
return {"error": "HTTP Version Not Supported"}, HTTPStatus.HTTP_VERSION_NOT_SUPPORTED
- Express
- 506
Variant Also Negotiates
A server configuration error in transparent content negotiation: the chosen variant negotiates again, causing a loop.
RFC 2295 ↗Go: http.StatusVariantAlsoNegotiatesPython: HTTPStatus.VARIANT_ALSO_NEGOTIATES[send it in code]
- Express
res.status(506).json({ error: 'Variant Also Negotiates' }); - Next.js
return Response.json({ error: 'Variant Also Negotiates' }, { status: 506 }); - Go
http.Error(w, http.StatusText(506), http.StatusVariantAlsoNegotiates) - Flask
return {"error": "Variant Also Negotiates"}, HTTPStatus.VARIANT_ALSO_NEGOTIATES
- Express
- 507
Insufficient Storage
WebDAV: the server does not have the space to store what the request needs.
RFC 4918 ↗Go: http.StatusInsufficientStoragePython: HTTPStatus.INSUFFICIENT_STORAGE[send it in code]
- Express
res.status(507).json({ error: 'Insufficient Storage' }); - Next.js
return Response.json({ error: 'Insufficient Storage' }, { status: 507 }); - Go
http.Error(w, http.StatusText(507), http.StatusInsufficientStorage) - Flask
return {"error": "Insufficient Storage"}, HTTPStatus.INSUFFICIENT_STORAGE
- Express
- 508
Loop Detected
WebDAV: the server found an infinite loop while processing the request.
RFC 5842 ↗Go: http.StatusLoopDetectedPython: HTTPStatus.LOOP_DETECTED[send it in code]
- Express
res.status(508).json({ error: 'Loop Detected' }); - Next.js
return Response.json({ error: 'Loop Detected' }, { status: 508 }); - Go
http.Error(w, http.StatusText(508), http.StatusLoopDetected) - Flask
return {"error": "Loop Detected"}, HTTPStatus.LOOP_DETECTED
- Express
The request needs an HTTP extension the server does not support.
Marked obsolete in the IANA registry: the HTTP Extension Framework was moved to Historic.
RFC 2774 ↗Go: http.StatusNotExtendedPython: HTTPStatus.NOT_EXTENDED[send it in code]
- Express
res.status(510).json({ error: 'Not Extended' }); - Next.js
return Response.json({ error: 'Not Extended' }, { status: 510 }); - Go
http.Error(w, http.StatusText(510), http.StatusNotExtended) - Flask
return {"error": "Not Extended"}, HTTPStatus.NOT_EXTENDED
- Express
- 511
Network Authentication Required
The client has to sign in to the network, for example a hotel or airport Wi-Fi captive portal.
When to use: Sent by captive portals, never by origin servers.
RFC 6585 ↗Go: http.StatusNetworkAuthenticationRequiredPython: HTTPStatus.NETWORK_AUTHENTICATION_REQUIRED[send it in code]
- Express
res.status(511).json({ error: 'Network Authentication Required' }); - Next.js
return Response.json({ error: 'Network Authentication Required' }, { status: 511 }); - Go
http.Error(w, http.StatusText(511), http.StatusNetworkAuthenticationRequired) - Flask
return {"error": "Network Authentication Required"}, HTTPStatus.NETWORK_AUTHENTICATION_REQUIRED
- Express
Not in the IANA registry, but you will meet them in logs and error pages: nginx, Cloudflare, AWS load balancers, Laravel.
Laravel's response when the CSRF token is missing or does not match.
Causes and fixes: An expired session, a form without @csrf, or a cached page with an old token.
nginx closes the connection without sending any response. It shows up only in nginx logs.
When to use: return 444; to drop requests from scanners or for unknown host names.
The client closed the connection to the load balancer before the idle timeout elapsed.
Causes and fixes: A client timeout shorter than the load balancer's idle timeout.
The load balancer got an X-Forwarded-For header with more than 30 IP addresses.
The request protocol does not match the target group protocol version (for example HTTP/1.1 to a gRPC target).
nginx's internal code for request headers bigger than large_client_header_buffers. The client gets a 400 "Request Header Or Cookie Too Large" page.
An error occurred while verifying the client certificate (mutual TLS). The client gets a 400; the code is for error_page handling.
The client did not present a required client certificate. The client gets a 400; the code is for error_page handling.
A plain HTTP request was sent to a port that expects HTTPS. The client gets a 400; the code is for error_page handling.
Causes and fixes: A URL with http:// and an explicit HTTPS port, like http://example.com:443.
The client closed the connection while nginx was still waiting for the upstream. It appears only in logs.
Causes and fixes: A slow upstream and an impatient client: the user navigated away, a fetch was aborted, or a client timeout fired first.
The origin sent an empty, unknown or unexpected response to Cloudflare.
Causes and fixes: The origin crashed or reset the connection, or sent headers that are too large or invalid.
The origin refused the connection from Cloudflare.
Causes and fixes: The web server is stopped, or a firewall blocks Cloudflare's IP ranges.
Cloudflare could not complete a TCP connection to the origin in time.
Causes and fixes: An overloaded origin, a firewall silently dropping packets, a wrong DNS record pointing to the wrong IP.
Cloudflare could not reach the origin at all.
Causes and fixes: A wrong origin IP in DNS, or a routing problem between Cloudflare and the host.
Cloudflare connected to the origin, but the origin did not send a response before the timeout.
Causes and fixes: Long-running requests. Move slow work to a background job and poll for the result.
The TLS handshake between Cloudflare and the origin failed.
Causes and fixes: No certificate on the origin, no common cipher, or SNI not configured on the origin.
In Full (strict) mode, Cloudflare could not validate the origin's certificate.
Causes and fixes: An expired or self-signed origin certificate, or one for the wrong host name.
Returned together with a 1xxx Cloudflare error. The 1xxx code on the page explains the cause.
The five classes of HTTP status codes
The first digit is the class. Clients must understand the class even when they do not know the exact code.
| Class | Meaning | Most common codes |
|---|---|---|
| 1xx | Informational: received, still working. Interim, never the final answer. | 100, 101, 103 |
| 2xx | Success: the request was received, understood and accepted. | 200, 201, 204 |
| 3xx | Redirection: the client must take another step, usually follow Location. | 301, 302, 304, 307, 308 |
| 4xx | Client error: the request is wrong or not allowed. Repeating it unchanged fails again. | 400, 401, 403, 404, 409, 422, 429 |
| 5xx | Server error: the request looked valid, the server failed. | 500, 502, 503, 504 |
RFC 9110 says a client that gets an unknown code treats it as the x00 code of its class: an unknown 471 is handled like 400 Bad Request. Codes outside 100-599 are invalid and should be treated as a server error. Type any three-digit number in the search box to see how it is handled.
Which status code should my API return?
Most APIs need about a dozen codes. These are the usual choices:
| Situation | Code | Notes |
|---|---|---|
| Read succeeded | 200 OK | Body holds the data. |
| Created a resource | 201 Created | Send the new URL in Location. |
| Job queued, not done yet | 202 Accepted | Return a URL to poll for status. |
| Success with nothing to return | 204 No Content | No body at all. Common for DELETE. |
| Malformed request (bad JSON, wrong type) | 400 Bad Request | Say which field or line is wrong. |
| Valid JSON, invalid data | 422 Unprocessable Content | Or 400; pick one and stay consistent. |
| Not logged in, token missing or expired | 401 Unauthorized | Must include WWW-Authenticate. |
| Logged in but not allowed | 403 Forbidden | Or 404 to hide that the resource exists. |
| No such resource | 404 Not Found | Use 410 Gone if it was deleted for good. |
| Wrong method on a real route | 405 Method Not Allowed | Must include Allow: GET, POST. |
| Duplicate or edit conflict | 409 Conflict | Or 412 when using If-Match and ETags. |
| Rate limit hit | 429 Too Many Requests | Add Retry-After. |
| Unhandled error | 500 Internal Server Error | Log details; do not leak stack traces. |
| Maintenance or overload | 503 Service Unavailable | Add Retry-After. |
301 vs 302 vs 303 vs 307 vs 308
Five redirect codes, split by two questions: is the move permanent, and may the client change the method?
| Code | Permanent | Method on the new URL | Use for |
|---|---|---|---|
| 301 | Yes | May change POST to GET | Moved pages, HTTP to HTTPS for GET |
| 302 | No | May change POST to GET | Temporary redirects, login pages |
| 303 | No | Always GET | Redirect after a form POST |
| 307 | No | Same method and body | Temporary redirect of an API call |
| 308 | Yes | Same method and body | Moved API endpoints |
301 and 308 are cacheable by default, so browsers may remember them for a long time. When testing a redirect, start with 302 or 307, and switch to the permanent code once it is right.
Check a status code with curl, JavaScript, Python and Go
# Only the status code
curl -s -o /dev/null -w '%{http_code}\n' https://example.com
# Status line and headers (HEAD request)
curl -sI https://example.com
# Follow redirects and print every hop's status
curl -sIL https://example.com | grep -i '^HTTP/'// JavaScript: fetch only rejects on network errors, not on 404 or 500
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`); // ok is true for 200-299
const data = res.status === 204 ? null : await res.json();# Python (requests) r = requests.get(url, timeout=10) print(r.status_code, r.reason) # 404 Not Found r.raise_for_status() # raises HTTPError for 4xx and 5xx from http import HTTPStatus HTTPStatus(404).phrase # 'Not Found'
// Go
resp, err := http.Get(url)
if err != nil { return err }
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("got %d %s", resp.StatusCode, http.StatusText(resp.StatusCode))
}Status code gotchas
- Soft 404s. An error page sent with 200 OK looks like success to clients, monitoring and search engines. Send the real code with your error page.
- fetch does not throw on errors. A 404 or 500 resolves normally. Check
response.okorresponse.status. - 204 has no body. Calling
response.json()on it throws. Servers must not send a body with 204 or 304. - Reason phrases are optional. The text after the number ("Not Found") can be anything, and HTTP/2 and HTTP/3 do not send it at all. Code against the number.
- Some codes never reach the client. nginx 444 and 499 exist only in logs, and nginx turns 494-497 into a 400 for the client.
- Old names still appear. RFC 9110 renamed 413 Payload Too Large to Content Too Large and 422 Unprocessable Entity to Unprocessable Content. Libraries keep older names too: Go has
StatusRequestEntityTooLargeandStatusRequestURITooLong, from RFC 2616. Search for either name above.
Debugging an API? Turn a request from DevTools into code with the curl converter, read query strings with the URL parser, or inspect a token behind a 401 with the JWT decoder. For monitoring and testing products, browse API tools on DevHunt.
More free developer tools
FAQ
What are the five classes of HTTP status codes?+
The first digit gives the class. 1xx informational (interim responses such as 100 Continue), 2xx success (200 OK, 201 Created), 3xx redirection (301, 302, 304), 4xx client error (400, 401, 403, 404, 429) and 5xx server error (500, 502, 503, 504). Valid codes run from 100 to 599.
What is the difference between 401 and 403?+
401 Unauthorized means the request has no valid credentials: log in or send a valid token and try again. The server must include a WWW-Authenticate header. 403 Forbidden means the server knows who you are, or does not need to, and still refuses: logging in again will not help, you need different permissions.
What is the difference between 301 and 302 redirects?+
301 Moved Permanently says the new URL replaces the old one for good, so browsers cache it and search engines move ranking to the new URL. 302 Found is temporary, so clients keep using the old URL. Both let browsers change a POST into a GET. If the method must stay the same, use 308 (permanent) or 307 (temporary).
What does 502 Bad Gateway mean?+
A proxy, load balancer or CDN in front of your app got an invalid response, or none, from the server behind it. The usual causes are an app that crashed or is restarting, an app listening on a different port than the proxy expects, or an upstream that closed the connection early. Check the app logs first, then the proxy logs.
What is a 499 status code?+
499 Client Closed Request is an nginx code, not a standard one. nginx logs it when the client closed the connection before nginx could send a response, usually because the upstream was slow and the user navigated away or a client timeout fired. The client never receives a 499; you only see it in nginx logs.
Should I return 400 or 422 for validation errors?+
Use 400 Bad Request when the request cannot be parsed, such as malformed JSON or a wrong Content-Type. Use 422 Unprocessable Content when the request is well formed but the data fails validation, such as an invalid email. Many frameworks follow this split (Rails, Laravel, FastAPI). Either is valid if your API is consistent and says what is wrong in the body.
What is the difference between 404 and 410?+
404 Not Found says there is nothing at the URL, without saying whether that is permanent. 410 Gone says the resource existed and was removed on purpose. Search engines treat both as removed pages; 410 is the clearer signal when you deleted content and will not bring it back.
What does HTTP 418 I'm a teapot mean?+
418 comes from an April Fools' joke, RFC 2324, about controlling coffee pots. RFC 9110 lists it as "(Unused)" and reserves it so it never gets a real meaning, because many servers and libraries implement it as a joke. Do not use it for real errors.
Sources: IANA HTTP Status Code Registry · RFC 9110: HTTP Semantics · RFC 6585: Additional HTTP Status Codes · RFC 9113 §8.3.2 (HTTP/2 responses) · MDN: HTTP response status codes · MDN: Response.ok · nginx ngx_http_ssl_module · Cloudflare 5xx errors · AWS Application Load Balancer troubleshooting · Go net/http status constants · Python http.HTTPStatus. Checked 2026-10-31.