Every HTTP response carries a three-digit status code that tells the client exactly what happened on the server. Whether you're building a REST API, debugging a 503 on production, or just trying to understand why your fetch call failed, knowing your status codes is non-negotiable.
This guide covers all five status code families — 1xx through 5xx — with plain-English explanations, real-world HTTP response examples, and practical debugging tips for every major code. You'll also find a section at the end on how to choose the right status code when designing your own API.
If you want to look up a code quickly without reading the whole article, jump straight to DevToolkit's HTTP Status Codes tool for an instant searchable reference.
How HTTP Status Codes Are Structured
HTTP status codes are three-digit integers grouped into five classes based on their first digit:
- 1xx — Informational: The request was received and processing is continuing.
- 2xx — Success: The request was successfully received, understood, and accepted.
- 3xx — Redirection: Further action is needed to complete the request.
- 4xx — Client Error: The request contains bad syntax or cannot be fulfilled by the server.
- 5xx — Server Error: The server failed to fulfill an apparently valid request.
Every status code is accompanied by a short reason phrase (e.g., 200 OK, 404 Not Found), though HTTP/2 dropped the reason phrase from the wire format. What matters is always the numeric code.
1xx — Informational Responses
1xx codes are provisional responses. They tell the client that the request is being processed and more is coming. You'll rarely handle these manually; they're mostly handled transparently by HTTP clients and servers.
100 Continue
The server has received the request headers and the client should proceed to send the request body. Clients sending large payloads (like file uploads) can send an Expect: 100-continue header first to confirm the server will accept the body before actually transmitting it. This prevents wasting bandwidth uploading a large file only to receive a 413 rejection.
POST /api/upload HTTP/1.1
Host: example.com
Content-Length: 5242880
Expect: 100-continue
HTTP/1.1 100 Continue
[client now sends the file body] 101 Switching Protocols
The server agrees to switch protocols as requested by the client via an Upgrade header. This is the handshake you see at the start of every WebSocket connection — the client upgrades from HTTP/1.1 to the WebSocket protocol.
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Debugging tip: If your WebSocket connection fails to establish, verify the server returns 101 and not 400 or 426. A 400 usually means a missing or malformed header; a 426 means the server requires an upgrade but the client didn't request one.
2xx — Success
2xx codes confirm that the client's request was received, understood, and processed successfully. Picking the right 2xx code in your own API communicates intent clearly to API consumers.
200 OK
The all-purpose success response. The request succeeded and the response body contains the requested data (for GET), the result of an action (for POST), or confirmation of an update (for PUT/PATCH). When in doubt, 200 is the safe choice — but more specific codes are almost always better.
GET /api/users/42 HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 42,
"name": "Jane Doe",
"email": "[email protected]"
} 201 Created
The request succeeded and a new resource was created as a result. Always use 201 instead of 200 for successful POST requests that create resources. Best practice is to include a Location header pointing to the newly created resource, and optionally return the created object in the body.
POST /api/users HTTP/1.1
Content-Type: application/json
{ "name": "John Smith", "email": "[email protected]" }
HTTP/1.1 201 Created
Location: /api/users/43
Content-Type: application/json
{
"id": 43,
"name": "John Smith",
"email": "[email protected]"
} 204 No Content
The request succeeded but there is no content to return. Commonly used for DELETE operations and PUT/PATCH requests where you don't need to return the updated resource. The response must not include a body. If your client is expecting data after a 204 and hanging, that's your culprit.
DELETE /api/users/42 HTTP/1.1
HTTP/1.1 204 No Content Debugging tip for 2xx: A 200 when you expected 201 often means the server is reusing an existing resource rather than creating a new one (idempotent creation). A 200 on a DELETE means the server returned a body — check whether your client is discarding it unexpectedly.
3xx — Redirection
3xx codes tell the client to take an additional action to complete the request — usually following a redirect to a different URL. The Location header specifies where to go next.
301 Moved Permanently
The resource has been permanently moved to a new URL. Browsers and HTTP clients will cache this redirect and automatically use the new URL for all future requests. Search engines transfer link equity to the new URL. Use this when you're permanently restructuring your URLs or migrating a domain.
GET /old-page HTTP/1.1
HTTP/1.1 301 Moved Permanently
Location: https://example.com/new-page 302 Found
A temporary redirect. The resource is available at a different URL for this request only. Clients should continue to use the original URL for future requests. In practice, most browsers convert the follow-up request to GET even if the original was POST — which is why 307 and 308 were introduced.
304 Not Modified
Used for conditional GET requests with caching. If the client already has a cached version and sends an If-None-Match or If-Modified-Since header, and the resource hasn't changed, the server responds with 304 and no body — saving bandwidth. The client uses its cached copy.
GET /api/data HTTP/1.1
If-None-Match: "abc123"
HTTP/1.1 304 Not Modified
ETag: "abc123" 307 Temporary Redirect
Like 302, but guarantees the HTTP method is preserved. If you POST to a URL that redirects with 307, the client must POST to the new URL as well (not convert to GET). Use this for temporary redirects where method preservation matters — for example, redirecting form submissions.
308 Permanent Redirect
Like 301, but preserves the HTTP method. The permanent equivalent of 307. Use 308 instead of 301 when you need to permanently redirect non-GET requests (such as API endpoints that accept POST or PUT).
Debugging tip for 3xx: Redirect loops (the browser keeps following redirects endlessly) are a common trap. They usually happen when a 301 is misconfigured to point back to itself, or when HTTP and HTTPS rewrites conflict. Check your Location headers carefully. Also watch for redirect chains longer than 3–4 hops — each hop adds latency.
4xx — Client Errors
4xx codes mean the client did something wrong — bad input, missing authentication, insufficient permissions, or a resource that doesn't exist. These are the most common errors you'll encounter when building or consuming APIs.
400 Bad Request
The server could not understand the request due to invalid syntax, missing required fields, or malformed data. This is the generic catch-all for client-side input problems.
POST /api/users HTTP/1.1
Content-Type: application/json
{ "email": "not-an-email", "name": "" }
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "Validation failed",
"details": [
{ "field": "email", "message": "Must be a valid email address" },
{ "field": "name", "message": "Name cannot be empty" }
]
} 401 Unauthorized
Despite the name, 401 means unauthenticated — the client has not proved their identity. The server should include a WWW-Authenticate header indicating how to authenticate. Common causes: missing Authorization header, expired JWT token, or invalid API key.
GET /api/profile HTTP/1.1
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api"
Content-Type: application/json
{ "error": "Authentication required" } 403 Forbidden
The client is authenticated but does not have permission to access the resource. Unlike 401, re-authenticating will not help — the identity is known but the action is not allowed. Use 403 when a logged-in user tries to access another user's data, or a regular user tries to access an admin endpoint.
GET /api/admin/users HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
HTTP/1.1 403 Forbidden
Content-Type: application/json
{ "error": "Insufficient permissions" } 404 Not Found
The most famous status code. The server cannot find the requested resource. This could mean the URL is wrong, the resource was deleted, or the resource never existed. Note: servers sometimes deliberately return 404 instead of 403 to avoid leaking information about the existence of a resource.
405 Method Not Allowed
The HTTP method used is not supported for the requested resource. The server must include an Allow header listing the permitted methods. This often happens when you try to POST to a read-only endpoint, or DELETE a resource that doesn't support deletion.
DELETE /api/config HTTP/1.1
HTTP/1.1 405 Method Not Allowed
Allow: GET, PUT 409 Conflict
The request conflicts with the current state of the server. Common scenarios: trying to create a resource that already exists (duplicate email, duplicate username), or a version conflict in optimistic locking (you tried to update version 5 of a record but it's now version 6).
POST /api/users HTTP/1.1
Content-Type: application/json
{ "email": "[email protected]", "name": "Jane" }
HTTP/1.1 409 Conflict
Content-Type: application/json
{ "error": "A user with this email already exists" } 422 Unprocessable Entity
The server understands the content type and the syntax is correct, but it was unable to process the contained instructions due to semantic errors. While 400 covers syntax errors, 422 is for semantic validation failures — the JSON is valid, but the data doesn't make business sense. Heavily used in Rails APIs and increasingly in REST APIs generally.
POST /api/orders HTTP/1.1
Content-Type: application/json
{ "quantity": -5, "product_id": 99 }
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"error": "Unprocessable entity",
"details": [
{ "field": "quantity", "message": "Must be greater than 0" }
]
} 429 Too Many Requests
The client has sent too many requests in a given time period (rate limiting). The server should include a Retry-After header indicating when the client can try again. If you're hitting 429s in production, you need to implement backoff and retry logic, or request a higher rate limit from the API provider.
GET /api/search?q=shoes HTTP/1.1
HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1711000000
{ "error": "Rate limit exceeded. Try again in 30 seconds." } Debugging tip for 4xx: Always read the response body — well-designed APIs include a machine-readable error code and a human-readable message explaining what went wrong. A 400 without a body is frustrating to debug. For auth issues, double-check whether the issue is 401 (who are you?) versus 403 (I know who you are, but no). Use DevToolkit's API Tester to inspect headers and bodies in detail.
5xx — Server Errors
5xx codes mean the server encountered an error while processing a valid request. The client didn't do anything wrong — the problem is on the server side. These are the errors you need to monitor, alert on, and fix.
500 Internal Server Error
The generic server error — something went wrong and the server doesn't have a more specific code to describe it. This could be an unhandled exception, a null pointer, a database query that threw an error, or any unexpected condition. Check your server logs immediately when you see 500s.
GET /api/report?month=13 HTTP/1.1
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
{
"error": "Internal server error",
"request_id": "req_abc123"
}
Good APIs return a request_id or trace_id in 500 responses so you can correlate the client-facing error with a specific log line on the server. Always log the full stack trace server-side, but never expose it to clients.
502 Bad Gateway
A server acting as a gateway or proxy received an invalid response from an upstream server. In modern architectures with load balancers, reverse proxies (nginx, Caddy), and microservices, 502 usually means one of your backend services crashed or returned garbage. The proxy is fine; the app server behind it is not.
GET /api/products HTTP/1.1
HTTP/1.1 502 Bad Gateway
Content-Type: text/html
<html><body>502 Bad Gateway</body></html> 503 Service Unavailable
The server is temporarily unable to handle the request — usually because it's overloaded or down for maintenance. A Retry-After header should indicate when the service will be available again. Unlike 500 (something broke unexpectedly), 503 is often intentional — returned during rolling deployments or maintenance windows.
POST /api/checkout HTTP/1.1
HTTP/1.1 503 Service Unavailable
Retry-After: 120
Content-Type: application/json
{ "error": "Service temporarily unavailable. Maintenance in progress." } 504 Gateway Timeout
The gateway or proxy did not receive a timely response from the upstream server. Similar to 502, but specifically about a timeout rather than an invalid response. Common causes: a slow database query holding up a request, a microservice taking too long to respond, or network issues between services. Your upstream service is alive but too slow.
GET /api/analytics/report?range=1year HTTP/1.1
HTTP/1.1 504 Gateway Timeout
Content-Type: application/json
{ "error": "Upstream service timed out" } Debugging tip for 5xx: 500 means check your app logs. 502 means check if the app server is running. 503 means check load and capacity. 504 means profile your slowest operations and check inter-service timeouts. In all cases, set up alerting so you know about 5xx spikes before users report them. The request_id pattern (return an ID in the 5xx response, log the full context server-side) is the fastest way to trace the root cause.
Choosing the Right Status Code for Your API
When building your own API, using semantically correct status codes dramatically improves the developer experience for anyone integrating with your service. Here are the key decisions:
Creating Resources
- Use 201 Created (not 200) for successful POST requests that create a new resource.
- Include a
Locationheader pointing to the new resource. - Use 409 Conflict if a duplicate resource already exists.
Reading Resources
- Use 200 OK for successful GET requests.
- Use 304 Not Modified to support client-side caching with ETags.
- Use 404 Not Found if the resource doesn't exist. Use 403 if it exists but the user can't access it (though 404 is acceptable if you don't want to leak existence).
Updating Resources
- Use 200 OK if you return the updated resource in the response.
- Use 204 No Content if you don't return a body.
- Use 409 Conflict for optimistic locking failures.
- Use 422 Unprocessable Entity for semantic validation failures.
Deleting Resources
- Use 204 No Content for successful deletions.
- Use 404 Not Found if the resource doesn't exist (or 204 if you prefer idempotent behavior — deleting something that's already gone is still "success").
Authentication and Authorization
- Use 401 Unauthorized when credentials are missing or invalid — the client needs to authenticate.
- Use 403 Forbidden when credentials are valid but permissions are insufficient — the client is authenticated but not authorized.
Validation Errors
- Use 400 Bad Request for malformed syntax, missing required fields, or invalid data types.
- Use 422 Unprocessable Entity for syntactically valid but semantically invalid data.
- Always include a machine-readable error structure in the body — field name, error code, and human-readable message.
Rate Limiting
- Use 429 Too Many Requests with a
Retry-Afterheader and rate limit headers (X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset).
A Note on Error Response Bodies
Status codes tell you the category of the problem. Error response bodies tell you the specifics. For any 4xx or 5xx response, always return a JSON body (for JSON APIs) with at minimum:
- A human-readable
messageorerrorstring. - A machine-readable
code(e.g.,"VALIDATION_ERROR","RESOURCE_NOT_FOUND") so clients can handle specific errors programmatically without parsing strings. - For validation errors: a
detailsarray listing each field and its problem. - For 500s: a
request_idfor server-side log correlation.
{
"error": "Validation failed",
"code": "VALIDATION_ERROR",
"request_id": "req_7f3a9c2b",
"details": [
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "Must be a valid email address"
}
]
} Related Guides and Tools
HTTP status codes are just one part of working with APIs effectively. To go deeper:
- Read the REST API Testing Guide to learn how to test status codes and response bodies systematically.
- Check out the Regex Cheat Sheet if you're parsing or validating response data in your tests.
- Use DevToolkit's API Tester to send live requests and inspect status codes, headers, and bodies in your browser — no Postman installation needed.
Try DevToolkit's HTTP Status Codes Reference
Need to look up a specific code without digging through RFCs? DevToolkit's HTTP Status Codes tool gives you an instant, searchable reference for every standard status code — with descriptions, typical use cases, and which RFC defines it. Bookmark it, and the next time a 422 shows up in your logs you'll have the answer in seconds.