HTTP Status Codes: A Practical Reference for Each Class and the Ones You're Probably Misusing
Five classes, dozens of codes, and a small set of frequent misuses (404 vs 410, 401 vs 403, 500 vs 503). Learn which code to actually return when, with an emphasis on real-world API design choices.
HTTP status codes are how servers tell clients what happened. Most developers know 200, 404, and 500 well enough to fake it — but that's where the bugs live. Returning a 200 with { error: ...}in the body is the classic anti-pattern; using 401 when you mean 403 confuses every monitoring tool; 404 vs 410 has SEO implications that quietly cost real traffic.
The five classes
- 1xx — Informational. Provisional response, request processing continues.
- 2xx — Success. Request was received, understood, and accepted.
- 3xx — Redirection. Further action needed to complete the request.
- 4xx — Client error. Request can't be fulfilled because of something the client did.
- 5xx — Server error. Server failed to fulfill an apparently valid request.
The 2xx codes you should know
- 200 OK. Generic success. Returns content. Default for GET.
- 201 Created. Successful POST that created a resource. Should include
Locationheader pointing to the new resource. - 202 Accepted. Request accepted but not yet processed. Used for async operations. Often paired with a polling URL.
- 204 No Content. Success, but no response body. Common for DELETE and idempotent updates with no returnable data.
The 3xx codes and SEO implications
- 301 Moved Permanently. Permanent redirect. Search engines transfer link equity. Use for restructured URLs.
- 302 Found. Temporary redirect. Original URL retains link equity. Use for short-term changes (login redirect, A/B tests).
- 303 See Other. Redirect after POST. The redirected GET fetches a confirmation page. Used to prevent "Are you sure you want to resubmit?" on browser refresh.
- 304 Not Modified. Cache validation success. Server tells client "your cached copy is still good." No body returned.
- 307 Temporary Redirect. Like 302, but enforces same HTTP method. POST stays POST.
- 308 Permanent Redirect. Like 301, but enforces same HTTP method.
The 301 vs 302 mistake
The 4xx codes you actually need
400 Bad Request
Generic "your request has problems." Use for malformed JSON, missing required fields, invalid types. Always include a body explaining what specifically failed.
401 Unauthorized vs 403 Forbidden
These get confused constantly:
- 401: "You're not authenticated. Provide credentials."
- 403: "You're authenticated but not authorized for this resource."
Rule: 401 means the client should retry with credentials. 403 means "don't bother — even with credentials, you can't access this."
404 Not Found vs 410 Gone
Both indicate the resource doesn't exist. The difference matters for SEO:
- 404: "Not found right now. Maybe later." Search engines will continue indexing attempts.
- 410: "Gone permanently. Stop trying." Search engines deindex faster.
Use 410 when you've permanently retired a URL. Search engines clean it from their index 5–10x faster, freeing crawl budget for active pages.
422 Unprocessable Entity
The request was understood (good JSON, well-formed) but the data is semantically wrong (validation errors, business-rule violations). Increasingly preferred over 400 for validation errors because it signals the request could have been valid.
429 Too Many Requests
Rate limiting. Should include Retry-After header (in seconds or as a date). Better than silently dropping requests.
The 5xx codes
- 500 Internal Server Error. The default catch-all when something went wrong on the server. Useful starting point but should be replaced with more specific codes when possible.
- 502 Bad Gateway. Server (acting as gateway/proxy) got an invalid response from an upstream server. Common with load balancers and reverse proxies.
- 503 Service Unavailable. Server is temporarily down (overload or maintenance). Should include
Retry-Afterif known. - 504 Gateway Timeout. Server (acting as gateway) didn't get a response in time from upstream. Different from 502 — upstream may still be processing.
The 200-with-error-body anti-pattern
Some APIs return:
HTTP/1.1 200 OK
{ "error": "Item not found", "code": "NOT_FOUND" }This is wrong. It breaks every monitoring tool, retry logic, and HTTP client default behavior. The correct response:
HTTP/1.1 404 Not Found
{ "error": "Item not found", "code": "NOT_FOUND" }The status code is for machines and infrastructure. The body is for developers. Keep them aligned.
Specific situations and the right code
API key validation
- Missing API key → 401
- Invalid API key → 401
- Expired API key → 401
- Valid key, but no access to this resource → 403
- Valid key, but exceeded rate limit → 429
Resource lifecycle
- POST creates a new resource → 201 with
Locationheader - GET succeeds → 200
- PUT/PATCH succeeds with response body → 200
- PUT/PATCH succeeds with no body → 204
- DELETE succeeds → 204 (no body) or 200 (if returning deleted resource)
- POST creates an async job → 202 with polling URL
Search and filtering
- GET with filters returns empty list → 200 with empty array (not 404).
- Resource at /users/123 doesn't exist → 404.
- Filter syntax is invalid → 400.
Common mistakes
- 200 with error body. Discussed above. The classic API anti-pattern.
- 404 for "no results" from search. Empty list at the resource level isn't "not found" — it's "found, and it's empty."
- 500 for everything. Generic 500s hide the actual issue. Use specific codes when known.
- 302 for permanent moves. Loses SEO equity. Use 301.
- 401 instead of 403 for permission issues. Causes infinite re-auth loops.
- Returning 200 from a webhook handler when processing failed. The remote service won't retry.
- Mixing 5xx for client errors. Bad request data isn't a server error.
The lesser-known but useful codes
- 206 Partial Content. Response to a Range request. Streaming video and resumable downloads.
- 409 Conflict. Request conflicts with current state. Useful for optimistic locking (etag mismatch) and unique constraint violations.
- 412 Precondition Failed. When
If-MatchorIf-Unmodified-Sinceconditions aren't met. Used for ETag-based concurrency control. - 418 I'm a teapot. Yes, this is real. Defined by RFC 2324 as an April Fools joke. Some APIs use it for "automated traffic detected" without revealing the rule.
- 451 Unavailable For Legal Reasons. Geo-blocked content, court-ordered takedowns. Named after Fahrenheit 451.
Key Takeaways
- Status codes are for machines (monitoring, retry logic, caching). Body is for humans. Keep them aligned.
- 200 with error body is the classic anti-pattern. Use the right 4xx/5xx code with the error details in the body.
- 301 for permanent redirects, 302 for temporary. Misusing 302 can quietly destroy SEO rankings.
- 404 vs 410: 410 deindexes faster. Use 410 for permanently retired URLs to free SEO crawl budget.
- 401 = not authenticated; 403 = authenticated but not authorized. The retry logic is fundamentally different.