HTTP Status Codes Reference — Complete Guide to All Response Codes
HTTP status codes tell clients what happened with their request. Here's a complete reference for all status code classes (1xx-5xx), when to use each code, and common...
HTTP status codes are three-digit numbers in the server’s response that tell the client what happened. The first digit defines the class: 1xx (informational), 2xx (success), 3xx (redirect), 4xx (client error), 5xx (server error). Using the right code makes APIs predictable and debuggable.
Use the HTTP Status Codes reference tool for a searchable guide to all codes.
1xx — Informational
Rarely used in application code. The server is processing the request.
| Code | Name | When used |
|---|---|---|
| 100 | Continue | Client should continue sending request body |
| 101 | Switching Protocols | Upgrading from HTTP to WebSocket |
| 103 | Early Hints | Send preload headers before final response |
2xx — Success
The request was received, understood, and accepted.
| Code | Name | When to use |
|---|---|---|
| 200 | OK | General success. GET, PUT, PATCH responses |
| 201 | Created | Resource was created. POST that creates a resource |
| 202 | Accepted | Request received, processing async. Job queued |
| 204 | No Content | Success, no response body. DELETE, PATCH with no body |
| 206 | Partial Content | Range request success. File download chunks |
200 vs 201 vs 204
// 200 OK — general success:
app.get('/users/:id', (req, res) => {
res.status(200).json(user);
});
// 201 Created — resource was created:
app.post('/users', (req, res) => {
const user = await createUser(req.body);
res.status(201)
.header('Location', `/users/${user.id}`) // Where to find the new resource
.json(user);
});
// 204 No Content — success, nothing to return:
app.delete('/users/:id', (req, res) => {
await deleteUser(req.params.id);
res.status(204).send(); // No body
});
// 202 Accepted — async processing:
app.post('/videos/transcode', (req, res) => {
const jobId = enqueueTranscoding(req.body.videoId);
res.status(202).json({ jobId, status: 'queued' });
});
3xx — Redirects
The client must take additional action to complete the request.
| Code | Name | When to use |
|---|---|---|
| 301 | Moved Permanently | URL changed forever. Old URL should be updated |
| 302 | Found | Temporary redirect. Keep original URL |
| 303 | See Other | After POST, redirect to GET (Post/Redirect/Get pattern) |
| 304 | Not Modified | Browser cached version is still fresh (ETag/If-None-Match) |
| 307 | Temporary Redirect | Temporary, preserve HTTP method |
| 308 | Permanent Redirect | Permanent, preserve HTTP method |
301 vs 302 vs 307 vs 308
301 Permanent:
- Old URL → New URL, forever
- Browser and search engines update bookmarks
- HTTP method may change (POST → GET is common)
302 Found:
- Temporary redirect
- Search engines keep the original URL indexed
- Common for login redirects ("you need to log in first")
307 Temporary Redirect:
- Like 302, but MUST preserve HTTP method
- POST to /checkout → 307 → POST stays as POST
308 Permanent Redirect:
- Like 301, but MUST preserve HTTP method
- POST to /api/v1/users → 308 → /api/v2/users (POST preserved)
4xx — Client Errors
The request has an error on the client side.
| Code | Name | When to use |
|---|---|---|
| 400 | Bad Request | Malformed syntax, invalid parameters |
| 401 | Unauthorized | Not authenticated (no/invalid credentials) |
| 403 | Forbidden | Authenticated but not authorized |
| 404 | Not Found | Resource doesn’t exist |
| 405 | Method Not Allowed | HTTP method not supported for this endpoint |
| 409 | Conflict | Conflict with current state (duplicate, version mismatch) |
| 410 | Gone | Resource permanently deleted (stronger than 404) |
| 422 | Unprocessable Entity | Syntactically valid but semantically invalid |
| 429 | Too Many Requests | Rate limit exceeded |
401 vs 403
401 Unauthorized: "Who are you? Prove your identity."
- Client should try again with credentials
- Sends WWW-Authenticate header with auth scheme
403 Forbidden: "I know who you are, but you can't do this."
- Client is authenticated but lacks permission
- No amount of re-authenticating will help
// 401 — not authenticated:
if (!req.headers.authorization) {
return res.status(401)
.header('WWW-Authenticate', 'Bearer')
.json({ error: 'authentication_required' });
}
// 403 — authenticated but unauthorized:
if (!user.hasPermission('admin:write')) {
return res.status(403).json({ error: 'insufficient_permissions' });
}
400 vs 422
400 Bad Request: Malformed request (JSON syntax error, wrong content-type)
422 Unprocessable Entity: Valid JSON, but data fails business validation
// 400 — malformed input:
try {
JSON.parse(req.body);
} catch {
return res.status(400).json({ error: 'invalid_json' });
}
// 422 — valid JSON, invalid data:
if (!isValidEmail(body.email)) {
return res.status(422).json({
error: 'validation_failed',
details: [{ field: 'email', message: 'Invalid email format' }]
});
}
409 Conflict
// Creating duplicate resource:
const existing = await User.findOne({ email: body.email });
if (existing) {
return res.status(409).json({
error: 'conflict',
message: 'Email already registered'
});
}
// Optimistic concurrency conflict:
if (entity.version !== body.version) {
return res.status(409).json({
error: 'version_conflict',
message: 'Resource was modified by another process'
});
}
5xx — Server Errors
Something went wrong on the server.
| Code | Name | When to use |
|---|---|---|
| 500 | Internal Server Error | Unexpected server failure |
| 501 | Not Implemented | Method or feature not implemented |
| 502 | Bad Gateway | Upstream server returned bad response |
| 503 | Service Unavailable | Temporarily down, overloaded, or in maintenance |
| 504 | Gateway Timeout | Upstream server didn’t respond in time |
502 vs 503 vs 504
502 Bad Gateway: Your reverse proxy got an invalid response from the app server
503 Service Unavailable: App server is down (restarting, deploying, overloaded)
504 Gateway Timeout: App server is up but took too long to respond
These typically indicate infrastructure issues, not application bugs.
Common mistakes
Using 200 for errors:
// WRONG: 200 with error in body — clients can't programmatically detect failure:
res.status(200).json({ success: false, error: 'Not found' });
// CORRECT: use proper status code:
res.status(404).json({ error: 'not_found' });
Using 400 for everything:
// WRONG: Using 400 for all input problems:
res.status(400).json({ error: 'User with that email exists' }); // Should be 409
// CORRECT: Use specific codes:
res.status(409).json({ error: 'email_already_exists' });
Not returning 404 for missing resources:
// WRONG: Empty 200 response:
res.status(200).json(null);
// CORRECT: 404 when resource doesn't exist:
const user = await User.findById(req.params.id);
if (!user) return res.status(404).json({ error: 'user_not_found' });
res.json(user);
Related tools
- HTTP Status Codes — searchable status code reference
- HTTP 404 Not Found — handling 404 errors
- HTTP 500 Error — debugging 500 errors
Related posts
- When to Use 422 vs 400 (and Other HTTP Status Code Debates) — 400 means the request is malformed. 422 means it's valid but semantically wrong.…
- HTTP 301 vs 302 Redirect — Permanent vs Temporary Redirects Explained — Understand when to use 301 (permanent) vs 302 (temporary) redirects, how they af…
- HTTP 404 Not Found — What It Means and How to Fix It — HTTP 404 means the server understood the request but can't find the resource. It…
- HTTP 500 Internal Server Error — What It Means and How to Debug It — HTTP 500 means the server encountered an unexpected error it couldn't handle. Un…
Related tool
Full HTTP status code reference with explanations and when to use each.
Written by Mian Ali Khalid. Part of the Dev Productivity pillar.