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.
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
| Class | Meaning |
|---|---|
1xx | Informational — the request was received, keep going. |
2xx | Success — the request worked. |
3xx | Redirection — you need to go somewhere else. |
4xx | Client error — you sent something wrong. |
5xx | Server 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
Locationheader. 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
Allowheader 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-Afterheader.
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— respectRetry-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
- Client mistake → 4xx. Server mistake → 5xx. Success → 2xx. Never mix them.
- Reach for 201, 204, 303, 401, 403, 404, 422, 429, 503 before you reach for 200 or 500.