What Are HTTP Status Codes?
Returning 200 for everything is one of the most common API mistakes — and one of the most annoying to debug. Status codes are free information: they tell clients what happened without them having to parse a response body. A 201 for creation, 204 for deletion, 422 for bad input — use them correctly and your API becomes self-documenting.
Status codes are grouped into five classes based on their first digit. Each class has a specific meaning: informational, success, redirection, client error, or server error. Understanding the full spectrum of codes allows you to build more robust APIs, debug issues faster, and provide better error messages to your users.
1xx — Informational Responses
Informational status codes indicate that the request was received and the server is continuing to process it. These are rarely seen by developers directly.
- 100 Continue: The server has received the request headers and the client should proceed to send the request body. Used to avoid sending large bodies before knowing if the server will accept the request.
- 101 Switching Protocols: The server agrees to switch protocols as requested. Commonly seen when upgrading HTTP connections to WebSocket.
- 103 Early Hints: Primarily used with the Link header to allow browsers to start preloading resources while the server prepares the final response.
2xx — Success Codes
Success codes confirm that the request was received, understood, and accepted. These are the codes you want to return from your APIs.
| Code | Name | When to Use |
|---|---|---|
| 200 | OK | Standard success for GET, PUT, PATCH requests |
| 201 | Created | Successful POST that created a new resource |
| 202 | Accepted | Request accepted but processing not yet complete (async) |
| 204 | No Content | Success but no body to return (DELETE operations) |
| 206 | Partial Content | Partial GET response (used for file downloads/streaming) |
The most common mistake is returning 200 for everything, including errors. A successful deletion should return 204. A successful creation should return 201. Using precise codes makes your API self-documenting and easier for clients to handle correctly.
3xx — Redirection Codes
Redirection codes tell the client that further action is needed to complete the request, typically by following a different URL.
- 301 Moved Permanently: The resource has been permanently moved to a new URL. Browsers and search engines update their records. Use this for permanent URL changes.
- 302 Found: Temporary redirect. The resource is temporarily at a different URL. The client should keep using the original URL for future requests.
- 304 Not Modified: The cached version of the resource is still valid. The client can use its cached copy instead of downloading again. Critical for performance.
- 307 Temporary Redirect: Like 302, but guarantees the HTTP method won't change (a POST redirect stays a POST).
- 308 Permanent Redirect: Like 301, but guarantees the HTTP method won't change.
For SEO, always use 301 for permanent URL changes. Search engines transfer "link juice" (ranking authority) from the old URL to the new one. Using 302 tells search engines the move is temporary and they won't update their index.
4xx — Client Error Codes
Client error codes indicate that the request contained bad syntax, referenced a resource that doesn't exist, or lacked the required authentication. The problem is on the client's side.
| Code | Name | Common Cause |
|---|---|---|
| 400 | Bad Request | Malformed request syntax, invalid parameters |
| 401 | Unauthorized | Authentication required or failed |
| 403 | Forbidden | Authenticated but lacks permission |
| 404 | Not Found | Resource doesn't exist at this URL |
| 405 | Method Not Allowed | HTTP method not supported for this endpoint |
| 409 | Conflict | Request conflicts with current state (duplicate entry) |
| 410 | Gone | Resource permanently deleted (stronger than 404) |
| 422 | Unprocessable Entity | Syntax valid but semantically wrong (validation errors) |
| 429 | Too Many Requests | Rate limit exceeded |
401 vs 403: A Common Confusion
Developers frequently confuse these two codes. 401 Unauthorized means the client must authenticate itself to get the requested response — the user isn't logged in or their token is invalid. 403 Forbidden means the client's identity is known but they don't have permission — the user is logged in but doesn't have the right role.
A practical example: A logged-out user trying to access a dashboard gets 401. A logged-in standard user trying to access an admin-only page gets 403.
5xx — Server Error Codes
Server error codes indicate that the server encountered an unexpected condition that prevented it from fulfilling the request. Unlike 4xx errors, the client did nothing wrong — the problem is on the server's side.
| Code | Name | Meaning |
|---|---|---|
| 500 | Internal Server Error | Generic catch-all for unhandled server errors |
| 501 | Not Implemented | Server doesn't support the functionality |
| 502 | Bad Gateway | Upstream server returned an invalid response |
| 503 | Service Unavailable | Server temporarily down (overload or maintenance) |
| 504 | Gateway Timeout | Upstream server didn't respond in time |
A 502 or 504 often indicates a problem with your reverse proxy (like Nginx) failing to reach your application server. A 503 is often returned intentionally during maintenance windows. Always include a Retry-After header with 503 responses so clients know when to try again.
Best Practices for API Design
Be Specific, Not Generic
Avoid returning 200 for everything and encoding the error in the response body. This forces clients to parse every response body to determine success, breaking conventional HTTP semantics. Use the right status code — it's free information that clients can use without parsing JSON.
Always Include a Response Body on Errors
For 4xx and 5xx errors, always return a JSON body explaining what went wrong. A good error response includes the error code, a human-readable message, and optionally a link to documentation. Never return a bare 400 with no explanation.
{
"error": "validation_failed",
"message": "Email address is invalid",
"field": "email",
"docs": "https://api.example.com/docs/errors"
}Use 422 for Validation Errors
When a request is syntactically valid JSON but contains invalid values (e.g., an email field that isn't an email address), return 422 rather than 400. This distinction helps clients understand they need to fix their data, not their request format.
Return 429 with Retry-After
When rate limiting, always include the Retry-After header so clients know when they can try again. This prevents clients from hammering your server in a retry loop and helps them implement proper backoff strategies.
Handling Status Codes in JavaScript
// Using fetch API with proper status handling async function fetchUser(id) { const response = await fetch(`/api/users/${id}`); if (response.status === 404) { throw new Error('User not found'); } if (response.status === 401) { redirectToLogin(); return; } if (response.status === 429) { const retryAfter = response.headers.get('Retry-After'); throw new Error(`Rate limited. Try again in ${retryAfter}s`); } if (!response.ok) { // Catches all 4xx and 5xx not handled above const error = await response.json(); throw new Error(error.message); } return response.json(); }
Conclusion
HTTP status codes are a rich vocabulary for communicating between clients and servers. Using them correctly makes your APIs more intuitive, easier to debug, and better aligned with web standards. The key is being specific: use 201 for creation, 204 for successful deletion, 422 for validation failures, and 429 for rate limiting. Reserve 500 for truly unexpected errors, and always include helpful error messages in your response bodies.
Take time to review the APIs you build and consume — using the right status code at the right time is one of the simplest ways to make your APIs feel professional and well-designed.