What are HTTP Status Codes?
Every HTTP response includes a three-digit status code. The first digit tells you the category, the other two get specific.
const response = await fetch("/api/users/123");
console.log(response.status); // 200
console.log(response.ok); // true (status 200-299)
If you've ever seen a 404 page, you've seen a status code in the wild.
The Five Categories
| Range | Category | Meaning |
|---|---|---|
| 1xx | Informational | Hold on, still processing |
| 2xx | Success | It worked |
| 3xx | Redirection | Go somewhere else |
| 4xx | Client Error | You messed up |
| 5xx | Server Error | We messed up |
Success Codes (2xx)
The happy path. Your request worked.
| Code | Name | When to Use |
|---|---|---|
| 200 | OK | Standard success (GET, PUT, PATCH) |
| 201 | Created | Resource created (POST) |
| 202 | Accepted | Request received, processing later |
| 204 | No Content | Success but nothing to return (DELETE) |
// Handling success codes
async function createUser(data) {
const res = await fetch("/api/users", {
method: "POST",
body: JSON.stringify(data)
});
if (res.status === 201) {
const user = await res.json();
console.log("Created:", user.id);
}
}
Redirection Codes (3xx)
The resource moved. Follow the Location header.
| Code | Name | When to Use |
|---|---|---|
| 301 | Moved Permanently | URL changed forever, update bookmarks |
| 302 | Found | Temporary redirect |
| 304 | Not Modified | Use your cached version |
| 307 | Temporary Redirect | Same as 302 but keep the method |
| 308 | Permanent Redirect | Same as 301 but keep the method |
With 301/302, browsers may change POST to GET on redirect. Use 307/308 when you need to preserve the original HTTP method.
Client Error Codes (4xx)
Something's wrong with the request. Fix it on your end.
| Code | Name | Meaning |
|---|---|---|
| 400 | Bad Request | Malformed syntax, invalid JSON |
| 401 | Unauthorized | No auth or bad credentials |
| 403 | Forbidden | Valid auth but not allowed |
| 404 | Not Found | Resource doesn't exist |
| 405 | Method Not Allowed | Wrong HTTP verb |
| 409 | Conflict | Request conflicts with current state |
| 410 | Gone | Resource existed but was deleted |
| 422 | Unprocessable Entity | Validation failed |
| 429 | Too Many Requests | Rate limited, slow down |
// Handling client errors
async function getUser(id) {
const res = await fetch(`/api/users/${id}`);
switch (res.status) {
case 200:
return res.json();
case 401:
throw new Error("Please log in");
case 403:
throw new Error("You don't have permission");
case 404:
return null; // User doesn't exist
case 429:
throw new Error("Too many requests, try again later");
default:
throw new Error(`Request failed: ${res.status}`);
}
}
401 vs 403
| Code | Auth Present? | Permission? | Example |
|---|---|---|---|
| 401 | No/Invalid | N/A | Missing or expired token |
| 403 | Yes, valid | No | User trying to access admin panel |
Server Error Codes (5xx)
The server broke. Not your fault (usually).
| Code | Name | Meaning |
|---|---|---|
| 500 | Internal Server Error | Generic "something broke" |
| 502 | Bad Gateway | Upstream server sent bad response |
| 503 | Service Unavailable | Server is overloaded or down |
| 504 | Gateway Timeout | Upstream server didn't respond in time |
// Handling server errors with retry
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
const res = await fetch(url);
if (res.status >= 500) {
// Server error - wait and retry
await new Promise(r => setTimeout(r, 1000 * (i + 1)));
continue;
}
return res;
}
throw new Error("Server unavailable after retries");
}
Quick Reference Table
| Code | Name | Remember It As |
|---|---|---|
| 200 | OK | "Here's your stuff" |
| 201 | Created | "Made it for you" |
| 204 | No Content | "Done, nothing to say" |
| 301 | Moved Permanently | "We moved, update your links" |
| 304 | Not Modified | "Use your cache" |
| 400 | Bad Request | "I can't understand you" |
| 401 | Unauthorized | "Who are you?" |
| 403 | Forbidden | "I know you, but no" |
| 404 | Not Found | "Doesn't exist" |
| 409 | Conflict | "Can't do that right now" |
| 422 | Unprocessable | "I understand but won't do it" |
| 429 | Too Many Requests | "Calm down" |
| 500 | Internal Server Error | "We broke" |
| 502 | Bad Gateway | "Upstream broke" |
| 503 | Service Unavailable | "Try again later" |
Common Gotchas
Some APIs return 200 with an error in the JSON body. Always check both the status code AND the response body. { "status": 200, "error": "Not found" } is poor API design, but it exists.
A 404 means "this resource doesn't exist." A 204 means "success, but nothing to return." Don't use 404 for successful DELETE operations.
- response.ok only checks 2xx - In JavaScript fetch,
response.okis true for 200-299 only. A 304 redirect returnsok: false. - Browsers hide 401/403 details - For security, browsers may not expose the full error. Check network tab for details.
- Rate limit headers - When you get 429, check
Retry-Afterheader to know how long to wait. - Don't use 200 for errors - If validation fails, use 400 or 422, not 200 with an error message.
- 503 means "come back later" - If you see 503, the server is overwhelmed. Implementing exponential backoff is polite.
Try It
Format API Error Responses"HTTP status codes: where 200 means success, 404 means missing, and 418 means someone at the IETF had a sense of humor."