HTTP Status Codes: A Field Guide for Developers
From 200 to 451, the status codes you meet every day and what they really mean.
HTTP status codes are grouped into five classes, identified by the first digit of the three-digit code. Codes in the 1xx range are informational and tell the client to keep waiting for a final response. The 2xx range marks successful requests. The 3xx range is for redirection, telling the client the resource is somewhere else. The 4xx range indicates client errors, meaning the request as sent cannot be fulfilled. The 5xx range indicates server errors, meaning the server failed to handle an otherwise valid request. This grouping is more than a convention, since many HTTP libraries and proxies branch on the first digit, and tools like curl treat 4xx and 5xx differently from 2xx. When you are designing an API or debugging a failing request, the class alone tells you where to look: 4xx means the client did something wrong, 5xx means the server did.
The 200 OK response is the default success code, and it means the server is returning the requested representation in the body. 201 Created is more specific: it should be used when a POST request has successfully created a new resource, and the response should include a Location header pointing to the new resource's URL. 204 No Content means the request succeeded but there is no body to return, which is the right choice for a successful DELETE or a PUT that updates a resource without returning it. The distinction matters for clients that react to the status code rather than parsing the body. Returning 200 with an empty body for a delete operation technically works but loses information for client libraries that use the status code to decide whether to refresh a list. Pick the code that says exactly what happened.
Redirects are subtle, and the difference between them comes down to whether the HTTP method is preserved. 301 Moved Permanently tells the client that the resource has moved and that future requests should use the new URL. Historically, some clients changed POST to GET on a 301, which led to confusion. 302 Found is the temporary equivalent, but it has the same method-changing behavior. To fix this, HTTP/1.1 introduced 307 Temporary Redirect and 308 Permanent Redirect, both of which preserve the original HTTP method. In practice, 308 should replace 301 for permanent moves where you want POST to stay POST, and 307 should replace 302 for temporary moves. Search engines treat 301 and 308 similarly for SEO purposes, but the method-preservation difference matters when you are redirecting form submissions or API calls.
The 4xx range covers errors caused by the client. 400 Bad Request is the catch-all for malformed input, such as invalid JSON or missing required fields. 401 Unauthorized actually means unauthenticated, since the request lacks valid authentication credentials, and the response should include a WWW-Authenticate header. 403 Forbidden means the server understood the request and authenticated the user, but refuses to authorize the action. The difference between 401 and 403 trips up many API designers: 401 asks "who are you?", while 403 says "I know who you are, and you cannot do this." 404 Not Found is the most familiar, returned when the resource does not exist or the server does not want to confirm its existence to an unauthorized user. 429 Too Many Requests is the rate-limiting code, and it should include a Retry-After header telling the client when to try again.
The 5xx range indicates the server failed to handle a valid request. 500 Internal Server Error is the generic catch-all for unhandled exceptions, and it usually means a bug in the application code. 502 Bad Gateway means a proxy or gateway received an invalid response from an upstream server, which often points to a crash or timeout in a backend service. 503 Service Unavailable means the server is temporarily unable to handle the request, usually due to maintenance or overload, and should include a Retry-After header. 504 Gateway Timeout means a proxy did not receive a response from the upstream server in time. When you are debugging, the specific 5xx code tells you where to look: 500 is your application, 502 and 504 are usually infrastructure or a backend you depend on, and 503 is often intentional.
A few less-common status codes are worth knowing. 422 Unprocessable Entity comes from WebDAV but is widely used in APIs to indicate that the request was well-formed syntactically but semantically invalid, for example trying to create an account with an email that is already taken. It is more precise than 400 for validation errors. 418 I'm a Teapot is an April Fools' joke from RFC 2324 that some servers return as an Easter egg; never use it in a real API. 451 Unavailable For Legal Reasons indicates the resource is unavailable due to legal demand, such as government censorship; it is used by sites like GitHub to signal takedowns. 409 Conflict is useful when a request conflicts with the current state of the resource, like trying to create a duplicate. Knowing these codes lets you build APIs that communicate errors precisely rather than overloading 400 and 500 for everything.
When designing an API, the goal is to return a status code that lets the client react correctly without parsing the body. Use 2xx codes that match the action: 201 for creation, 204 for deletion, 200 for general retrieval and updates. Use 4xx codes that describe the client's mistake: 400 for malformed input, 401 for missing auth, 403 for insufficient permissions, 404 for missing resources, 409 for conflicts, 422 for semantic validation errors, 429 for rate limits. Reserve 5xx for genuine server failures, and never use 500 as a default error handler for things like validation errors, because that prevents clients from distinguishing their mistakes from your outages. Include a consistent error body with a machine-readable code and a human-readable message. Document the codes you use, and resist the temptation to invent custom ones unless you also control the client that consumes them.
When something goes wrong with a web request, the browser's DevTools Network panel is your first stop. Open it, reproduce the request, and look at the Status column. A 4xx tells you the request was wrong, so inspect the request headers and body to see what the client actually sent. A 5xx tells you the server failed, so check the server logs if you can and look at the response body for an error message. For APIs, the response body usually contains a structured error that points to the specific field or rule that failed. The Network panel also shows the timing breakdown, which helps distinguish a slow server from a slow network. Use the "Copy as cURL" option to reproduce the exact request in a terminal, and use the "Replay" feature in some browsers to retry without navigating. Reading status codes carefully turns a vague "it doesn't work" into a precise, fixable problem.