HTTP Status Codes Explained
Status codes are the first thing your code should check in any HTTP response. They communicate the outcome of a request in a standardised, machine-readable way. Understanding them deeply helps you write better error handling and debug issues faster.
1. The Five Categories
Every status code begins with a digit that tells you the broad category:
1xx ─── Informational "I received your request, still working..."
2xx ─── Success "It worked!"
3xx ─── Redirection "It's over there now."
4xx ─── Client Error "You did something wrong."
5xx ─── Server Error "I did something wrong."
The remaining two digits are specific codes within that category.
2. 1xx — Informational
Rarely seen by application code. These are protocol-level signals.
| Code | Name | When used |
|---|---|---|
100 | Continue | Server received the request headers; client should proceed with the body |
101 | Switching Protocols | Server agrees to upgrade (e.g. HTTP → WebSocket) |
102 | Processing | Server is working on a long operation (WebDAV) |
3. 2xx — Success
200 OK
The gold standard. Everything worked perfectly.
GET /api/courses/1 HTTP/1.1
→ HTTP/1.1 200 OK
Content-Type: application/json
{"id":1,"title":"HTML5","chapters":18}
201 Created
A new resource was successfully created. Should always include a Location header pointing to the new resource's URL.
POST /api/users HTTP/1.1
{"email":"alice@example.com"}
→ HTTP/1.1 201 Created
Location: /api/users/99
{"id":99,"email":"alice@example.com","createdAt":"2025-03-31T07:38:00Z"}
202 Accepted
The request has been received but processing has not completed. Used for async operations.
POST /api/reports/generate HTTP/1.1
→ HTTP/1.1 202 Accepted
{"jobId":"gen-7d3c9f","status":"queued","checkAt":"/api/jobs/gen-7d3c9f"}
204 No Content
Success, but there's nothing to return (body is empty). Common for DELETE and PATCH operations.
DELETE /api/courses/42 HTTP/1.1
→ HTTP/1.1 204 No Content
[no body]
206 Partial Content
Sent in response to a Range header. Used by video players to stream mid-file.
GET /videos/intro.mp4 HTTP/1.1
Range: bytes=0-999999
→ HTTP/1.1 206 Partial Content
Content-Range: bytes 0-999999/52428800
[first 1MB of video data]
4. 3xx — Redirection
301 Moved Permanently
The resource has moved forever. Search engines update their index; browsers cache the redirect.
GET /old-courses HTTP/1.1
→ HTTP/1.1 301 Moved Permanently
Location: /courses
302 Found (Temporary Redirect)
Temporarily redirected. Search engines keep the old URL; browsers don't cache it.
GET /api/login HTTP/1.1
→ HTTP/1.1 302 Found
Location: /api/v2/auth/login
304 Not Modified
The client's cached version is still valid. No body is sent — the browser uses its cache.
GET /api/courses/1 HTTP/1.1
If-None-Match: "a1b2c3"
→ HTTP/1.1 304 Not Modified
[no body — use your cached version]
307 Temporary Redirect
Like 302, but the HTTP method must not change. A POST to /action stays POST at the redirect target.
308 Permanent Redirect
Like 301, but the HTTP method must not change. Used when moving API endpoints permanently.
5. 4xx — Client Errors
These mean the request had a problem that the client needs to fix.
400 Bad Request
Malformed request — missing required fields, invalid JSON, wrong data type.
// Client sent this:
{ "username": 123 } // should be a string!
// Server responds:
// HTTP/1.1 400 Bad Request
// {"error":"username must be a string"}
401 Unauthorized
The client needs to authenticate first. Despite the name, this means unauthenticated — you haven't proven who you are yet.
// No Authorization header sent
fetch('/api/profile')
// → 401 Unauthorized
// WWW-Authenticate: Bearer realm="api"
// After logging in:
fetch('/api/profile', {
headers: { 'Authorization': `Bearer ${token}` }
})
// → 200 OK
403 Forbidden
You are authenticated, but you don't have permission to access this specific resource.
// Alice is logged in but tries to access Bob's private data:
GET /api/users/bob/private-notes HTTP/1.1
Authorization: Bearer alice-token
→ HTTP/1.1 403 Forbidden
{"error":"You can only access your own notes"}
404 Not Found
The requested resource does not exist at this URL.
GET /api/courses/99999 HTTP/1.1
→ HTTP/1.1 404 Not Found
{"error":"Course not found"}
Be careful: Some APIs return 404 instead of 403 to avoid revealing that a resource exists at all (security through obscurity). Others prefer the explicit 403.
405 Method Not Allowed
The URL is valid but doesn't support the HTTP method used.
DELETE /api/public-announcements/1 HTTP/1.1
→ HTTP/1.1 405 Method Not Allowed
Allow: GET, HEAD
{"error":"Public announcements cannot be deleted via the API"}
Note the Allow header listing the permitted methods.
408 Request Timeout
The server gave up waiting for the client to send the complete request.
409 Conflict
The request conflicts with the current state of the resource.
POST /api/users HTTP/1.1
{"email":"existing@example.com"}
→ HTTP/1.1 409 Conflict
{"error":"A user with this email already exists"}
410 Gone
Like 404, but explicitly permanent — the resource once existed and has been deleted. Search engines should de-index it.
422 Unprocessable Entity
The request is syntactically valid, but the data fails business logic validation.
POST /api/users HTTP/1.1
{"email":"not-an-email","age":-5}
→ HTTP/1.1 422 Unprocessable Entity
{
"errors": [
{"field":"email","message":"Must be a valid email address"},
{"field":"age","message":"Must be a positive number"}
]
}
429 Too Many Requests
Rate limit exceeded. The client is sending too many requests.
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-Rate-Limit-Limit: 100
X-Rate-Limit-Remaining: 0
X-Rate-Limit-Reset: 1711864800
{"error":"Rate limit exceeded. Try again in 60 seconds."}
6. 5xx — Server Errors
These mean the server failed — not the client's fault.
500 Internal Server Error
An unhandled exception or bug on the server. The catch-all for server-side failures.
GET /api/courses/crash-this
→ HTTP/1.1 500 Internal Server Error
{"error":"An unexpected error occurred. Reference: req-7e4c9f21"}
Always return a reference ID (not the stack trace) so developers can look it up in logs without exposing internals.
502 Bad Gateway
The server is acting as a gateway/proxy, and the upstream server it was talking to returned an invalid response.
Browser → nginx (reverse proxy) → Node.js app (crashed)
→ nginx responds: 502 Bad Gateway
503 Service Unavailable
The server is temporarily unable to handle the request (overloaded, maintenance mode, etc.).
HTTP/1.1 503 Service Unavailable
Retry-After: 300
{"error":"The service is under maintenance. Back in 5 minutes."}
504 Gateway Timeout
The upstream server didn't respond in time.
Browser → nginx → Slow database query (30s+)
nginx gives up after 25s → 504 Gateway Timeout
7. Handling Status Codes in JavaScript
async function apiRequest(url, options = {}) {
const response = await fetch(url, options);
const contentType = response.headers.get('content-type') ?? '';
const isJson = contentType.includes('application/json');
switch (true) {
case response.status === 204:
return null; // No content
case response.ok: // 200–299
return isJson ? response.json() : response.text();
case response.status === 401:
// Redirect to login or refresh token
window.location.href = '/login';
throw new Error('Session expired. Please log in again.');
case response.status === 403:
throw new Error('You do not have permission to do this.');
case response.status === 404:
throw new Error('Not found.');
case response.status === 409: {
const body = isJson ? await response.json() : {};
throw new Error(body.error ?? 'Conflict: resource already exists.');
}
case response.status === 422: {
const body = isJson ? await response.json() : {};
const messages = body.errors?.map(e => `${e.field}: ${e.message}`).join(', ');
throw new Error('Validation failed: ' + messages);
}
case response.status === 429: {
const retryAfter = response.headers.get('retry-after') ?? '60';
throw new Error(`Rate limited. Try again in ${retryAfter}s.`);
}
case response.status >= 500:
throw new Error(`Server error (${response.status}). Please try again later.`);
default:
throw new Error(`Unexpected status: ${response.status}`);
}
}
8. Status Code Decision Guide (for API Design)
Request comes in — what should I respond?
│
├── Did it succeed?
│ ├── Reading? → 200 OK
│ ├── Creating? → 201 Created + Location header
│ ├── Async? → 202 Accepted + jobId
│ └── Deleting or PATCH with no return value? → 204 No Content
│
├── Client's fault?
│ ├── Malformed body/missing fields? → 400 Bad Request
│ ├── Not logged in? → 401 Unauthorized + WWW-Authenticate
│ ├── Logged in, no permission? → 403 Forbidden
│ ├── URL doesn't exist? → 404 Not Found
│ ├── Wrong HTTP method? → 405 Method Not Allowed + Allow header
│ ├── Duplicate resource? → 409 Conflict
│ ├── Invalid field values? → 422 Unprocessable Entity
│ └── Too many requests? → 429 Too Many Requests + Retry-After
│
└── Server's fault?
├── Unhandled exception? → 500 Internal Server Error
├── Upstream service down? → 502 Bad Gateway
├── Overloaded/maintenance?→ 503 Service Unavailable + Retry-After
└── Upstream timed out? → 504 Gateway Timeout
9. Common Mistakes
| Mistake | Problem | Fix |
|---|---|---|
Returning 200 for errors (e.g. {"success":false}) | Client code can't detect failure without parsing body | Use the correct 4xx or 5xx status code |
| Returning 500 for validation failures | Misleads client into thinking it's a server bug | Use 400 for bad input, 422 for validation failures |
| Using 404 when resource is forbidden | Hides auth issues — may frustrate legitimate users | Use 403 explicitly (unless hiding resource existence is intentional) |
| Not including error details in 4xx bodies | Developer can't debug without calling support | Return a structured error body with field-level messages |
| Returning a stack trace in 500 responses | Leaks internal server information to attackers | Log the trace server-side; return only a reference ID |
| Never using 201/204 — always returning 200 | API consumers can't know if something was created | Use semantically correct codes |
10. Review Questions
- What is the difference between
401and403? - When would you use
202 Acceptedinstead of200 OK? - What should a server always include in a
201 Createdresponse? - What does
304 Not Modifiedmean and what is the benefit of triggering it? - What is the difference between
404 Not Foundand410 Gone? - When should you use
422 Unprocessable Entityrather than400 Bad Request? - What header should accompany a
429 Too Many Requestsresponse?