HTTP status codes

The status codes worth actually knowing — 201 vs 200, 401 vs 403, 422 vs 400 — plus retry semantics, redirect method preservation, and idempotency.

6 min read

Every HTTP response comes with a three-digit status code. Pick the right one and clients — browsers, proxies, monitoring tools, other services — behave correctly without any extra work. Pick the wrong one and you get silent retries, cached errors, and confused users. This is the shortlist worth actually knowing.

The five classes

ClassMeaning
1xxInformational — the request was received, keep going.
2xxSuccess — the request worked.
3xxRedirection — you need to go somewhere else.
4xxClient error — you sent something wrong.
5xxServer error — we broke something.

2xx — success

  • 200 OK — generic success with a body. Default for GETs.
  • 201 Created — a resource was created. Return the new resource in the body and its URL in a Location header. Use for POSTs that make things.
  • 202 Accepted — request accepted for async processing; result not ready. Return a status URL the client can poll.
  • 204 No Content — success, no body. Perfect for DELETEs and PUTs where you have nothing new to return.

3xx — redirection

  • 301 Moved Permanently — the URL has changed forever. Search engines update their index. Browsers cache aggressively.
  • 302 Found — temporary redirect. Historical baggage: many clients change POST to GET when following it.
  • 303 See Other — after a POST, redirect the client to GET a different URL. The correct code for the classic post-redirect-get pattern.
  • 304 Not Modified — the client's cached copy is still fresh. Sent in response to a conditional GET with If-None-Match/If-Modified-Since. No body.
  • 307 Temporary Redirect / 308 Permanent Redirect — like 302/301 but the HTTP method is guaranteed to be preserved. Prefer these for API redirects.

4xx — client errors

  • 400 Bad Request — the request is syntactically wrong (malformed JSON, missing required field). Generic "you goofed".
  • 401 Unauthorized — you're not authenticated. You haven't proven who you are, or your token is invalid/expired. Client should re-authenticate.
  • 403 Forbidden — you're authenticated but not allowed to do this. Re-logging in won't help.
  • 404 Not Found — the resource doesn't exist. Also fine as a generic "hide the existence of a resource from unauthorized users" response.
  • 405 Method Not Allowed — the URL exists but doesn't accept this HTTP method. Include an Allow header listing what's supported.
  • 409 Conflict — the request would create a conflict with the current state (duplicate unique key, edit-conflict on a versioned resource).
  • 410 Gone — the resource used to exist and is permanently deleted. Nicer than 404 when you can tell.
  • 422 Unprocessable Entity — the request is syntactically valid but semantically wrong (email in a phone field, age of -3). Perfect for validation errors.
  • 429 Too Many Requests — rate limited. Include a Retry-After header.

5xx — server errors

  • 500 Internal Server Error — something threw and you don't want to be more specific. Never return 500 for anything a well-formed client caused.
  • 502 Bad Gateway — you're a proxy and an upstream server gave you garbage.
  • 503 Service Unavailable — you're temporarily overloaded or in maintenance. Include Retry-After. Well-behaved clients back off.
  • 504 Gateway Timeout — you're a proxy and an upstream server didn't respond in time.

Common misuses

  • 200 with an error body. "success: false" inside a 200 response defeats every retry, monitoring, and error-tracking tool. Use the right status code.
  • 401 vs 403. 401 = "who are you"; 403 = "I know who you are, and no". Mixing these up sends users to the login page when they can't be helped by logging in.
  • 400 for validation errors. Technically OK, but 422 is more precise: the syntax was fine, the content wasn't.
  • 500 for user errors. If a specific input caused it, it's a 4xx. Reserve 5xx for "you did nothing wrong; we broke".
  • Redirecting a POST with 301/302. Some clients silently rewrite it to a GET. Use 307/308 to preserve the method, or 303 if you intentionally want a GET at the new URL.

Retry semantics

Well-behaved clients retry on:

  • 408 Request Timeout — the server got bored waiting.
  • 425 Too Early — replay-related.
  • 429 Too Many Requests — respect Retry-After.
  • 500, 502, 503, 504 — transient server issues, exponential backoff.

They do not retry on any other 4xx — the request will fail the same way every time.

Idempotency

Retry safety is a property of the HTTP method, not the status code. GET, PUT, and DELETE are idempotent — calling them repeatedly has the same effect as calling them once. POST is not. For POSTs that must be safe to retry, accept an Idempotency-Key header and dedupe on the server.

The two-line summary

  1. Client mistake → 4xx. Server mistake → 5xx. Success → 2xx. Never mix them.
  2. Reach for 201, 204, 303, 401, 403, 404, 422, 429, 503 before you reach for 200 or 500.

Try the tools

Related reading