What Are HTTP Status Codes Developers Actually Meet
The HTTP status codes developers actually meet, grouped by meaning, with what scripts should do for each, retries, redirects and curl options.

The list of HTTP status codes has about sixty entries and a working developer meets fewer than twenty. Knowing those, and the right response in a script to each, covers most API failures. The exact semantics are set by the standard, and APIs sometimes bend them. The family tables here pair with the error posts 401 versus 403, 429 and backoff and 502 and 504. Examples use curl 8.
See the code in a script
curl -s -o /dev/null -w '%{http_code}\n' https://api.example.test/items
code=$(curl -s -o response.json -w '%{http_code}' https://api.example.test/items)
curl -sS -I https://api.example.test/items | head -1
curl -fsS https://api.example.test/missing; echo "exit: $?"200
HTTP/2 200
curl: (22) The requested URL returned error: 404
exit: 22
-w '%{http_code}' prints the status; without -f curl exits 0 on any HTTP response, which is why a script that does not check the code treats an error page as success. With -f (fail), curl exits 22 for status 400 and above; add --fail-with-body (curl 7.76 and later) to keep the response body for the error message.
2xx: it worked
200 OK request succeeded, body has the result
201 Created a resource was created; look at the Location header
202 Accepted queued, not finished; poll for the result
204 No Content success, nothing to return (common for DELETE)
206 Partial Content a range of the resource (resumed downloads)
Treat the whole 2xx class as success but handle 202 specially: the work is not done yet, and the response usually points to a status URL. A 204 has an empty body, so piping it to jq fails with a parse error; check the code first.
3xx: go somewhere else
301 Moved Permanently update your URL; method may change to GET for POST
302 Found temporary; browsers switch POST to GET
303 See Other follow with GET
304 Not Modified your cached copy is current (response to a conditional request)
307 Temporary Redirect repeat the same method at the new URL
308 Permanent Redirect permanent, same method
curl does not follow redirects unless you pass -L; a script that gets a 301 for an http:// URL that redirects to https:// and ignores it sees an empty or odd body. With -L, a POST redirected by 301 or 302 is re-sent as GET unless you add --post301, so prefer 307 and 308 in your own APIs. 304 is the answer to a conditional request, see avoiding repeated API calls.
4xx: the request is the problem
400 Bad Request malformed syntax or invalid parameters fix the request
401 Unauthorized no or invalid credentials refresh or supply credentials
403 Forbidden authenticated but not allowed change permissions, not credentials
404 Not Found no such resource (or hidden) check the URL and ID
405 Method Not Allowed wrong HTTP method check the Allow header
408 Request Timeout the server gave up waiting retry
409 Conflict state conflict (duplicate, version) resolve, then retry
410 Gone permanently removed stop asking
413 Payload Too Large body exceeds the limit send less
415 Unsupported Media wrong Content-Type set the header
422 Unprocessable Entity well-formed but semantically invalid read the error details
429 Too Many Requests rate limited back off, honour Retry-After
A 4xx tells you that repeating the same request will fail again, so scripts should not retry them blindly. The exceptions are 408 and 429. Distinguishing 401 and 403 matters for what to do next, see 401 versus 403. A 404 may also be returned instead of 403 to avoid revealing that a resource exists. The details of error bodies are in parsing API errors.
5xx: the server failed
500 Internal Server Error unhandled failure in the application
501 Not Implemented the server does not support the method
502 Bad Gateway a proxy got an invalid response from the upstream
503 Service Unavailable overloaded or in maintenance; may have Retry-After
504 Gateway Timeout a proxy timed out waiting for the upstream
These are usually temporary, so retry with exponential backoff and a limit, but only for idempotent requests (GET, PUT, DELETE): a retried POST may have been processed the first time. 502 and 504 come from proxies and load balancers and point to the upstream being down or slow, as in 502 and 504 errors. Honour a Retry-After header when present.
A retry rule for scripts
case $code in
2??) ok ;;
408|429|500|502|503|504) retry_with_backoff ;;
4??) fail "client error $code" ;;
3??) follow_or_fail ;;
esacThis small case statement is a sound default. The full retry pattern, with jitter and a limit, is in rate limits and retries in scripts.
Reading a response as a whole
The status code is one line of information in a response that carries several. The headers add context: Content-Type says how to parse the body, Location says where a created or moved resource lives, Retry-After says how long to wait, WWW-Authenticate says what credentials are expected, and rate-limit headers say how much budget is left. A script that looks only at the code discards the most actionable information. Capture the headers with -D to a file and read the ones relevant to the status you got.
The body is the third part. Well-designed APIs return a structured error with a machine-readable code, a human-readable message and a request identifier. Log all three. Poorly designed ones return an HTML error page from a proxy in front of the application, which is a signal in itself: a 502 with an HTML body came from the proxy, a 502 with JSON came from the application, and the owner of the fix is different in each case. Check the Content-Type header before parsing the body as JSON, and treat a mismatch as an infrastructure failure.
Idempotency and what is safe to retry
Retry decisions depend on the method as much as on the code. GET, HEAD, PUT and DELETE are defined to be idempotent: sending them twice leaves the server in the same state as sending them once. POST is not, so retrying after a timeout may create a second order or send a second email. Many APIs offer an idempotency key header for this reason: the client sends a unique value with the request, and the server returns the stored result for a repeat. Generate the key once per logical operation, not per attempt, and keep it across retries.
A timeout is the hardest case because the status is unknown. The server may have processed the request and failed to answer. Treat unknown outcomes as questions to resolve, by reading the resource's state before retrying a non-idempotent call, not as failures to repeat blindly. A script that creates resources and retries on timeout without checking will, on a bad day, create them several times.
Using codes in your own API
If you write an API, the codes are part of its contract. Use 201 with a Location header for creation, 204 for success with no body, 400 for syntax and validation errors with details of which field failed, 404 for a missing resource, 409 for a conflict such as a duplicate, 422 for well-formed but semantically invalid input when you want to separate it from 400, and 429 with Retry-After for rate limits. Keep 500 for real bugs and alert on them, since a 500 means your code did something you did not expect. Returning 200 with an error field defeats every generic client, cache and monitor, because they all key on the code.
Monitoring by status class
Counting codes by class gives a fast health signal. A rise in 5xx means the service is failing, a rise in 4xx after a release usually means a client or a contract change, and a spike in 429 means somebody is exceeding limits. Chart the ratio of each class to total traffic instead of raw counts so the graph reads the same at night and at noon. Alert on the 5xx ratio crossing a threshold for several minutes, and look at 4xx trends weekly. Both are one line in most log tools, and they find problems before users report them.
Where this does not apply
Some APIs return 200 with an error in the body (GraphQL, some JSON-RPC and older SOAP-style services), so read the payload too. Others use nonstandard codes: 520 to 527 are Cloudflare's, 499 is nginx's "client closed request". The standard is RFC 9110. See the status codes section of RFC 9110 and the MDN status code reference.


