Error Handling Guide 🗄️ Archived
Best practices for handling and recovering from API errors
Proper error handling is crucial for building robust applications.
Error Response Format
All errors follow a consistent structure:
{
"error": {
"code": "error_code",
"message": "Human-readable error message",
"details": [
{
"field": "field_name",
"message": "Field-specific error"
}
],
"documentation_url": "https://docs.example.com/errors/error_code"
},
"meta": {
"request_id": "req_abc123",
"timestamp": "2025-02-08T10:30:00Z"
}
}Common Error Codes
Client Errors (4xx)
| Code | HTTP Status | Description |
|---|---|---|
invalid_request | 400 | Malformed request |
authentication_required | 401 | Missing or invalid credentials |
insufficient_permissions | 403 | Lacks required permissions |
resource_not_found | 404 | Resource doesn’t exist |
method_not_allowed | 405 | HTTP method not supported |
rate_limit_exceeded | 429 | Too many requests |
Server Errors (5xx)
| Code | HTTP Status | Description |
|---|---|---|
internal_server_error | 500 | Unexpected server error |
service_unavailable | 503 | Temporary service disruption |
gateway_timeout | 504 | Upstream service timeout |
Handling Errors in Code
Python
import requests
from requests.exceptions import RequestException
def make_api_request(url, headers):
try:
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 429:
# Rate limit - wait and retry
retry_after = int(e.response.headers.get('Retry-After', 60))
print(f"Rate limited. Retry after {retry_after} seconds")
raise
elif e.response.status_code >= 500:
# Server error - retry with backoff
print("Server error. Will retry...")
raise
else:
# Client error - don't retry
error_data = e.response.json()
print(f"Error: {error_data['error']['message']}")
raise
except requests.exceptions.Timeout:
print("Request timed out")
raise
except requests.exceptions.ConnectionError:
print("Connection error")
raise
except RequestException as e:
print(f"Unexpected error: {e}")
raiseJavaScript
async function makeApiRequest(url, options) {
try {
const response = await fetch(url, options);
if (!response.ok) {
const errorData = await response.json();
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
throw new RateLimitError(
errorData.error.message,
retryAfter
);
} else if (response.status >= 500) {
throw new ServerError(errorData.error.message);
} else {
throw new ClientError(
errorData.error.message,
errorData.error.code
);
}
}
return await response.json();
} catch (error) {
if (error instanceof TypeError) {
// Network error
console.error('Network error:', error);
}
throw error;
}
}Retry Strategies
Exponential Backoff
import time
from functools import wraps
def retry_with_backoff(max_retries=3, backoff_factor=2):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
retries = 0
while retries < max_retries:
try:
return func(*args, **kwargs)
except Exception as e:
retries += 1
if retries >= max_retries:
raise
wait_time = backoff_factor ** retries
print(f"Retry {retries}/{max_retries} after {wait_time}s")
time.sleep(wait_time)
return wrapper
return decorator
@retry_with_backoff(max_retries=3)
def fetch_data():
return make_api_request(url, headers)Circuit Breaker Pattern
class CircuitBreaker:
def __init__(self, failure_threshold=5, timeout=60):
self.failure_threshold = failure_threshold
self.timeout = timeout
self.failures = 0
self.last_failure_time = None
self.state = 'closed'
def call(self, func, *args, **kwargs):
if self.state == 'open':
if time.time() - self.last_failure_time > self.timeout:
self.state = 'half-open'
else:
raise Exception("Circuit breaker is open")
try:
result = func(*args, **kwargs)
self.on_success()
return result
except Exception as e:
self.on_failure()
raise
def on_success(self):
self.failures = 0
self.state = 'closed'
def on_failure(self):
self.failures += 1
self.last_failure_time = time.time()
if self.failures >= self.failure_threshold:
self.state = 'open'Validation Errors
Handle validation errors gracefully:
def create_resource(data):
try:
response = api.create('/resources', data)
return response
except ValidationError as e:
# Extract field-specific errors
errors = e.details
for error in errors:
print(f"{error['field']}: {error['message']}")
# Show user-friendly message
raise Exception("Please correct the form errors")Logging Errors
Always log errors with context:
import logging
logger = logging.getLogger(__name__)
def make_request_with_logging(url, headers):
request_id = headers.get('X-Request-ID')
try:
response = requests.get(url, headers=headers)
response.raise_for_status()
logger.info(f"Request successful: {request_id}")
return response.json()
except Exception as e:
logger.error(
f"Request failed: {request_id}",
exc_info=True,
extra={
'url': url,
'status_code': getattr(e.response, 'status_code', None)
}
)
raiseUser-Friendly Error Messages
Map error codes to user-friendly messages:
ERROR_MESSAGES = {
'invalid_request': 'Please check your input and try again.',
'authentication_required': 'Please log in to continue.',
'insufficient_permissions': 'You don\'t have permission to perform this action.',
'resource_not_found': 'The requested item could not be found.',
'rate_limit_exceeded': 'Too many requests. Please wait a moment and try again.',
'internal_server_error': 'Something went wrong. Our team has been notified.',
}
def get_user_message(error_code):
return ERROR_MESSAGES.get(
error_code,
'An unexpected error occurred. Please try again.'
)Best Practices
- Always include request IDs for debugging
- Log errors with context for troubleshooting
- Retry transient errors with exponential backoff
- Don’t retry client errors (4xx) automatically
- Show user-friendly messages to end users
- Monitor error rates to detect issues early
- Document error codes in your API documentation