Error Handling
This guide covers the error format, common error codes, and best practices for handling errors in your applications.
Error Format
All errors follow a consistent JSON structure:
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error message",
"details": {
// Additional context (optional)
},
"request_id": "req_abc123xyz"
}
}
| Field | Description |
|---|---|
code | Machine-readable error code |
message | Human-readable description |
details | Additional context (field errors, limits, etc.) |
request_id | Unique identifier for support requests |
HTTP Status Codes
| Status | Meaning | When It Occurs |
|---|---|---|
400 | Bad Request | Invalid request syntax or parameters |
401 | Unauthorized | Missing or invalid authentication |
403 | Forbidden | Valid auth but insufficient permissions |
404 | Not Found | Resource doesn't exist |
409 | Conflict | Resource state conflict |
422 | Unprocessable Entity | Validation errors |
429 | Too Many Requests | Rate limit exceeded |
500 | Internal Server Error | Server-side error |
502 | Bad Gateway | Upstream service error |
503 | Service Unavailable | Temporary maintenance |
504 | Gateway Timeout | Request timeout |
Common Error Codes
Authentication Errors (401)
{
"error": {
"code": "INVALID_TOKEN",
"message": "The provided access token is invalid or expired"
}
}
| Code | Description |
|---|---|
INVALID_TOKEN | Token is malformed or expired |
TOKEN_EXPIRED | Token has expired |
INVALID_API_KEY | API key is invalid |
API_KEY_EXPIRED | API key has expired |
MISSING_AUTH | No authentication provided |
Authorization Errors (403)
{
"error": {
"code": "INSUFFICIENT_PERMISSIONS",
"message": "You don't have permission to access this resource",
"details": {
"required": "write:schedules",
"granted": ["read:schedules"]
}
}
}
| Code | Description |
|---|---|
INSUFFICIENT_PERMISSIONS | Missing required permissions |
RESOURCE_ACCESS_DENIED | Cannot access this specific resource |
IP_NOT_ALLOWED | Request from unauthorized IP |
ACCOUNT_SUSPENDED | Account has been suspended |
Validation Errors (422)
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": {
"fields": {
"name": ["Site name is required"],
"location": ["Must be a valid location string"]
}
}
}
}
| Code | Description |
|---|---|
VALIDATION_ERROR | One or more fields failed validation |
INVALID_FORMAT | Request body format is invalid |
MISSING_FIELD | Required field is missing |
INVALID_VALUE | Field value is not acceptable |
Resource Errors (404, 409)
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Asset not found",
"details": {
"resource_type": "Asset",
"resource_id": "ast_abc123"
}
}
}
| Code | Description |
|---|---|
RESOURCE_NOT_FOUND | Resource doesn't exist |
ALREADY_EXISTS | Resource already exists |
CONFLICT | State conflict (e.g., concurrent modification) |
GONE | Resource was deleted |
Rate Limiting Errors (429)
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests",
"details": {
"limit": 1000,
"window": 60,
"retry_after": 45
}
}
}
Server Errors (5xx)
{
"error": {
"code": "INTERNAL_ERROR",
"message": "An unexpected error occurred",
"request_id": "req_abc123xyz"
}
}
| Code | Description |
|---|---|
INTERNAL_ERROR | Unexpected server error |
SERVICE_UNAVAILABLE | Temporary maintenance |
UPSTREAM_ERROR | Dependency service failed |
TIMEOUT | Request processing timeout |
Error Handling Best Practices
1. Always Check Status Codes
- Python
- JavaScript
import requests
def handle_api_request(method, url, **kwargs):
response = requests.request(method, url, **kwargs)
if response.ok:
return response.json()
error_data = response.json().get('error', {})
if response.status_code == 401:
raise AuthenticationError(error_data['message'])
elif response.status_code == 403:
raise PermissionError(error_data['message'])
elif response.status_code == 404:
raise NotFoundError(error_data['message'])
elif response.status_code == 422:
raise ValidationError(error_data['details'])
elif response.status_code == 429:
raise RateLimitError(
error_data['message'],
retry_after=error_data['details']['retry_after']
)
elif response.status_code >= 500:
raise ServerError(
error_data['message'],
request_id=error_data.get('request_id')
)
else:
raise APIError(error_data['message'])
class APIError extends Error {
constructor(code, message, details, requestId) {
super(message);
this.code = code;
this.details = details;
this.requestId = requestId;
}
}
async function handleAPIRequest(url, options) {
const response = await fetch(url, options);
if (response.ok) {
return response.json();
}
const { error } = await response.json();
switch (response.status) {
case 401:
throw new APIError('AUTH_ERROR', error.message);
case 403:
throw new APIError('PERMISSION_ERROR', error.message, error.details);
case 404:
throw new APIError('NOT_FOUND', error.message);
case 422:
throw new APIError('VALIDATION_ERROR', error.message, error.details);
case 429:
const retryAfter = error.details?.retry_after || 60;
throw new APIError('RATE_LIMIT', error.message, { retryAfter });
default:
throw new APIError(error.code, error.message, error.details, error.request_id);
}
}
2. Handle Validation Errors Gracefully
Display field-specific errors to users:
async function createSite(siteData) {
try {
return await api.createSite(siteData);
} catch (error) {
if (error.code === 'VALIDATION_ERROR') {
// Map errors to form fields
const fieldErrors = error.details.fields;
Object.entries(fieldErrors).forEach(([field, messages]) => {
showFieldError(field, messages.join(', '));
});
return null;
}
throw error;
}
}
3. Implement Retry Logic for Transient Errors
import time
from functools import wraps
def retry_on_error(max_retries=3, retry_on=(500, 502, 503, 504)):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_error = None
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except APIError as e:
last_error = e
if e.status_code == 429:
time.sleep(e.retry_after)
elif e.status_code in retry_on:
delay = 2 ** attempt # Exponential backoff
time.sleep(delay)
else:
raise
raise last_error
return wrapper
return decorator
@retry_on_error(max_retries=3)
def fetch_site(site_id):
return api.get_site(site_id)
4. Log Request IDs for Debugging
import logging
logger = logging.getLogger(__name__)
try:
result = api.create_schedule(schedule_data)
except APIError as e:
logger.error(
f"API Error: {e.code} - {e.message}",
extra={
'request_id': e.request_id,
'error_details': e.details
}
)
# Include request_id in support tickets
raise UserFacingError(
f"Something went wrong. Reference: {e.request_id}"
)
5. Provide Helpful User Messages
Map technical errors to user-friendly messages:
const ERROR_MESSAGES = {
'INVALID_TOKEN': 'Your session has expired. Please log in again.',
'INSUFFICIENT_PERMISSIONS': 'You don\'t have access to this feature.',
'RESOURCE_NOT_FOUND': 'The requested item could not be found.',
'VALIDATION_ERROR': 'Please check your input and try again.',
'RATE_LIMIT_EXCEEDED': 'Too many requests. Please wait a moment.',
'INTERNAL_ERROR': 'Something went wrong. Please try again later.',
};
function getErrorMessage(error) {
return ERROR_MESSAGES[error.code] || 'An unexpected error occurred.';
}
Debugging Tips
Use Request ID for Support
When contacting support, always include:
- Request ID from the error response
- Timestamp of when the error occurred
- Endpoint you were calling
- Request body (sanitized of sensitive data)
Enable Debug Logging
# Include debug info in response
curl -X GET "https://platform.powerverse.com/inventory-service/assets" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-Debug: true"
Response includes additional debugging info:
{
"data": [...],
"debug": {
"request_id": "req_abc123",
"duration_ms": 45,
"server": "api-prod-1"
}
}
Next Steps
- Rate Limits - Handle rate limiting
- Pagination - Handle paginated responses
- API Reference - Endpoint reference