How Status Codes Are Structured
HTTP status codes are three-digit numbers grouped into five classes based on the first digit:
- **1xx — Informational:** The request was received and processing is continuing
- **2xx — Success:** The request was received, understood, and accepted
- **3xx — Redirection:** Further action is needed to complete the request
- **4xx — Client Error:** The request contains bad syntax or cannot be fulfilled
- **5xx — Server Error:** The server failed to fulfil a valid request
The Codes You Need to Know
2xx Success
**200 OK** — The standard success response. For GET requests, the response body contains the requested resource. For POST, it contains the result.
**201 Created** — A resource was successfully created. Returned after a successful POST to a creation endpoint. Should include a Location header pointing to the new resource.
**204 No Content** — The request succeeded but there's nothing to return. Common for DELETE requests and PUT requests that update without returning the result.
**206 Partial Content** — Used for range requests (streaming video, resumable downloads). The Content-Range header specifies which chunk is being returned.
3xx Redirection
**301 Moved Permanently** — The resource has permanently moved. Browsers and search engines cache this and update bookmarks. Use for permanent URL changes.
**302 Found** — Temporary redirect. Browsers follow it but don't cache. The original URL is still "valid". Use sparingly — it's often misused where 301 is more appropriate.
**303 See Other** — After a POST request, redirect to a GET request. The standard way to implement the Post/Redirect/Get pattern to prevent form resubmission.
**304 Not Modified** — The client has a cached copy and the content hasn't changed. The server sends this instead of re-transmitting the full response.
**307 Temporary Redirect** — Like 302, but explicitly guarantees the redirect uses the same HTTP method. A POST to a 307 redirects as POST, not GET.
**308 Permanent Redirect** — Like 301, but method-preserving. Use when permanently moving a POST endpoint.
4xx Client Errors
**400 Bad Request** — The request is malformed. Syntax error in the body, invalid JSON, missing required field. The client should not retry without fixing the request.
**401 Unauthorized** — Authentication required (confusingly named — this means "unauthenticated"). The client should send credentials.
**403 Forbidden** — Authenticated but not authorised. The server understood the request but refuses to authorise it. Don't reveal whether the resource exists.
**404 Not Found** — The resource doesn't exist at this URL. Can also be used to hide a 403 — returning 404 for unauthorised access prevents enumeration attacks.
**405 Method Not Allowed** — The HTTP method isn't supported. GET on a POST-only endpoint.
**409 Conflict** — The request conflicts with the current state. Creating a user with a duplicate email, trying to check in a file that's already checked out.
**410 Gone** — Like 404, but the resource used to exist and has been permanently removed. Tells crawlers to remove it from their index.
**422 Unprocessable Entity** — The syntax is valid but the semantics are wrong. The JSON parsed correctly, but the values fail validation. More specific than 400.
**429 Too Many Requests** — Rate limit exceeded. The response should include Retry-After header indicating when the client can retry.
5xx Server Errors
**500 Internal Server Error** — A generic server failure. An unhandled exception, a crashed process, a misconfigured server. Check server logs.
**502 Bad Gateway** — The server (acting as a proxy or gateway) received an invalid response from an upstream server. Common when your app server is down but your reverse proxy (Nginx, Cloudflare) is up.
**503 Service Unavailable** — The server is temporarily unable to handle the request. Maintenance mode, overload, or a deployment in progress. Include Retry-After if you know when it'll be back.
**504 Gateway Timeout** — The upstream server took too long to respond. Your load balancer or CDN couldn't reach your app server in time.
Practical Checklist
- Never return 200 with an error in the body ("200 OK" +
{"error": "not found"}is an anti-pattern) - Use 4xx for client mistakes, 5xx for server mistakes
- 404 is safe for both "not found" and "forbidden" — prevents resource enumeration
- Include meaningful error details in 4xx responses (what went wrong, what the client should do)
- 503 during deployments is better than letting requests hang or fail with 502