Loading content...
When a server receives an HTTP request, it must communicate not just what it's returning, but how the request was processed. Did it succeed? Did something go wrong? Should the client try somewhere else? This communication happens through HTTP status codes—a three-digit numeric vocabulary that has evolved over three decades of web development.
Status codes are the first line of every HTTP response:
HTTP/1.1 200 OK
HTTP/1.1 404 Not Found
HTTP/1.1 500 Internal Server Error
The first digit indicates the category of response, and the remaining two digits provide specificity within that category. Understanding status codes is essential for building reliable APIs, debugging web applications, and implementing proper error handling.
By the end of this page, you will: • Understand all five status code categories (1xx-5xx) • Master the most important codes in each category • Know when to use each status code in API design • Handle status codes correctly in client applications • Debug HTTP issues using status code semantics
HTTP status codes follow a systematic structure defined by RFC 9110. The three-digit code consists of:
The reason phrase (e.g., "OK", "Not Found") is human-readable but has no semantic meaning—clients should not parse it.
| Class | Range | Category | Description |
|---|---|---|---|
| 1xx | 100-199 | Informational | Request received, processing continues |
| 2xx | 200-299 | Success | Request received, understood, accepted |
| 3xx | 300-399 | Redirection | Further action needed to complete request |
| 4xx | 400-499 | Client Error | Request contains error or cannot be fulfilled |
| 5xx | 500-599 | Server Error | Server failed to fulfill valid request |
The category structure enables generic error handling. A client that doesn't recognize a specific code can fall back to category-level behavior:
418 I'm a teapot → Unknown, but 4xx → Client error → Fix the request
507 Insufficient Storage → Unknown, but 5xx → Server error → Retry later
HTTP/1.1 404 Not Found
├──────┘ └─┘ └────────┘
│ │ └── Reason Phrase (informational only)
│ └── Status Code (machine-readable)
└── HTTP Version
Important: In HTTP/2 and HTTP/3, the reason phrase is not transmitted—only the status code. Design your systems to rely solely on the numeric code.
1xx codes are interim responses indicating that the server has received the request and is continuing to process it. These are rare in typical web browsing but important in advanced scenarios.
| Code | Name | Use Case |
|---|---|---|
| 100 | Continue | Server received headers; client should send body |
| 101 | Switching Protocols | Server agrees to protocol change (e.g., WebSocket upgrade) |
| 102 | Processing (WebDAV) | Server is processing but no response yet available |
| 103 | Early Hints | Server sending preliminary headers before final response |
When a client wants to send a large request body, it can first send:
POST /upload HTTP/1.1
Host: example.com
Content-Length: 10485760
Expect: 100-continue
(No body yet)
The server can respond:
100 Continue → "Send the body, I'll accept it"417 Expectation Failed → "Don't send; I'll reject it anyway"This prevents wasting bandwidth on uploads that would be rejected (e.g., file too large, unauthorized).
Used when upgrading from HTTP to WebSocket:
# Client Request
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
# Server Response
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
After this exchange, the connection switches to WebSocket protocol.
A newer code (RFC 8297) that allows servers to send headers before the final response:
HTTP/1.1 103 Early Hints
Link: </styles.css>; rel=preload; as=style
Link: </script.js>; rel=preload; as=script
(Server continues processing...)
HTTP/1.1 200 OK
Content-Type: text/html
...
Browsers can start preloading resources while waiting for the final response.
Most web developers rarely encounter 1xx codes directly because:
• HTTP libraries handle 100 Continue automatically • WebSocket libraries abstract 101 Switching Protocols • 103 Early Hints is relatively new and server-specific
However, understanding them is crucial for debugging low-level HTTP issues.
2xx codes indicate successful request processing. The specific code communicates how the request succeeded, which affects client behavior regarding caching, redirects, and data handling.
| Code | Name | When to Use |
|---|---|---|
| 200 | OK | Standard success response with body |
| 201 | Created | Resource successfully created (include Location) |
| 202 | Accepted | Request accepted for async processing |
| 203 | Non-Authoritative Information | Transformed by proxy |
| 204 | No Content | Success but no body to return |
| 205 | Reset Content | Success; client should reset document view |
| 206 | Partial Content | Range request fulfilled |
| 207 | Multi-Status (WebDAV) | Multiple operations with mixed results |
| 208 | Already Reported (WebDAV) | Members already enumerated |
| 226 | IM Used | Delta encoding applied |
The default success code. Request received, understood, and processed:
HTTP/1.1 200 OK
Content-Type: application/json
{"status": "success", "data": {...}}
Use for: GET/POST/PUT/PATCH/DELETE that succeed and have a response body.
A new resource was created as a result of the request:
POST /api/users HTTP/1.1
{"name": "Alice"}
HTTP/1.1 201 Created
Location: /api/users/12345
Content-Type: application/json
{"id": 12345, "name": "Alice", "createdAt": "2025-01-18T12:00:00Z"}
Key requirements:
Location header pointing to new resourceRequest accepted but not yet processed—ideal for async operations:
POST /api/reports/generate HTTP/1.1
{"type": "annual", "year": 2024}
HTTP/1.1 202 Accepted
Location: /api/reports/jobs/abc123
Content-Type: application/json
{
"jobId": "abc123",
"status": "queued",
"statusUrl": "/api/reports/jobs/abc123",
"estimatedCompletion": "2025-01-18T12:10:00Z"
}
Clients should poll the status URL or use webhooks.
Success with no response body needed:
DELETE /api/users/12345 HTTP/1.1
HTTP/1.1 204 No Content
Common uses:
Important: 204 responses MUST NOT include a body. The Content-Length should be 0 or absent.
Used with range requests—essential for video streaming and download resumption:
GET /video.mp4 HTTP/1.1
Range: bytes=1000-1999
HTTP/1.1 206 Partial Content
Content-Type: video/mp4
Content-Range: bytes 1000-1999/1000000
Content-Length: 1000
(1000 bytes of video data)
This enables:
200 OK: Default for GET, and for POST/PUT/PATCH/DELETE when returning data
201 Created: POST that creates a new resource (not updating existing)
204 No Content: DELETE, or any operation where the client doesn't need response data
Why it matters: Some HTTP libraries treat 204 as success without trying to parse a body. Returning 200 with an empty body may cause parsing errors in some clients.
3xx codes indicate that the client must take additional action to complete the request—typically following a redirect to a different URL.
| Code | Name | Method Preservation | Use Case |
|---|---|---|---|
| 300 | Multiple Choices | N/A | Multiple options; client chooses |
| 301 | Moved Permanently | May change to GET | Permanent URL change (SEO-friendly) |
| 302 | Found | May change to GET | Temporary redirect (historical) |
| 303 | See Other | Always GET | Redirect after POST (PRG pattern) |
| 304 | Not Modified | N/A | Resource unchanged; use cache |
| 307 | Temporary Redirect | Preserved | Temporary; must keep method |
| 308 | Permanent Redirect | Preserved | Permanent; must keep method |
The resource has permanently moved to a new URL:
GET /old-page HTTP/1.1
HTTP/1.1 301 Moved Permanently
Location: https://example.com/new-page
Implications:
Caveat: Historically, browsers would change POST to GET on 301. Use 308 if method preservation is required.
The resource is temporarily at a different URL:
GET /seasonal-sale HTTP/1.1
HTTP/1.1 302 Found
Location: /winter-sale-2025
Implications:
Historical note: 302 originally meant "keep the method," but browsers incorrectly changed POST to GET. 307 was created to fix this.
After processing a POST, redirect to a GET-able result:
POST /orders HTTP/1.1
{"items": [...]}
HTTP/1.1 303 See Other
Location: /orders/12345
This implements the Post/Redirect/Get (PRG) pattern, preventing duplicate form submissions on refresh.
Used with conditional requests to indicate cached content is still valid:
GET /resource HTTP/1.1
If-None-Match: "etag-abc123"
HTTP/1.1 304 Not Modified
ETag: "etag-abc123"
Cache-Control: max-age=3600
Key points:
Need to redirect?
├── Permanent change?
│ ├── Yes → Method preservation needed?
│ │ ├── Yes → 308 Permanent Redirect
│ │ └── No → 301 Moved Permanently
│ └── No → Method preservation needed?
│ ├── Yes → 307 Temporary Redirect
│ └── No → After POST? → 303 See Other
│ Otherwise → 302 Found
A redirect loop occurs when A redirects to B, and B redirects back to A (directly or through a chain). Browsers typically detect this after 20-50 redirects and display an error.
Common causes: • Misconfigured HTTP→HTTPS redirects • CDN/origin misconfiguration • Cookie-based redirects for logged-out users
Always test redirect changes carefully!
4xx codes indicate the client sent a request the server cannot or will not process. These are client errors—the solution requires the client to change something.
| Code | Name | Meaning |
|---|---|---|
| 400 | Bad Request | Malformed request syntax or invalid data |
| 401 | Unauthorized | Authentication required or failed |
| 403 | Forbidden | Authenticated but not authorized |
| 404 | Not Found | Resource does not exist |
| 405 | Method Not Allowed | HTTP method not supported for this resource |
| 406 | Not Acceptable | Cannot satisfy Accept headers |
| 409 | Conflict | Request conflicts with current state |
| 410 | Gone | Resource permanently removed |
| 415 | Unsupported Media Type | Content-Type not supported |
| 422 | Unprocessable Entity | Semantically invalid (WebDAV, commonly used) |
| 429 | Too Many Requests | Rate limit exceeded |
The generic client error—the request is malformed or invalid:
POST /api/users HTTP/1.1
Content-Type: application/json
{invalid json
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "Bad Request",
"message": "Invalid JSON in request body",
"details": {
"line": 1,
"column": 2,
"expected": "property name"
}
}
Use for:
This distinction confuses many developers:
401 Unauthorized: "Who are you?" — Authentication missing or failed
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api"
{"error": "Authentication required"}
Client should provide/fix credentials.
403 Forbidden: "I know who you are, but no." — Authenticated but not authorized
HTTP/1.1 403 Forbidden
{"error": "You do not have permission to access this resource"}
Re-authenticating won't help; user needs different permissions.
403 also used for: Requests that are always forbidden regardless of authentication (e.g., accessing an internal-only endpoint from public internet).
The most famous HTTP status code—resource doesn't exist:
GET /api/users/99999999 HTTP/1.1
HTTP/1.1 404 Not Found
{"error": "User not found"}
Security consideration: Sometimes you should return 404 instead of 403 to avoid revealing that a resource exists. Example: checking if a username is taken during registration.
The request conflicts with the current state of the resource:
# Optimistic locking failure
PUT /api/documents/123 HTTP/1.1
If-Match: "old-etag"
HTTP/1.1 409 Conflict
{"error": "Document has been modified by another user"}
Use for:
The request is syntactically correct but semantically invalid:
POST /api/users HTTP/1.1
{"email": "not-an-email", "age": -5}
HTTP/1.1 422 Unprocessable Entity
{
"error": "Validation failed",
"details": [
{"field": "email", "message": "Invalid email format"},
{"field": "age", "message": "Must be a positive number"}
]
}
400 vs 422:
When rate limiting, always include Retry-After header:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705590000
{"error": "Rate limit exceeded. Retry after 60 seconds."}
Clients should respect Retry-After to avoid worsening the situation.
| Code | Name | Use Case |
|---|---|---|
| 405 | Method Not Allowed | Server should include Allow header with valid methods |
| 406 | Not Acceptable | Can't satisfy Accept headers (content negotiation failure) |
| 408 | Request Timeout | Client took too long to send the request |
| 410 | Gone | Resource existed but permanently removed (stronger than 404) |
| 411 | Length Required | Server requires Content-Length header |
| 412 | Precondition Failed | If-Match, If-Unmodified-Since failed |
| 413 | Payload Too Large | Request body exceeds server limits |
| 414 | URI Too Long | URL exceeds server limits (maybe use POST?) |
| 415 | Unsupported Media Type | Server doesn't support the Content-Type |
| 418 | I'm a teapot | April Fools' joke; do not use in production |
| 451 | Unavailable For Legal Reasons | Censorship/legal block (named after Fahrenheit 451) |
5xx codes indicate the server failed to fulfill a valid request. The problem is on the server side—the client request was fine. These are the errors that wake operations teams at 3 AM.
| Code | Name | Meaning |
|---|---|---|
| 500 | Internal Server Error | Generic server-side failure |
| 501 | Not Implemented | Server doesn't support this functionality |
| 502 | Bad Gateway | Upstream server returned invalid response |
| 503 | Service Unavailable | Server temporarily overloaded or down |
| 504 | Gateway Timeout | Upstream server didn't respond in time |
| 505 | HTTP Version Not Supported | Server doesn't support HTTP version |
| 507 | Insufficient Storage (WebDAV) | Server storage exhausted |
| 508 | Loop Detected (WebDAV) | Infinite loop detected |
| 511 | Network Authentication Required | Captive portal (hotel WiFi, etc.) |
The catch-all for server-side failures:
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
{
"error": "Internal Server Error",
"requestId": "abc123",
"message": "An unexpected error occurred"
}
Best practices:
A reverse proxy/load balancer couldn't get a valid response from upstream:
[Client] → [Nginx] → [App Server]
↓
App Server returns:
- Malformed response
- Connection reset
- Protocol error
↓
Nginx returns 502 to client
Common causes:
Server is temporarily unable to handle requests:
HTTP/1.1 503 Service Unavailable
Retry-After: 300
{"error": "Service temporarily unavailable", "retryAfter": 300}
Use for:
Important: Include Retry-After header so clients know when to retry.
The gateway didn't receive a response from upstream in time:
[Client] → [Load Balancer] → [App Server]
↓
Waits 60 seconds...
No response from App Server
↓
Returns 504 to client
Common causes:
| Code | Problem | Solution |
|---|---|---|
| 502 | Upstream returned garbage | Fix upstream app |
| 503 | Server overwhelmed or down | Wait and retry |
| 504 | Upstream is too slow | Increase timeout or optimize app |
Server-side:
Client-side:
Don't return 500 for client errors:
Wrong:
// Status: 500
{"error": "Invalid email format"}
Right:
// Status: 400 or 422
{"error": "Invalid email format", "field": "email"}
Returning 500 for validation errors confuses monitoring (real server issues get lost in noise), breaks client retry logic (shouldn't retry client errors), and misrepresents the problem.
Choosing the right status code is a design decision. This section provides quick reference guides for common scenarios.
| Operation | Success | Not Found | Error |
|---|---|---|---|
| GET resource | 200 OK | 404 Not Found | 400/500 |
| POST create | 201 Created | N/A | 400/409/422 |
| POST action | 200 OK / 202 Accepted | 404 Not Found | 400/422/500 |
| PUT update | 200 OK / 204 No Content | 404 Not Found | 400/409/422 |
| PUT create | 201 Created | N/A | 400/409 |
| PATCH update | 200 OK | 404 Not Found | 400/409/422 |
| DELETE | 200 / 204 / 202 | 404 (optional) | 409 |
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
function chooseStatusCode(request, result) { // Success cases if (result.success) { if (request.method === 'POST' && result.resourceCreated) { return 201; // Created } if (result.asyncProcessing) { return 202; // Accepted } if (result.noContent || request.method === 'DELETE') { return 204; // No Content } return 200; // OK } // Client errors if (result.validationError) { return result.syntaxError ? 400 : 422; } if (result.authenticationRequired) { return 401; // Unauthorized } if (result.permissionDenied) { return 403; // Forbidden } if (result.resourceNotFound) { return 404; // Not Found } if (result.conflict) { return 409; // Conflict } if (result.rateLimited) { return 429; // Too Many Requests } // Server errors if (result.serverError) { if (result.upstreamError) { return 502; // Bad Gateway } if (result.overloaded) { return 503; // Service Unavailable } if (result.upstreamTimeout) { return 504; // Gateway Timeout } return 500; // Internal Server Error }}HTTP status codes provide a standardized vocabulary for server responses. Let's consolidate the essential knowledge:
You now have comprehensive knowledge of HTTP status codes across all categories. You can select appropriate codes for any API scenario and handle them correctly in client applications. In the next page, we'll explore Content-Type and media types—the system that defines how HTTP payloads are encoded and interpreted.