DevToolNow

HTTP Status Codes: Complete Reference (1xx–5xx) for Engineers

DevToolNow Editorial Team··~12 min read

HTTP status codes are the contract between clients and servers — and most APIs misuse them. RFC 9110 (June 2022) is the current source of truth, replacing RFC 7231. This guide is the complete reference engineers actually need: every code that matters, the differences that confuse most developers (401 vs 403, 409 vs 422, 502 vs 503 vs 504), and concrete examples from real APIs.

⚡ Quick mental model

  • 1xx Informational — handshake / hints (rare in app code)
  • 2xx Success — request completed
  • 3xx Redirection — go somewhere else
  • 4xx Client error — your request is wrong
  • 5xx Server error — server failed despite a valid request

1. The 1xx range — Informational

Rarely seen in application code; handled at the transport layer. The only one worth knowing for app developers is 101 Switching Protocols, used for WebSocket upgrades.

  • 100 Continue — server received request headers, client should send body. Used with Expect: 100-continue.
  • 101 Switching Protocols — server agrees to switch protocols (WebSocket, HTTP/2 upgrade).
  • 103 Early Hints (RFC 8297) — preloads resources before the final response. Modern CDNs (Cloudflare, Fastly) support it.

2. The 2xx range — Success

CodeNameWhen to use
200OKDefault success. GET responses, PUT updates with returned body.
201CreatedPOST that created a resource. Include Location header pointing to it.
202AcceptedAsync work queued. Client polls or uses webhooks.
204No ContentSuccess with empty body — DELETE, PUT without echo.
206Partial ContentRange requests (video streaming, large file resume).

Anti-pattern: Returning 200 with { "success": false, "error": "..." }for client errors. This breaks middleware, caching, monitoring, and circuit breakers. Use 4xx codes — your tooling will thank you.

3. The 3xx range — Redirection

The most subtle range. The key distinction is whether clients should preserve the HTTP method (GET vs POST) on the redirected request.

CodePermanent?Method preserved?Use case
301PermanentNo (legacy)Browser URL changes, SEO redirects
302TemporaryNo (legacy)Login redirects, A/B routing
303Forces GETPOST/Redirect/GET pattern
304Not Modified — used with ETag and If-None-Match
307TemporaryYesModern temporary redirects (preserves POST body)
308PermanentYesAPI endpoint moves, load balancer redirects

Practical rule: For REST APIs, prefer 308 over 301. Browsers handle 301 by silently changing POST to GET, which can break write operations during a migration.

4. The 4xx range — Client errors

The most-used and most-misused range. Below are the codes that genuinely matter plus the ones developers consistently get wrong.

4.1 The auth confusion: 401 vs 403

  • 401 Unauthorized — "I don't know who you are." Missing or invalid credentials. Server should return WWW-Authenticate header indicating how to authenticate.
  • 403 Forbidden — "I know who you are, but you can't do this." Authentication is valid but authorization fails (insufficient role, regional block, plan-level restriction).

The naming is famously bad — Unauthorized would more accurately be "Unauthenticated". The codes have not been renamed to preserve compatibility with three decades of client code.

4.2 Validation: 400 vs 422

  • 400 Bad Request — request is malformed (invalid JSON, missing required header, ill-formed URL).
  • 422 Unprocessable Content — request is well-formed but business validation fails (e.g., email field is required but empty, or price is negative).

422 history: originally defined for WebDAV (RFC 4918), promoted to general use by RFC 9110. Most modern frameworks (Rails, Laravel, FastAPI, NestJS) default to 422 for validation errors. Use it.

4.3 The conflict trio: 409, 410, 412

  • 409 Conflict — the resource state conflicts with the request (concurrent edit, duplicate username, version mismatch).
  • 410 Gone — resource was deleted permanently. Used to signal SEO / search engines to drop the URL (vs 404 which suggests "try again later").
  • 412 Precondition Failed — used with If-Match / If-Unmodified-Since for optimistic concurrency control.

4.4 Rate limiting: 429

429 Too Many Requests with a Retry-After header is the standard for rate limiting. The header can be either a number of seconds or an HTTP date. GitHub, Stripe, and Twitter all use this pattern.

HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json

{
  "error": "rate_limit_exceeded",
  "limit": 5000,
  "remaining": 0,
  "reset": 1714867200
}

4.5 Other 4xx codes worth knowing

  • 404 Not Found — resource doesn't exist (or is hidden behind 403 logic that prefers obscurity).
  • 405 Method Not Allowed — the resource exists but doesn't accept this method. Must include Allow header listing supported methods.
  • 406 Not Acceptable — server can't produce content matching the Accept header.
  • 408 Request Timeout — client took too long to send the request.
  • 413 Payload Too Large — body exceeds server limit (uploads).
  • 415 Unsupported Media Type — content-type not supported (e.g., XML when API only accepts JSON).
  • 451 Unavailable For Legal Reasons — RFC 7725. GDPR, court orders, regional bans.

5. The 5xx range — Server errors

5xx codes signal that the server failed despite a valid request. The most-confused trio is 502 vs 503 vs 504:

