7.13. HTTP Status Codes
Table of Contents
Understanding HTTP Status Codes
HTTP status codes are short numeric codes that tell the client what happened with its request. Every HTTP response has a status line, which includes:
- The HTTP version
- The status code (like
200) - A short reason phrase (like
OK)
Example raw response start:
HTTP/1.1 200 OK
Content-Type: application/jsonAs a backend developer, returning the correct status code is critical. It affects:
- How browsers behave
- How API clients handle errors and retries
- How other developers understand your API
In this chapter we will focus on how to use status codes correctly in REST APIs, not the full protocol theory behind them.
Important rule:
Always return a status code that matches what actually happened on the server.
Never use 200 OK for everything.
Status Code Categories
All status codes are 3 digits. The first digit describes the category:
| Range | Category | Typical Meaning |
|---|---|---|
| 1xx | Informational | Request received, processing continues |
| 2xx | Success | Request was successfully received and handled |
| 3xx | Redirection | Client must take additional action |
| 4xx | Client error | Problem with the request |
| 5xx | Server error | Server failed to process a valid request |
For REST APIs, you will mostly use:
- 2xx codes for successful operations
- 4xx codes for invalid input or unauthorized access
- 5xx codes when something goes wrong on your server
Common 2xx Success Codes
2xx codes mean "the request was successfully received, understood, and accepted."
200 OK
Use when the request succeeded and you return a response body.
Typical cases:
- Successful GET request
- Successful PUT or PATCH where you return the updated resource
- Successful POST where you choose to return a full response body
Example: GET a list of users
GET /users HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json
[
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
]201 Created
Use when a new resource has been created as a result of the request, usually with POST.
You should also include a Location header with the URL of the new resource.
Example: Create a new user
POST /users HTTP/1.1
Content-Type: application/json
{"name": "Alice"}
HTTP/1.1 201 Created
Location: /users/1
Content-Type: application/json
{"id": 1, "name": "Alice"}
In REST APIs, 201 Created is preferred over 200 OK for successful creation.
202 Accepted
Use when the request has been accepted for processing, but the processing is not complete yet.
Typical for:
- Long-running operations
- Background jobs
Example: Start a report generation job
POST /reports HTTP/1.1
Content-Type: application/json
{"type": "monthly"}
HTTP/1.1 202 Accepted
Content-Type: application/json
{"job_id": "abc123", "status": "pending"}The actual report will be generated later. The client might poll another endpoint to check status.
204 No Content
Use when the request succeeded but there is no response body.
Typical cases:
- Successful DELETE
- Successful PUT or PATCH when you choose not to return content
- Some POST actions that do not return data
Example: Delete a user
DELETE /users/1 HTTP/1.1
HTTP/1.1 204 No Content
Do not include a response body with 204. Many HTTP clients will ignore it.
Common 3xx Redirection Codes
In pure APIs, 3xx codes are less common but still important.
301 Moved Permanently
Use when a resource has permanently moved to a new URL. Helpful when you reorganize your API.
Example:
GET /v1/users HTTP/1.1
HTTP/1.1 301 Moved Permanently
Location: /v2/usersClients should update their URLs.
302 Found
Traditional web redirect, often used after form submissions in browser apps.
In pure JSON APIs, you usually avoid 302, and instead return 200/201 with JSON that tells the client what to do.
304 Not Modified
Used with caching. Means the resource has not changed since the last request, so the client can use its cached version.
Example flow:
- Client sends
If-None-Matchheader with an ETag - Server responds with 304 if nothing changed
GET /users/1 HTTP/1.1
If-None-Match: "v1"
HTTP/1.1 304 Not ModifiedNo response body is sent.
Common 4xx Client Error Codes
4xx codes mean "the client did something wrong" or "the client must change something."
400 Bad Request
Use when the request is malformed or invalid, and no more specific 4xx code fits.
Typical cases:
- Invalid JSON
- Missing required fields
- Invalid field formats (if you choose not to use 422)
Example: Invalid JSON
POST /users HTTP/1.1
Content-Type: application/json
{"name": "Alice" // missing closing brace
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "invalid_request",
"message": "Body contains invalid JSON"
}401 Unauthorized
Despite the name, 401 actually means "unauthenticated." Use when the request has no valid authentication credentials.
Typical cases:
- Missing token
- Expired token
- Invalid token
Example: Missing token
GET /me HTTP/1.1
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
Content-Type: application/json
{
"error": "unauthorized",
"message": "Authentication credentials were not provided"
}403 Forbidden
Use when the user is authenticated, but does not have permission to perform the action.
Comparison:
| Code | Meaning | Example |
|---|---|---|
| 401 | Not authenticated | Not logged in |
| 403 | Authenticated, but not allowed | Logged in as user, trying to access admin page |
Example: Normal user trying to access admin data
GET /admin/users HTTP/1.1
Authorization: Bearer user-token
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"error": "forbidden",
"message": "You do not have permission to access this resource"
}404 Not Found
Use when a resource cannot be found, or you do not want to reveal it exists.
Typical cases:
- GET, PUT, PATCH, DELETE on non-existing resource
- Intentionally hide whether resource exists (for security or privacy)
Example: User does not exist
GET /users/9999 HTTP/1.1
HTTP/1.1 404 Not Found
Content-Type: application/json
{
"error": "not_found",
"message": "User not found"
}Security example: Hiding whether an email exists in the system:
POST /password-reset HTTP/1.1
Content-Type: application/json
{"email": "unknown@example.com"}
HTTP/1.1 200 OK
Content-Type: application/json
{"message": "If an account with this email exists, a reset link has been sent"}You might still log internally that the email was not found, but you do not reveal it to the client.
405 Method Not Allowed
Use when the HTTP method is not allowed for the requested resource.
Example: You support GET on /users/1, but not POST.
POST /users/1 HTTP/1.1
HTTP/1.1 405 Method Not Allowed
Allow: GET, PUT, DELETE
Content-Type: application/json
{
"error": "method_not_allowed",
"message": "Method POST is not allowed for this resource"
}409 Conflict
Use when the request conflicts with the current state of the resource.
Typical cases:
- Creating a resource that already exists (e.g. username taken)
- Version conflicts with optimistic locking
Example: Username already taken
POST /users HTTP/1.1
Content-Type: application/json
{"username": "alice"}
HTTP/1.1 409 Conflict
Content-Type: application/json
{
"error": "conflict",
"message": "Username already exists"
}410 Gone
Use when a resource used to exist, but has been permanently removed and will not return.
Less common, but useful when you want to explicitly say a resource is gone, not just unknown.
415 Unsupported Media Type
Use when the server cannot handle the format of the body.
Example: API expects JSON, but client sends XML.
POST /users HTTP/1.1
Content-Type: application/xml
<user><name>Alice</name></user>
HTTP/1.1 415 Unsupported Media Type
Content-Type: application/json
{
"error": "unsupported_media_type",
"message": "Content type application/xml is not supported. Use application/json"
}422 Unprocessable Entity
Common in modern APIs for semantic validation errors. The request body is well-formed, but the data is invalid.
Typical cases:
- Field too short or too long
- Email not a valid email
- Missing required field in a JSON body
Many frameworks, such as FastAPI, use 422 by default for validation errors.
Example: Invalid data
POST /users HTTP/1.1
Content-Type: application/json
{"email": "not-an-email", "age": 10}
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"error": "validation_error",
"details": [
{"field": "email", "message": "Invalid email address"},
{"field": "age", "message": "Must be at least 13"}
]
}429 Too Many Requests
Use when a client has sent too many requests in a given period, for rate limiting.
Example:
GET /search?q=test HTTP/1.1
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json
{
"error": "too_many_requests",
"message": "Rate limit exceeded. Try again in 60 seconds"
}
The Retry-After header tells the client when to try again.
Common 5xx Server Error Codes
5xx codes mean something went wrong on the server side.
Important rule:
Use 5xx codes only when the server is at fault, not the client.
If the client sent a bad request, use a 4xx code, even if your code crashed while handling it.
500 Internal Server Error
Generic server error when something unexpected happens and no specific 5xx code applies.
Typical cases:
- Unhandled exceptions
- Programming errors
- Misconfigurations
Example:
GET /users HTTP/1.1
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
{
"error": "internal_server_error",
"message": "An unexpected error occurred"
}In production, do not return stack traces or sensitive details to the client. Log them on the server instead.
502 Bad Gateway
Use when your server is a gateway or proxy and receives an invalid response from an upstream server.
Example scenarios:
- Your API server behind Nginx, and Nginx cannot reach your app
- API gateway cannot talk to a microservice
In many setups, the reverse proxy (like Nginx) or load balancer will return 502, not your application code.
503 Service Unavailable
Use when the service is temporarily unavailable, for example:
- Maintenance windows
- Overloaded server
- Database down
You can include a Retry-After header to tell clients how long to wait.
Example:
GET /users HTTP/1.1
HTTP/1.1 503 Service Unavailable
Retry-After: 120
Content-Type: application/json
{
"error": "service_unavailable",
"message": "Service is temporarily down for maintenance"
}504 Gateway Timeout
Use when acting as a gateway or proxy and an upstream server takes too long to respond.
Example:
- API gateway calls a microservice that does not respond within the timeout
Again, often returned by infrastructure rather than your own application code.
Choosing the Right Status Code in REST APIs
You will often need to decide which code to use. Here are common REST actions and typical codes.
Typical Status Codes by HTTP Method
| Operation | Method | Typical Status Codes |
|---|---|---|
| Get a list of resources | GET | 200 |
| Get a single resource | GET | 200, 404 |
| Create a new resource | POST | 201, 400, 401, 403, 409, 422 |
| Update a resource fully | PUT | 200, 204, 400, 401, 403, 404, 409, 422 |
| Update a resource partially | PATCH | 200, 204, 400, 401, 403, 404, 409, 422 |
| Delete a resource | DELETE | 204, 404, 401, 403 |
| Non-idempotent action (e.g. login) | POST | 200, 400, 401, 422 |
Example: User Registration Endpoint
Requirements:
- Create user: return 201 and user data
- Validation errors: return 422 with details
- Username already exists: return 409
Possible responses:
POST /users HTTP/1.1
Content-Type: application/json
{"username": "alice", "email": "alice@example.com", "password": "short"}
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"error": "validation_error",
"details": [
{"field": "password", "message": "Password must be at least 8 characters"}
]
}POST /users HTTP/1.1
Content-Type: application/json
{"username": "alice", "email": "alice@example.com", "password": "longpassword"}
HTTP/1.1 201 Created
Location: /users/1
Content-Type: application/json
{"id": 1, "username": "alice", "email": "alice@example.com"}POST /users HTTP/1.1
Content-Type: application/json
{"username": "alice", "email": "another@example.com", "password": "longpassword"}
HTTP/1.1 409 Conflict
Content-Type: application/json
{
"error": "conflict",
"message": "Username already exists"
}Example: Login Endpoint
Requirements:
- Wrong credentials: 401
- Missing fields: 400 or 422
- Successful login: 200, return token
POST /login HTTP/1.1
Content-Type: application/json
{"username": "alice", "password": "wrong"}
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{
"error": "invalid_credentials",
"message": "Username or password is incorrect"
}POST /login HTTP/1.1
Content-Type: application/json
{"username": "alice", "password": "correct-password"}
HTTP/1.1 200 OK
Content-Type: application/json
{"access_token": "jwt-token-here", "token_type": "bearer"}Status Codes and Idempotency
Idempotent methods, such as GET, PUT, DELETE, should have consistent results if called multiple times.
Status codes help communicate this:
- Repeated DELETE of the same resource:
- First time:
204 No Content - Second time: often
404 Not Found - Repeated POST to create the same resource:
- Could be
201 Createdthen409 Conflictif you enforce uniqueness
Example:
DELETE /users/1 HTTP/1.1
HTTP/1.1 204 No ContentSecond attempt:
DELETE /users/1 HTTP/1.1
HTTP/1.1 404 Not Found
Content-Type: application/json
{"error": "not_found", "message": "User not found"}
Some APIs choose to return 204 both times for "delete if exists" semantics, but you must document this behavior clearly.
Designing Error Responses
Status codes are numeric and short. API clients also need structured error information.
A common pattern is to combine:
- A proper HTTP status code
- A JSON body with machine readable fields and human messages
Example standard structure:
{
"error": "validation_error",
"message": "There were validation errors",
"details": [
{"field": "email", "message": "Invalid email address"}
]
}Or for a simple not found:
{
"error": "not_found",
"message": "User not found"
}
Important rule:
Use HTTP status codes for the high level result,
and use JSON body fields for detailed error information.
Summary Cheat Sheet
| Code | Phrase | Typical Use in REST APIs |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH, POST with body |
| 201 | Created | Successful creation of a new resource |
| 202 | Accepted | Accepted for async processing |
| 204 | No Content | Successful delete or update without body |
| 301 | Moved Permanently | Resource permanently moved |
| 304 | Not Modified | Caching, resource unchanged |
| 400 | Bad Request | Malformed request, invalid syntax |
| 401 | Unauthorized | Authentication required or invalid |
| 403 | Forbidden | Authenticated but not allowed |
| 404 | Not Found | Resource does not exist |
| 405 | Method Not Allowed | Method not supported for this endpoint |
| 409 | Conflict | State conflict, duplicate resource, etc. |
| 410 | Gone | Resource permanently removed |
| 415 | Unsupported Media Type | Wrong Content-Type |
| 422 | Unprocessable Entity | Semantic validation errors |
| 429 | Too Many Requests | Rate limit reached |
| 500 | Internal Server Error | Unexpected server error |
| 502 | Bad Gateway | Invalid response from upstream |
| 503 | Service Unavailable | Temporary overload or maintenance |
| 504 | Gateway Timeout | Upstream service timeout |
As you build APIs, get into the habit of consciously choosing the status code. This small detail makes your backend more predictable, easier to integrate with, and easier to debug.
Views: 9
KAHIBARO