HTTP Status Codes: The Complete Developer Guide
HTTP status codes are the language servers use to tell clients what happened with a request.
Getting them wrong — returning 200 for errors, 404 for authorization failures, or 500 for
validation issues — breaks your API clients, monitoring, and debugging. This guide explains
every category, the nuances between similar codes, and exactly when to use each one.
1. How HTTP Status Codes Work
Every HTTP response begins with a status line containing a 3-digit status code and a
reason phrase. The first digit defines the class of response:
1xx
Informational
Request received, processing in progress
2xx
Success
Request was received, understood, accepted
3xx
Redirection
Further action needed to complete the request
4xx
Client Error
Request contains bad syntax or cannot be fulfilled
5xx
Server Error
Server failed to fulfil a valid request
The client (browser, mobile app, API consumer) uses the status code to decide how to behave:
retry the request, follow a redirect, show an error page, or parse the response body.
Status codes are not just documentation — they drive real logic in every HTTP client on the planet.
2. 2xx Success Codes — Choosing the Right One
Most developers default to 200 for everything successful but the 2xx range has several
more precise codes that communicate important semantics.
200 OK — The General Success
Use 200 for successful GET, PUT, PATCH, and POST requests that return data. The response body
contains the requested resource or the result of the operation. If there is nothing to return,
use 204 instead.
GET /api/users/42 → 200 OK (return the user object)
PUT /api/users/42 → 200 OK (return the updated user)
POST /api/search → 200 OK (return search results)
201 Created — New Resource
Return 201 when a POST (or sometimes PUT) creates a new resource. Always include a
Location header pointing to the new resource's URL. The body can contain the
created resource or be empty.
POST /api/users
→ 201 Created
→ Location: /api/users/43
→ Body: { "id": 43, "name": "Alice" }
202 Accepted — Async Processing
Use 202 when the request has been accepted for processing but processing is not yet complete.
Common for long-running jobs, email sending, and background tasks. Return a job ID or
polling URL in the body.
POST /api/reports/generate
→ 202 Accepted
→ Body: { "jobId": "abc123", "statusUrl": "/api/jobs/abc123" }
204 No Content — Success With No Body
Return 204 when the operation succeeded but there is nothing to return. The canonical
case is a successful DELETE. Never return 200 with an empty body — use 204 for that.
DELETE /api/users/42 → 204 No Content (no body)
PUT /api/settings → 204 No Content (settings saved, nothing to return)
Rule of thumb: If you created something, use 201. If you return data, use 200.
If the operation succeeded but there is nothing to return, use 204. Use 202 for async work.
3. 3xx Redirect Codes — Permanent vs Temporary
Redirect codes tell the client to make another request to a different URL. Choosing between
them incorrectly has real consequences for SEO and API behavior.
301 Moved Permanently vs 308 Permanent Redirect
Both indicate a permanent move. The critical difference is method preservation:
- 301 — browsers typically downgrade POST to GET on redirect. Search engines transfer link equity. Cached by browsers.
- 308 — the HTTP method is preserved (POST stays POST). Preferred for API endpoints that change URL.
302 Found vs 307 Temporary Redirect
Same distinction: 302 permits method downgrade, 307 preserves the method. Use 307 in APIs.
Use 302 for temporary web page redirects where method doesn't matter.
303 See Other — Post/Redirect/Get Pattern
After a successful POST (e.g. form submission), return 303 with a Location
header pointing to a confirmation page. The browser will GET that page. This prevents
the "resubmit form on back button" problem.
POST /contact-form
→ 303 See Other
→ Location: /thank-you
304 Not Modified — Efficient Caching
Returned when a client sends a conditional GET (with If-None-Match or
If-Modified-Since) and the resource has not changed. No body is returned.
Browsers use the cached copy. Critical for performance on static assets.
SEO note: 301 and 308 pass link equity; 302 and 307 do not. For permanent
URL migrations, always use 301. Never leave a 302 in place for months — it will not pass SEO value.
4. 4xx Client Error Codes — The Developer's Most Important Category
4xx errors tell the client it did something wrong. Picking the right code communicates
exactly what the client needs to fix, making your API dramatically easier to debug.
400 Bad Request — Malformed Syntax
The request cannot be parsed or is structurally invalid. Use 400 for:
invalid JSON, wrong content type, missing required fields in the wrong structural sense,
malformed query parameters.
POST /api/users (Content-Type: text/plain instead of application/json)
→ 400 Bad Request
→ Body: { "error": "Content-Type must be application/json" }
401 Unauthorized — Not Authenticated
The client has not authenticated. The name is misleading — it is really
“unauthenticated.” Include a WWW-Authenticate header indicating
how to authenticate. The client should retry with credentials.
GET /api/profile (no Authorization header)
→ 401 Unauthorized
→ WWW-Authenticate: Bearer realm="api"
403 Forbidden — Not Authorized
The client is authenticated but lacks permission. Re-authenticating will not help.
Return 403 when a logged-in user tries to access a resource they do not own or a
role they do not have.
DELETE /api/users/99 (authenticated as user 42)
→ 403 Forbidden (you can only delete your own account)
404 Not Found — Resource Does Not Exist
The resource does not exist at this URL. Note: some APIs return 404 for authorization
failures to avoid leaking the existence of resources. This is valid security practice
for sensitive resources (e.g. medical records).
405 Method Not Allowed
The HTTP method is not supported on this endpoint. Always include an Allow
header listing the permitted methods.
DELETE /api/products (DELETE not allowed — only GET and POST are)
→ 405 Method Not Allowed
→ Allow: GET, POST
409 Conflict — State Conflicts
The request conflicts with the current resource state. Common cases:
trying to create a resource that already exists (duplicate email),
optimistic locking conflicts (resource was modified since you last read it),
trying to cancel an order that is already shipped.
410 Gone — Permanently Deleted
Unlike 404, a 410 explicitly says the resource existed but has been permanently removed
with no forwarding address. Search engines de-index 410 pages faster than 404s.
Use 410 when you intentionally retire a URL and want search engines to remove it.
422 Unprocessable Entity — Validation Errors
The request is syntactically correct but semantically invalid. This is the right code
for business rule validation failures. Return structured error details in the body.
POST /api/bookings
Body: { "checkIn": "2026-04-15", "checkOut": "2026-04-10" }
→ 422 Unprocessable Entity
→ Body: {
"errors": [
{ "field": "checkOut", "message": "Check-out must be after check-in" }
]
}
429 Too Many Requests — Rate Limiting
The client has exceeded the allowed request rate. Include Retry-After
(seconds or HTTP date) and optionally X-RateLimit-Limit,
X-RateLimit-Remaining, and X-RateLimit-Reset headers.
→ 429 Too Many Requests
→ Retry-After: 30
→ X-RateLimit-Limit: 100
→ X-RateLimit-Remaining: 0
→ X-RateLimit-Reset: 1743000000
The most common mistake: Using 400 for everything. A 400 tells
the client “fix your request structure.” A 422 says “your data is invalid.”
A 403 says “you don't have permission.” Each requires a different response from the client.
5. 5xx Server Error Codes — The Server's Fault
5xx errors indicate the server failed to process a valid request. The client did nothing wrong
— these are your bugs, infrastructure failures, or capacity issues.
500 Internal Server Error — Unhandled Exceptions
The catch-all for unexpected server failures. Return 500 for unhandled exceptions.
Never expose stack traces or internal details in the error body —
log them server-side and return a generic message to the client.
// ❌ Dangerous — leaks implementation details
→ 500 { "error": "MySQLi error: Table 'users' doesn't exist in /app/db.php line 42" }
// ✅ Safe
→ 500 { "error": "An unexpected error occurred. Reference: err_abc123" }
502 Bad Gateway — Upstream Failure
Your server is acting as a proxy (Nginx, API gateway, load balancer) and received an invalid
response from an upstream service. Common when a backend container crashes or restarts.
The fix is upstream, not in the client.
503 Service Unavailable — Temporary Outage
The server is temporarily unable to handle requests. Use during planned maintenance
or under extreme load. Include a Retry-After header. Return a
useful maintenance page for web traffic.
→ 503 Service Unavailable
→ Retry-After: Fri, 27 Mar 2026 02:00:00 GMT
504 Gateway Timeout — Upstream Too Slow
Your proxy did not receive a timely response from an upstream server. Different from 502 —
the upstream did not crash, it just timed out. Common causes: slow database queries,
unoptimized external API calls, network latency spikes.
Observability tip: 5xx rates are the most important metric to alert on.
A sudden spike in 500s means your application is broken. A spike in 502s/504s means an
upstream dependency is failing. Treat them as separate alert rules.
6. Designing REST API Error Responses
The status code alone is not enough. A well-designed error response body helps API clients
(and developers) understand exactly what went wrong and how to fix it.
Recommended Error Body Structure
{
"error": {
"code": "VALIDATION_FAILED", // machine-readable code
"message": "Request validation failed", // human-readable summary
"details": [ // array of specific issues
{
"field": "email",
"message": "Must be a valid email address",
"value": "not-an-email"
}
],
"requestId": "req_7f3k2p9x" // for log correlation
}
}
Key Principles
- Always use the correct HTTP status code — never 200 for errors
- Include a machine-readable error code alongside the human message — clients can switch on it without parsing strings
- Include a request ID to correlate client-reported errors with server logs
- Never leak internals in 5xx responses (stack traces, SQL, file paths)
- Be consistent across all endpoints — use the same error structure everywhere
- For 422 validation errors, include which fields failed and why
7. Quick Decision Guide
A fast reference for the most common API design decisions:
| Situation |
Correct Code |
| GET request, resource found, data returned | 200 OK |
| POST request created a new resource | 201 Created |
| Async job queued, not yet processed | 202 Accepted |
| DELETE succeeded, nothing to return | 204 No Content |
| URL has permanently moved | 301 Moved Permanently |
| After POST form, redirect to confirmation | 303 See Other |
| Request body cannot be parsed | 400 Bad Request |
| No valid credentials provided | 401 Unauthorized |
| Authenticated but no permission | 403 Forbidden |
| Resource does not exist | 404 Not Found |
| Resource existed but permanently deleted | 410 Gone |
| Data passes parsing but fails validation | 422 Unprocessable Entity |
| Client is sending too many requests | 429 Too Many Requests |
| Unhandled exception in your code | 500 Internal Server Error |
| Upstream service returned invalid response | 502 Bad Gateway |
| Planned maintenance or overloaded | 503 Service Unavailable |
| Upstream service did not respond in time | 504 Gateway Timeout |
Frequently Asked Questions
What is the difference between 200 OK and 201 Created?
200 OK is the general success response — data is returned. 201 Created means a new resource
was created. 201 should include a Location header pointing to the new resource's URL.
When should an API return 400 vs 422?
400 when the request is structurally unreadable (invalid JSON, wrong content type).
422 when the request is valid JSON but the values are logically wrong
(date in the past, out-of-range number, business rule violation).
Why does everyone say to never return 200 for an error?
Returning 200 {"status":"error"} breaks HTTP semantics. Monitoring tools,
load balancers, CDNs, and API clients all rely on status codes to detect failures
automatically. A 200 with an error body fools all of them.
What is the difference between 401 and 403?
401 Unauthorized = not authenticated (not logged in).
403 Forbidden = authenticated but no permission.
Re-authenticating fixes a 401. Re-authenticating will not fix a 403.
Should a DELETE request return 200 or 204?
Use 204 No Content if deletion succeeded and there is nothing to return.
Use 200 OK if you are returning a confirmation body.
Never return 200 with an empty body — use 204 for that.
What HTTP status code should I use for rate limiting?
429 Too Many Requests. Include a Retry-After header.
Implement exponential backoff on the client when receiving 429s.