HTTP Status Codes — Choosing 200, 404, 500 and Friends

(Updated: July 16, 2026 ) HTTP API REST Web

Status codes are a shared language between clients, proxies, and humans reading logs. Misusing them — 200 with an error body, 500 for bad input — breaks retries, caching, and monitoring. This reference focuses on codes you will actually pick while building web APIs and sites.

Quick map

ClassMeaningClient heuristic
1xxInformationalRare in app code
2xxSuccessProceed
3xxRedirectFollow or fail closed
4xxClient errorFix request; careful retries
5xxServer errorRetry with backoff if idempotent

2xx — success without lying

CodeUse when
200 OKSuccessful GET/PUT/PATCH with body
201 CreatedResource created; prefer Location header
202 AcceptedAsync work queued; not done yet
204 No ContentSuccess with empty body (DELETE often)

Prefer 201 over 200 for creates so clients can branch cleanly. Do not return 200 with { "error": "..." } — that hides failures from CDNs and generic HTTP clients.

3xx — redirects and caches

CodeNotes
301Permanent redirect; caches remember
302/307Temporary; method handling differs (307 preserves method)
304Not Modified — conditional GET success
308Permanent; preserve method

For APIs, avoid surprise redirects on POST. For websites, be consistent about www/apex and trailing slashes so you do not invent redirect loops.

4xx — the client did something wrong (usually)

CodeUse when
400 Bad RequestMalformed JSON, failed validation (generic)
401 UnauthorizedMissing/invalid authentication
403 ForbiddenAuthenticated but not allowed
404 Not FoundNo resource (or hide existence on purpose)
405 Method Not AllowedWrong verb; include Allow
408 Request TimeoutClient took too long
409 ConflictVersion conflict, duplicate unique key
410 GoneUsed to exist; intentional removal
415 Unsupported Media TypeWrong Content-Type
422 Unprocessable EntitySemantically invalid (popular in APIs)
429 Too Many RequestsRate limit; send Retry-After

401 vs 403: unauthenticated vs unauthorized. Browsers treat them differently for UX; APIs should stay precise.

404 vs 403: some security teams return 404 for other users’ private ids to avoid oracle leaks — document the choice.

5xx — your problem (or your dependency’s)

CodeUse when
500Unexpected bug; log correlation id
502 Bad GatewayUpstream returned invalid response
503 Service UnavailableOverload/maintenance; Retry-After helps
504 Gateway TimeoutUpstream too slow

Map dependency failures carefully: if Postgres is down mid-request, 503 may be more honest than 500 for load balancers. Include an opaque request_id in the JSON body for support.

Caching and intermediaries

200/301/404 can be cached depending on headers. Set-Control and Vary matter. Returning 404 for “user not found” on a highly cacheable public CDN URL can sticky-cache a miss — choose cache policies deliberately for APIs.

Idempotency and retries

Clients safely retry GET and often PUT/DELETE. Blindly retrying POST after a timeout can double-charge — use idempotency keys and make status codes reflect whether creation happened (200/201 vs 409).

Problem details bodies

RFC 7807 application/problem+json pairs well with 4xx/5xx:

{
  "type": "https://example.com/errors/validation",
  "title": "Invalid email",
  "status": 422,
  "detail": "email must be a valid address",
  "instance": "/v1/users"
}

Keep status in sync with the HTTP line.

Anti-patterns to delete

  • Everything is 200
  • Validation failures as 500
  • 401 when the password is wrong and when the account is banned (collapse only if intentional)
  • HTML error pages for JSON API clients without content negotiation

Bottom line

Pick the narrowest honest code: 201 for creates, 401/403 for authZ, 404/409/422 for client mistakes, 503 for capacity, 500 for truly unexpected bugs. Your future on-call graphs — and every HTTP client library — depend on that honesty.