Appearance
Error Handling Guide
Table of Contents
- Error Response Format
- HTTP Status Codes
- Error Categories
- Error Resolution
- Retry Strategies
- Logging and Debugging
- Common Error Scenarios
Error Response Format
All API errors follow a consistent JSON structure for easy parsing and handling.
Standard Error Response
json
{
"error": "ErrorType",
"message": "Human-readable error description",
"details": {
"field": "Additional context about the error",
"code": "SPECIFIC_ERROR_CODE",
"timestamp": "2025-01-28T10:30:00Z",
"requestId": "req-123456-abcdef"
}
}Field Descriptions
| Field | Type | Description |
|---|---|---|
error | String | Error category/type |
message | String | User-friendly error message |
details | Object | Additional error context |
details.code | String | Specific error code for programmatic handling |
details.timestamp | String | When the error occurred (ISO 8601) |
details.requestId | String | Unique request identifier for support |
HTTP Status Codes
Success Codes (2xx)
| Code | Name | Description |
|---|---|---|
| 200 | OK | Request successful |
| 201 | Created | Resource created successfully |
| 202 | Accepted | Request accepted for processing |
| 204 | No Content | Request successful, no content to return |
Client Error Codes (4xx)
| Code | Name | Description | Common Causes |
|---|---|---|---|
| 400 | Bad Request | Invalid request syntax or parameters | Malformed JSON, invalid field values |
| 401 | Unauthorized | Authentication required or failed | Missing/invalid tenant-code or api-version |
| 403 | Forbidden | Access denied to resource | Insufficient permissions for tenant |
| 404 | Not Found | Resource not found | Invalid ID or deleted resource |
| 405 | Method Not Allowed | HTTP method not supported | Using GET on POST-only endpoint |
| 409 | Conflict | Request conflicts with current state | Duplicate entry, concurrent modification |
| 422 | Unprocessable Entity | Request understood but invalid | Business rule violation |
| 429 | Too Many Requests | Rate limit exceeded | Too many requests in time window |
Server Error Codes (5xx)
| Code | Name | Description | Retry Strategy |
|---|---|---|---|
| 500 | Internal Server Error | Unexpected server error | Retry with exponential backoff |
| 502 | Bad Gateway | Invalid upstream response | Retry after delay |
| 503 | Service Unavailable | Service temporarily unavailable | Check status page, retry later |
| 504 | Gateway Timeout | Upstream request timeout | Retry with smaller payload |
Error Categories
Authentication Errors
Errors related to authentication and authorization.
json
{
"error": "AuthenticationError",
"message": "Invalid or missing authentication credentials",
"details": {
"code": "AUTH_INVALID_TENANT",
"missing_headers": ["tenant-code"],
"timestamp": "2025-01-28T10:30:00Z"
}
}Common Authentication Error Codes:
| Code | Description | Resolution |
|---|---|---|
AUTH_MISSING_HEADERS | Required headers not provided | Include api-version and tenant-code headers |
AUTH_INVALID_TENANT | Tenant code not recognized | Verify tenant code with support |
AUTH_EXPIRED_TENANT | Tenant access expired | Contact support for renewal |
AUTH_IP_RESTRICTED | Request from unauthorized IP | Add IP to whitelist |
Validation Errors
Errors related to request data validation.
json
{
"error": "ValidationError",
"message": "Request validation failed",
"details": {
"code": "VALIDATION_FAILED",
"errors": [
{
"field": "startDate",
"message": "Invalid date format",
"expected": "YYYY-MM-DD"
},
{
"field": "pageSize",
"message": "Value exceeds maximum",
"maximum": 100
}
]
}
}Common Validation Error Codes:
| Code | Description | Resolution |
|---|---|---|
VALIDATION_REQUIRED_FIELD | Required field missing | Include all required fields |
VALIDATION_INVALID_FORMAT | Field format incorrect | Check expected format in docs |
VALIDATION_OUT_OF_RANGE | Value outside allowed range | Adjust value to valid range |
VALIDATION_INVALID_ENUM | Invalid enumeration value | Use accepted enum values |
Business Logic Errors
Errors related to business rules and constraints.
json
{
"error": "BusinessLogicError",
"message": "Operation violates business rules",
"details": {
"code": "BIZ_DUPLICATE_ENTRY",
"entity": "BillReport",
"conflictingId": 12345,
"timestamp": "2025-01-28T10:30:00Z"
}
}Common Business Error Codes:
| Code | Description | Resolution |
|---|---|---|
BIZ_DUPLICATE_ENTRY | Duplicate record exists | Use existing record or modify request |
BIZ_INVALID_STATE | Invalid state transition | Check current state before operation |
BIZ_DEPENDENCY_ERROR | Required dependency missing | Create/link required resources first |
BIZ_QUOTA_EXCEEDED | Resource quota exceeded | Delete unused resources or upgrade plan |
Rate Limiting Errors
Errors when rate limits are exceeded.
json
{
"error": "RateLimitError",
"message": "API rate limit exceeded",
"details": {
"code": "RATE_LIMIT_EXCEEDED",
"limit": 1000,
"remaining": 0,
"reset": 1706441400,
"retryAfter": 60
}
}Response Headers:
http
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1706441400
Retry-After: 60Error Resolution
Resolution Workflow
mermaid
graph TD
A[API Error] --> B{Error Type?}
B -->|400| C[Fix Request Data]
B -->|401| D[Check Authentication]
B -->|403| E[Verify Permissions]
B -->|429| F[Implement Backoff]
B -->|5xx| G[Retry with Backoff]
C --> H[Validate Against Schema]
D --> I[Verify Headers]
E --> J[Contact Support]
F --> K[Wait and Retry]
G --> L[Check Status Page]Resolution Strategies by Error Code
400 Bad Request
javascript
async function handle400Error(error) {
const { details } = error;
if (details.code === 'VALIDATION_FAILED') {
// Fix validation errors
details.errors.forEach(err => {
console.log(`Fix field '${err.field}': ${err.message}`);
});
// Correct the data and retry
const correctedData = fixValidationErrors(data, details.errors);
return retry(correctedData);
}
// Log for debugging
console.error('Bad request:', error.message);
return null;
}401 Unauthorized
python
def handle_401_error(error):
"""Handle authentication errors"""
details = error.get('details', {})
if 'missing_headers' in details:
print(f"Missing headers: {details['missing_headers']}")
# Add missing headers and retry
elif details.get('code') == 'AUTH_INVALID_TENANT':
print("Invalid tenant code. Please verify with support.")
# Cannot retry - need valid credentials
raise AuthenticationError("Invalid credentials")
return None429 Rate Limited
javascript
class RateLimitHandler {
async handleRateLimit(error, requestFn) {
const retryAfter = error.details.retryAfter || 60;
const resetTime = error.details.reset * 1000;
console.log(`Rate limited. Retrying after ${retryAfter} seconds`);
// Wait for the specified time
await this.sleep(retryAfter * 1000);
// Retry the request
return await requestFn();
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}Retry Strategies
Exponential Backoff
Recommended for handling transient errors and rate limits.
javascript
async function exponentialBackoff(fn, maxRetries = 5) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
lastError = error;
// Don't retry on client errors (except 429)
if (error.status >= 400 && error.status < 500 && error.status !== 429) {
throw error;
}
// Calculate delay: 2^i * 1000ms with jitter
const delay = Math.min(
Math.pow(2, i) * 1000 + Math.random() * 1000,
30000 // Max 30 seconds
);
console.log(`Retry ${i + 1}/${maxRetries} after ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
// Usage
const result = await exponentialBackoff(async () => {
return await apiClient.get('/api/1.0/bill/search');
});Circuit Breaker Pattern
Prevent cascading failures by stopping requests when error rate is high.
python
import time
from enum import Enum
class CircuitState(Enum):
CLOSED = 1
OPEN = 2
HALF_OPEN = 3
class CircuitBreaker:
def __init__(self, failure_threshold=5, timeout=60):
self.failure_threshold = failure_threshold
self.timeout = timeout
self.failure_count = 0
self.last_failure_time = None
self.state = CircuitState.CLOSED
def call(self, func, *args, **kwargs):
if self.state == CircuitState.OPEN:
if time.time() - self.last_failure_time > self.timeout:
self.state = CircuitState.HALF_OPEN
else:
raise Exception("Circuit breaker is OPEN")
try:
result = func(*args, **kwargs)
if self.state == CircuitState.HALF_OPEN:
self.state = CircuitState.CLOSED
self.failure_count = 0
return result
except Exception as e:
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = CircuitState.OPEN
raise e
# Usage
breaker = CircuitBreaker(failure_threshold=5, timeout=60)
try:
result = breaker.call(api_client.get, '/api/1.0/bills')
except Exception as e:
print(f"Request failed: {e}")Smart Retry with Jitter
Add randomization to prevent thundering herd problem.
javascript
function smartRetry(fn, options = {}) {
const {
maxRetries = 3,
initialDelay = 1000,
maxDelay = 30000,
factor = 2,
jitter = true
} = options;
return new Promise((resolve, reject) => {
let retries = 0;
function attempt() {
fn()
.then(resolve)
.catch(error => {
if (retries >= maxRetries) {
return reject(error);
}
retries++;
// Calculate delay with exponential backoff
let delay = Math.min(initialDelay * Math.pow(factor, retries - 1), maxDelay);
// Add jitter to prevent thundering herd
if (jitter) {
delay = delay * (0.5 + Math.random());
}
console.log(`Retry ${retries}/${maxRetries} in ${delay}ms`);
setTimeout(attempt, delay);
});
}
attempt();
});
}Logging and Debugging
Structured Error Logging
javascript
class ErrorLogger {
logError(error, context = {}) {
const logEntry = {
timestamp: new Date().toISOString(),
level: 'ERROR',
requestId: context.requestId || this.generateRequestId(),
tenantCode: context.tenantCode,
endpoint: context.endpoint,
method: context.method,
statusCode: error.status,
errorCode: error.details?.code,
errorMessage: error.message,
errorDetails: error.details,
stackTrace: error.stack,
userAgent: context.userAgent,
ipAddress: context.ipAddress
};
// Send to logging service
console.error(JSON.stringify(logEntry, null, 2));
// Store for debugging
this.storeErrorForDebug(logEntry);
return logEntry.requestId;
}
generateRequestId() {
return `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
storeErrorForDebug(logEntry) {
// Store in local storage or send to error tracking service
if (typeof window !== 'undefined' && window.localStorage) {
const errors = JSON.parse(localStorage.getItem('api_errors') || '[]');
errors.push(logEntry);
// Keep only last 50 errors
if (errors.length > 50) errors.shift();
localStorage.setItem('api_errors', JSON.stringify(errors));
}
}
}Debug Information Collection
python
import json
import logging
from datetime import datetime
class DebugCollector:
def __init__(self):
self.debug_info = []
def collect_error_context(self, error, request, response):
"""Collect comprehensive debug information"""
context = {
'timestamp': datetime.utcnow().isoformat(),
'request': {
'method': request.method,
'url': request.url,
'headers': dict(request.headers),
'body': request.body if hasattr(request, 'body') else None
},
'response': {
'status': response.status_code if response else None,
'headers': dict(response.headers) if response else None,
'body': response.text if response else None
},
'error': {
'type': type(error).__name__,
'message': str(error),
'details': error.__dict__ if hasattr(error, '__dict__') else None
},
'environment': {
'api_version': '1.0',
'sdk_version': '1.0.0',
'platform': 'python'
}
}
self.debug_info.append(context)
return context
def export_debug_log(self, filepath='debug_log.json'):
"""Export debug information for support"""
with open(filepath, 'w') as f:
json.dump(self.debug_info, f, indent=2, default=str)
print(f"Debug log exported to {filepath}")
return filepathCommon Error Scenarios
Scenario 1: Bulk Operation Partial Failure
javascript
async function handleBulkOperationError(operations, errors) {
const results = {
successful: [],
failed: [],
retryable: []
};
for (let i = 0; i < operations.length; i++) {
const error = errors[i];
if (!error) {
results.successful.push(operations[i]);
} else if (error.status >= 500 || error.status === 429) {
results.retryable.push({
operation: operations[i],
error: error
});
} else {
results.failed.push({
operation: operations[i],
error: error,
resolution: getErrorResolution(error)
});
}
}
// Retry retryable operations
if (results.retryable.length > 0) {
console.log(`Retrying ${results.retryable.length} operations`);
await retryOperations(results.retryable);
}
return results;
}Scenario 2: Authentication Token Refresh
python
class TokenManager:
def __init__(self):
self.token = None
self.token_expiry = None
def handle_auth_error(self, error):
"""Handle authentication errors with token refresh"""
if error.get('details', {}).get('code') == 'AUTH_TOKEN_EXPIRED':
# Refresh token
self.refresh_token()
return True # Retry request
elif error.get('details', {}).get('code') == 'AUTH_INVALID_TOKEN':
# Re-authenticate
self.authenticate()
return True # Retry request
return False # Cannot handle error
def refresh_token(self):
"""Refresh authentication token"""
# Implementation depends on auth mechanism
pass
def authenticate(self):
"""Perform full authentication"""
# Implementation depends on auth mechanism
passScenario 3: Handling Concurrent Modification
javascript
async function handleConcurrentModification(resource, updateFn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
// Get latest version
const current = await getResource(resource.id);
// Apply update
const updated = updateFn(current);
// Include version for optimistic locking
updated.version = current.version;
// Try to save
return await saveResource(updated);
} catch (error) {
if (error.status === 409 && error.details?.code === 'CONCURRENT_MODIFICATION') {
console.log(`Concurrent modification detected, retry ${i + 1}/${maxRetries}`);
// Add small delay before retry
await new Promise(resolve => setTimeout(resolve, 100 * (i + 1)));
} else {
throw error;
}
}
}
throw new Error('Failed to update resource after maximum retries');
}Scenario 4: Graceful Degradation
python
class GracefulDegradation:
def __init__(self):
self.cache = {}
self.fallback_data = {}
async def get_data_with_fallback(self, endpoint, params):
"""Get data with fallback options"""
try:
# Try primary request
return await self.api_client.get(endpoint, params)
except Exception as error:
# Try cache
cache_key = f"{endpoint}:{json.dumps(params)}"
if cache_key in self.cache:
print("Using cached data due to error")
return self.cache[cache_key]
# Try reduced functionality
if error.status == 503:
print("Service unavailable, using reduced functionality")
simplified_params = self.simplify_params(params)
return await self.api_client.get(endpoint, simplified_params)
# Use fallback data
if endpoint in self.fallback_data:
print("Using fallback data")
return self.fallback_data[endpoint]
# No fallback available
raise error
def simplify_params(self, params):
"""Reduce query complexity"""
simplified = params.copy()
# Remove optional parameters
optional_fields = ['includeArchived', 'metadata', 'expandRelations']
for field in optional_fields:
simplified.pop(field, None)
# Reduce page size
if 'pageSize' in simplified:
simplified['pageSize'] = min(simplified['pageSize'], 10)
return simplifiedError Recovery Best Practices
- Always log errors with context for debugging
- Implement retry logic for transient failures
- Use circuit breakers to prevent cascading failures
- Cache successful responses for fallback
- Provide meaningful error messages to users
- Track error patterns to identify systemic issues
- Test error scenarios in your integration tests
- Document error handling in your code
For authentication setup, see AuthenticationFor API endpoints, see API Reference
