February 8, 2025 32174b4 Edit this page

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)

CodeHTTP StatusDescription
invalid_request400Malformed request
authentication_required401Missing or invalid credentials
insufficient_permissions403Lacks required permissions
resource_not_found404Resource doesn’t exist
method_not_allowed405HTTP method not supported
rate_limit_exceeded429Too many requests

Server Errors (5xx)

CodeHTTP StatusDescription
internal_server_error500Unexpected server error
service_unavailable503Temporary service disruption
gateway_timeout504Upstream 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}")
        raise

JavaScript

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)
            }
        )
        raise

User-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

  1. Always include request IDs for debugging
  2. Log errors with context for troubleshooting
  3. Retry transient errors with exponential backoff
  4. Don’t retry client errors (4xx) automatically
  5. Show user-friendly messages to end users
  6. Monitor error rates to detect issues early
  7. Document error codes in your API documentation