CodeMeaningCommon cause
500Internal Server ErrorUnhandled exception in your code
501Not ImplementedHTTP method or feature not supported
502Bad GatewayUpstream returned invalid response (origin crashed)
503Service UnavailableOverloaded or under maintenance — use Retry-After
504Gateway TimeoutUpstream didn't respond within timeout window
507Insufficient StorageWebDAV storage full
508Loop DetectedInfinite WebDAV loop
511Network Authentication RequiredCaptive portal (hotel WiFi, public hotspots)

Debugging heuristic: 502/504 mean "upstream is broken or slow" (origin server, third-party API). 503 means "I am intentionally not serving you right now" (rate limit at infrastructure level, deploy in progress, circuit breaker open). 500 means "I tried but my code crashed" (bug in your service).

6. Status codes for caching: 200, 304, ETag

A simple but underused pattern for read-heavy APIs:

# First request
GET /api/users/42
HTTP/1.1 200 OK
ETag: "v3-a8f5"
Cache-Control: private, max-age=60

{ "id": 42, "name": "Ada Lovelace" }

# Second request (within 60s)
GET /api/users/42
If-None-Match: "v3-a8f5"

HTTP/1.1 304 Not Modified
ETag: "v3-a8f5"

# Body is empty — client uses cached copy

304 with proper ETag handling drops bandwidth costs by 80–95% on read-heavy endpoints. Both your CDN and browser respect it automatically.

7. Choosing codes: a decision tree

  1. Did the request succeed?
    • Yes, with body → 200 OK
    • Yes, created a resource → 201 Created + Location header
    • Yes, no body → 204 No Content
    • Async accepted → 202 Accepted
  2. Was the request invalid?
    • Malformed (parser failed) → 400 Bad Request
    • Valid syntax, semantic validation failed → 422 Unprocessable Content
    • Missing or bad credentials → 401 Unauthorized + WWW-Authenticate
    • Authenticated but not allowed → 403 Forbidden
    • Resource missing → 404 Not Found
    • Resource permanently deleted → 410 Gone
    • Conflicting state → 409 Conflict
    • Rate limited → 429 Too Many Requests + Retry-After
  3. Did the server fail?
    • Bug in your code → 500 Internal Server Error
    • Upstream returned bad response → 502 Bad Gateway
    • Intentionally offline → 503 Service Unavailable + Retry-After
    • Upstream timeout → 504 Gateway Timeout

FAQ

Q. What is the difference between 401 and 403?

A. 401 Unauthorized means the request lacks valid authentication credentials — the server doesn't know who you are. 403 Forbidden means the server knows who you are, but you don't have permission for this resource. A common heuristic: if presenting credentials would help, return 401; if no amount of credentials would help (e.g., a regular user trying to access an admin endpoint), return 403.

Q. When should I use 409 vs 422?

A. 409 Conflict means the request conflicts with the current state of the resource (e.g., editing a record that has been modified by someone else, or creating a username that already exists). 422 Unprocessable Content means the request is syntactically correct (valid JSON) but semantically invalid (e.g., 'email' field is required but missing, or value is out of range). 422 was originally from WebDAV (RFC 4918) and is now the de facto standard for validation errors in REST APIs.

Q. What's the difference between 502, 503, and 504?

A. 502 Bad Gateway means an upstream server returned an invalid response (e.g., a malformed HTTP response from your origin to your load balancer). 503 Service Unavailable means the server is temporarily overloaded or down for maintenance — clients can retry with backoff. 504 Gateway Timeout means an upstream server did not respond in time (e.g., your origin took longer than the load balancer's timeout). Use Retry-After header with 503 to hint clients when to retry.

Q. Should I return 200 with an error in the body or use HTTP error codes?

A. Always use HTTP error codes for HTTP-level errors. Returning 200 with {"error": "..."} breaks caching, monitoring, and middleware (CDNs, proxies, observability tools). The only valid case for 200-with-error-body is GraphQL, where the spec explicitly returns 200 for partial errors — and even then, transport errors (network, auth) should still use HTTP status codes.

Q. What does 418 I'm a teapot mean?

A. 418 was an April Fools' joke (RFC 2324, 'Hyper Text Coffee Pot Control Protocol'), and it remains an April Fools' joke. It is not part of the standard HTTP/1.1 specification. Some APIs use it humorously, but production code should avoid it — some clients and proxies treat unknown 4xx codes as 400, breaking error handling.

Q. Is 308 different from 301?

A. Yes. 301 Moved Permanently allows clients to change the request method (most clients change POST to GET). 308 Permanent Redirect requires clients to keep the same method and body. Same for 302 (allow method change) vs 307 (preserve method). Use 308 for permanent redirects of API endpoints; use 301 for browser-facing URL redirects where a GET is expected.

References

About the DevToolNow Editorial Team

DevToolNow's editorial team is made up of working software developers who use these tools every day. Every guide is reviewed against primary sources — IETF RFCs, W3C/WHATWG specifications, MDN Web Docs, and project repositories on GitHub — before publication. We update articles when standards change so the guidance stays current.

Sources we cite: IETF RFCs · MDN Web Docs · WHATWG · ECMAScript spec · Official project READMEs on GitHub