Definition
4xx Client Error status codes indicate that the request sent by the client contains an error or cannot be fulfilled. The error is on the client’s side (bad syntax, invalid data, missing authentication, etc.).
The most common 4xx status codes are:
- 400 Bad Request - Malformed request syntax or invalid data
- 401 Unauthorized - Missing or invalid authentication credentials
- 403 Forbidden - Authenticated but lacks permission to access resource
- 404 Not Found - Resource does not exist at the specified URI
- 409 Conflict - Request conflicts with current state (e.g., duplicate email)
- 422 Unprocessable Entity - Validation errors in request data
- 429 Too Many Requests - Rate limit exceeded
4xx codes are defined in RFC 7231 and RFC 6585, indicating that the client should modify the request before retrying.
Example
400 Bad Request - Invalid JSON:
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"name": "Alice",
"email": "not-an-email" // Invalid email format
}
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "Validation failed",
"details": [
{
"field": "email",
"message": "Invalid email format"
}
]
}
401 Unauthorized - Missing Token:
GET /api/users/me HTTP/1.1
Host: api.example.com
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api.example.com"
Content-Type: application/json
{
"error": "Authentication required",
"message": "Missing or invalid access token"
}
403 Forbidden - Insufficient Permissions:
DELETE /api/users/123 HTTP/1.1
Host: api.example.com
Authorization: Bearer YOUR_TOKEN
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"error": "Forbidden",
"message": "You do not have permission to delete this user"
}
404 Not Found - Resource Doesn’t Exist:
GET /api/users/999 HTTP/1.1
Host: api.example.com
HTTP/1.1 404 Not Found
Content-Type: application/json
{
"error": "Not Found",
"message": "User with ID 999 does not exist"
}
409 Conflict - Duplicate Resource:
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"name": "Alice",
"email": "[email protected]" // Email already exists
}
HTTP/1.1 409 Conflict
Content-Type: application/json
{
"error": "Conflict",
"message": "User with email [email protected] already exists"
}
422 Unprocessable Entity - Validation Error:
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"name": "A", // Too short
"email": "[email protected]",
"age": -5 // Invalid age
}
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"error": "Validation failed",
"details": [
{
"field": "name",
"message": "Name must be at least 2 characters"
},
{
"field": "age",
"message": "Age must be a positive number"
}
]
}
Code Example
JavaScript (Fetch API):
const createUser = async (userData) => {
try {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN'
},
body: JSON.stringify(userData)
});
// Handle specific 4xx errors
if (response.status === 400) {
const error = await response.json();
console.error('Bad Request:', error);
throw new Error(`Validation failed: ${JSON.stringify(error.details)}`);
}
if (response.status === 401) {
console.error('Unauthorized - token expired or missing');
// Redirect to login
window.location.href = '/login';
return;
}
if (response.status === 403) {
const error = await response.json();
console.error('Forbidden:', error.message);
throw new Error('Insufficient permissions');
}
if (response.status === 404) {
console.error('Resource not found');
throw new Error('User not found');
}
if (response.status === 409) {
const error = await response.json();
console.error('Conflict:', error.message);
throw new Error('User already exists');
}
if (response.status === 422) {
const error = await response.json();
console.error('Validation Error:', error.details);
throw new Error('Invalid user data');
}
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
console.error('Rate limit exceeded. Retry after:', retryAfter);
throw new Error(`Too many requests. Retry after ${retryAfter} seconds`);
}
// Check if response is successful (2xx)
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('Error creating user:', error);
throw error;
}
};
// Usage with error handling
const newUser = {
name: 'Alice Smith',
email: '[email protected]'
};
createUser(newUser)
.then(user => console.log('Created:', user))
.catch(error => {
// Show user-friendly error message
alert(`Failed to create user: ${error.message}`);
});
Python (requests library):
import requests
import time
def create_user(user_data):
try:
response = requests.post(
'https://api.example.com/users',
json=user_data,
headers={'Authorization': 'Bearer YOUR_TOKEN'}
)
# Handle specific 4xx errors
if response.status_code == 400:
error = response.json()
print(f'Bad Request: {error}')
raise ValueError(f"Validation failed: {error.get('details')}")
if response.status_code == 401:
print('Unauthorized - token expired or missing')
# Redirect to login or refresh token
raise PermissionError('Authentication required')
if response.status_code == 403:
error = response.json()
print(f"Forbidden: {error.get('message')}")
raise PermissionError('Insufficient permissions')
if response.status_code == 404:
print('Resource not found')
raise FileNotFoundError('User not found')
if response.status_code == 409:
error = response.json()
print(f"Conflict: {error.get('message')}")
raise ValueError('User already exists')
if response.status_code == 422:
error = response.json()
print(f"Validation Error: {error.get('details')}")
raise ValueError('Invalid user data')
if response.status_code == 429:
retry_after = response.headers.get('Retry-After')
print(f'Rate limit exceeded. Retry after: {retry_after}')
# Wait and retry
if retry_after:
time.sleep(int(retry_after))
return create_user(user_data) # Retry
raise Exception('Too many requests')
# Check if response is successful (2xx)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f'Error creating user: {e}')
raise
# Usage with error handling
new_user = {
'name': 'Alice Smith',
'email': '[email protected]'
}
try:
user = create_user(new_user)
print(f'Created: {user}')
except Exception as error:
print(f'Failed to create user: {error}')
Diagram
graph TB
subgraph "4xx Client Errors"
E400[400 Bad Request
Invalid syntax/data]
E401[401 Unauthorized
Missing auth]
E403[403 Forbidden
No permission]
E404[404 Not Found
Resource missing]
E409[409 Conflict
Duplicate/state conflict]
E422[422 Unprocessable
Validation failed]
E429[429 Too Many
Rate limit]
end
subgraph "Client Actions"
A1[Fix request syntax]
A2[Add/refresh token]
A3[Request access]
A4[Check URI]
A5[Modify data]
A6[Fix validation]
A7[Wait & retry]
end
E400 --> A1
E401 --> A2
E403 --> A3
E404 --> A4
E409 --> A5
E422 --> A6
E429 --> A7
Analogy
Think of 4xx errors like problems at a restaurant entrance:
- 400 Bad Request β “Your order form is incomplete”
- 401 Unauthorized β “You need to show ID to enter”
- 403 Forbidden β “This is the VIP section, you can’t enter”
- 404 Not Found β “That dish isn’t on our menu”
- 409 Conflict β “You already have a reservation”
- 429 Too Many Requests β “You’re ordering too fast, slow down”
Best Practices
- Use Specific 4xx Codes - Don’t use 400 for everything; use 401/403/404/422 appropriately
- Include Error Details - Provide helpful messages explaining what went wrong
- Validate Early - Return 400/422 before processing to avoid wasted resources
- Return Field-Level Errors - Include specific validation errors for each field
- Use WWW-Authenticate - Include header with 401 to indicate auth method
- Set Retry-After - Include header with 429 to tell clients when to retry
- Log 4xx Errors - Track client errors to identify API usability issues
Common Mistakes
- 400 for Everything - Using 400 Bad Request for all errors instead of specific codes
- 401 vs 403 Confusion - Mixing up authentication (401) vs authorization (403)
- No Error Details - Returning generic errors without helpful messages
- Exposing Internal Info - Including stack traces or database errors in 4xx responses
- No Retry-After - Not telling clients when to retry after rate limiting
- Inconsistent Format - Changing error response structure across endpoints
- 404 for Auth Failures - Returning 404 to hide existence of resources (security through obscurity)