HTTP status codes tell clients what happened with their request. Choosing the right code improves API usability, debugging, and SEO. This guide covers every status code category with practical usage examples.
Key Takeaways
- 12xx = success (200 OK, 201 Created, 204 No Content)
- 23xx = redirect (301 permanent, 302 temporary, 304 cached)
- 34xx = client error (400 bad request, 401 unauthenticated, 403 forbidden, 404 not found)
- 45xx = server error (500 internal, 502 bad gateway, 503 unavailable)
- 5401 means ’who are you?’ (login required); 403 means ’you can’t do that’ (no permission)
1Status Code Categories
HTTP status codes are grouped into five categories based on the first digit. Each category indicates a different type of response.
| Range | Category | Meaning |
|---|---|---|
| 1xx | Informational | Request received, continuing process |
| 2xx | Success | Request successfully received and processed |
| 3xx | Redirection | Further action needed to complete request |
| 4xx | Client Error | Request contains bad syntax or cannot be fulfilled |
| 5xx | Server Error | Server failed to fulfill valid request |
The most common codes you\
22xx Success Codes
Success codes indicate the request was received, understood, and accepted. Choose the most specific code for your response.
| Code | Name | When to Use |
|---|---|---|
| 200 | OK | Standard success; response has body (GET, POST with result) |
| 201 | Created | Resource created; include Location header with new resource URL |
| 202 | Accepted | Request accepted for processing, but not completed (async jobs) |
| 204 | No Content | Success with no response body (DELETE, PUT with no return) |
| 206 | Partial Content | Range request fulfilled (video streaming, large file downloads) |
// Express.js examples
// 200 OK - Standard response with body
app.get('/users/:id', (req, res) => {
const user = findUser(req.params.id);
res.status(200).json(user);
});
// 201 Created - Resource created
app.post('/users', (req, res) => {
const user = createUser(req.body);
res.status(201)
.location(`/users/${user.id}`)
.json(user);
});
// 204 No Content - Success, no body needed
app.delete('/users/:id', (req, res) => {
deleteUser(req.params.id);
res.status(204).send();
});Use 201 for POST that creates a resource, 200 for POST that performs an action without creating. Use 204 when clients don\
33xx Redirection Codes
Redirection codes tell clients to look elsewhere. The distinction between permanent and temporary affects caching and SEO.
| Code | Name | When to Use |
|---|---|---|
| 301 | Moved Permanently | URL changed forever; browsers cache and update bookmarks; SEO passes link equity |
| 302 | Found | Temporary redirect; don't cache; original URL may return |
| 303 | See Other | Redirect to GET a different resource (after POST form submission) |
| 304 | Not Modified | Cached version is still valid (conditional GET with ETag/If-Modified-Since) |
| 307 | Temporary Redirect | Like 302 but preserves HTTP method (POST stays POST) |
| 308 | Permanent Redirect | Like 301 but preserves HTTP method |
// 301 - Permanent redirect (URL change, SEO migration)
app.get('/old-page', (req, res) => {
res.redirect(301, '/new-page');
});
// 302 - Temporary redirect (A/B test, maintenance)
app.get('/promo', (req, res) => {
res.redirect(302, '/current-sale');
});
// 303 - See Other (redirect after POST)
app.post('/orders', (req, res) => {
const order = createOrder(req.body);
// Redirect to GET the confirmation page
res.redirect(303, `/orders/${order.id}/confirmation`);
});
// 304 - Not Modified (caching)
app.get('/api/data', (req, res) => {
const etag = '"abc123"';
if (req.headers['if-none-match'] === etag) {
return res.status(304).send();
}
res.set('ETag', etag).json(data);
});301 redirects are cached aggressively by browsers. If you accidentally 301 to the wrong URL, users may be stuck until they clear cache. Use 302 during testing.
44xx Client Error Codes
Client errors indicate problems with the request. These help clients understand what they did wrong.
| Code | Name | When to Use |
|---|---|---|
| 400 | Bad Request | Malformed syntax, invalid parameters, validation failed |
| 401 | Unauthorized | Authentication required or credentials invalid |
| 403 | Forbidden | Authenticated but not authorized for this resource |
| 404 | Not Found | Resource doesn't exist at this URL |
| 405 | Method Not Allowed | HTTP method not supported (GET on POST-only endpoint) |
| 409 | Conflict | Request conflicts with current state (duplicate, version mismatch) |
| 410 | Gone | Resource permanently deleted (unlike 404, we know it existed) |
| 415 | Unsupported Media Type | Content-Type not supported (JSON sent to XML-only endpoint) |
| 422 | Unprocessable Entity | Syntax correct but semantically invalid (valid JSON, invalid data) |
| 429 | Too Many Requests | Rate limit exceeded; include Retry-After header |
// 400 - Bad Request (validation error)
app.post('/users', (req, res) => {
if (!req.body.email) {
return res.status(400).json({
error: 'Bad Request',
message: 'Email is required',
field: 'email'
});
}
});
// 401 - Unauthorized (no/invalid auth)
app.get('/profile', (req, res) => {
if (!req.headers.authorization) {
return res.status(401)
.set('WWW-Authenticate', 'Bearer')
.json({ error: 'Authentication required' });
}
});
// 403 - Forbidden (authenticated but not allowed)
app.delete('/users/:id', (req, res) => {
if (!req.user.isAdmin) {
return res.status(403).json({
error: 'Forbidden',
message: 'Admin access required'
});
}
});
// 404 - Not Found
app.get('/users/:id', (req, res) => {
const user = findUser(req.params.id);
if (!user) {
return res.status(404).json({
error: 'Not Found',
message: `User ${req.params.id} not found`
});
}
});
// 429 - Rate Limited
app.use((req, res, next) => {
if (isRateLimited(req)) {
return res.status(429)
.set('Retry-After', '60')
.json({ error: 'Too many requests. Try again in 60 seconds.' });
}
next();
});401 vs 403: Use 401 when identity is unknown (not logged in or invalid token). Use 403 when identity is known but lacks permission. 401 says "who are you?"; 403 says "I know who you are, but no."
55xx Server Error Codes
Server errors indicate problems on the server side. The client\'s request may be valid, but the server couldn\'t fulfill it.
| Code | Name | When to Use |
|---|---|---|
| 500 | Internal Server Error | Unhandled exception, unexpected error (catch-all) |
| 501 | Not Implemented | Server doesn't support the requested functionality |
| 502 | Bad Gateway | Upstream server returned invalid response (proxy/load balancer) |
| 503 | Service Unavailable | Server temporarily down (maintenance, overload) |
| 504 | Gateway Timeout | Upstream server didn't respond in time |
// Global error handler
app.use((err, req, res, next) => {
console.error('Unhandled error:', err);
// Don't leak error details in production
const message = process.env.NODE_ENV === 'production'
? 'Internal server error'
: err.message;
res.status(500).json({
error: 'Internal Server Error',
message,
requestId: req.id // For debugging
});
});
// 503 - Maintenance mode
app.use((req, res, next) => {
if (isMaintenanceMode()) {
return res.status(503)
.set('Retry-After', '3600')
.json({
error: 'Service Unavailable',
message: 'Server is under maintenance. Please try again later.'
});
}
next();
});Never expose stack traces, database errors, or internal paths in 5xx responses to production clients. Log the full error server-side and return a generic message with a request ID for debugging.
6REST API Best Practices
Consistent status code usage makes APIs predictable and easier to consume. Follow these conventions.
| Operation | Success | Common Errors |
|---|---|---|
| GET /items | 200 | 401, 403, 404 |
| GET /items/:id | 200 | 401, 403, 404 |
| POST /items | 201 | 400, 401, 403, 409 |
| PUT /items/:id | 200 or 204 | 400, 401, 403, 404 |
| PATCH /items/:id | 200 or 204 | 400, 401, 403, 404 |
| DELETE /items/:id | 204 | 401, 403, 404 |
- Return consistent error response format: { error, message, details }
- Include Content-Type: application/json for JSON responses
- Use 400 for client input validation errors, 422 for business logic validation
- Always return 401 with WWW-Authenticate header for auth challenges
- Include Retry-After header with 429 and 503 responses
- Use 404 for missing resources, not empty arrays (return 200 with [])
- Return 201 with Location header for created resources
- Log all 5xx errors with request context for debugging
71xx Informational Codes
Informational codes are provisional responses. You\'ll rarely use these directly, but they\'re important in specific scenarios.
| Code | Name | When Used |
|---|---|---|
| 100 | Continue | Server received headers, client can send body (Expect: 100-continue) |
| 101 | Switching Protocols | Server switching to different protocol (HTTP → WebSocket) |
| 102 | Processing | Server is processing, response coming (WebDAV) |
| 103 | Early Hints | Preload resources while server prepares response |
// 101 - WebSocket upgrade
// Happens automatically with ws library
const WebSocket = require('ws');
const wss = new WebSocket.Server({ server });
// Client sends: Upgrade: websocket
// Server responds: 101 Switching Protocols
// 103 - Early Hints (Node.js 18+)
app.get('/page', (req, res) => {
res.writeEarlyHints({
link: '</style.css>; rel=preload; as=style'
});
// Continue processing...
res.render('page');
});Most developers never send 1xx codes directly. They\
8Common Mistakes to Avoid
These patterns seem logical but confuse clients and break conventions.
- Using 200 for everything with { success: false } in body—use proper error codes
- Returning 500 for validation errors—these are 400 (client errors)
- Using 403 when 401 is correct—unauthenticated vs unauthorized
- Returning 404 for empty search results—use 200 with empty array
- Using 302 for permanent URL changes—use 301 for SEO
- Returning 204 when clients expect a response body—use 200
- Generic 400 for all validation errors—include field-level details
- Missing Content-Type header on JSON responses
// ❌ Bad: 200 with error in body
res.status(200).json({ success: false, error: 'Not found' });
// ✅ Good: Proper status code
res.status(404).json({ error: 'Not Found' });
// ❌ Bad: 404 for empty results
if (results.length === 0) {
return res.status(404).json({ error: 'No results' });
}
// ✅ Good: 200 with empty array
res.status(200).json({ results: [], total: 0 });
// ❌ Bad: 500 for validation error
if (!isValid(data)) {
return res.status(500).json({ error: 'Invalid data' });
}
// ✅ Good: 400 for client error
res.status(400).json({ error: 'Validation failed', details });Boost Your Developer Workflow
Free online tools for encoding, formatting, hashing, and more.
Explore Dev ToolsFrequently Asked Questions
What is the difference between 401 and 403?
401 Unauthorized means authentication is required but missing or invalid—the server doesn’t know who you are. 403 Forbidden means authentication succeeded but you lack permission—the server knows who you are but won’t allow access. Use 401 for ’please log in’ and 403 for ’you can’t do that.’
When should I use 404 vs 410?
404 Not Found means the resource doesn’t exist at this URL—it may never have existed or may exist elsewhere. 410 Gone means the resource existed but was intentionally deleted and won’t return. Use 410 for removed content to help search engines de-index faster.
Should I use 400 or 422 for validation errors?
400 Bad Request is for malformed syntax (unparseable JSON, missing required header). 422 Unprocessable Entity is for semantically invalid data (valid JSON but invalid email format). Many APIs use 400 for both since 422 is less widely known. Be consistent within your API.
What status code should I return for rate limiting?
Use 429 Too Many Requests with a Retry-After header indicating when the client can try again. The value can be seconds (Retry-After: 60) or an HTTP date. Include the limit and remaining count in response headers (X-RateLimit-Limit, X-RateLimit-Remaining).
Do status codes affect SEO?
Yes significantly. 200 pages are indexed normally. 301 passes ~90% link equity to the new URL. 302 preserves original URL in index (temporary). 404/410 remove pages from index (410 faster). 503 tells crawlers to retry later. Using wrong codes can hurt rankings or cause de-indexing.