Error Handling
Comprehensive guide to handling API errors and troubleshooting common issues
Error Handling
This guide covers all possible error scenarios, response formats, and troubleshooting steps for the invoice management system API.
Error Response Format
All API errors follow a consistent JSON format:
{
  "error": "Error Type",
  "message": "Human-readable error description",
  "details": {
    "field1": ["Specific error message"],
    "field2": ["Another error message"]
  }
}HTTP Status Codes
400 Bad Request
Returned when the request contains invalid data or missing required fields.
{
  "error": "Validation Error",
  "message": "The request contains invalid data",
  "details": {
    "email": ["Email is required", "Email format is invalid"],
    "name": ["Name must be at least 2 characters"]
  }
}Common Causes:
- Missing required fields
- Invalid data formats
- Constraint violations
- Invalid query parameters
401 Unauthorized
Returned when authentication fails or API key is invalid.
{
  "error": "Unauthorized",
  "message": "Invalid or missing API key"
}Common Causes:
- Missing X-Invois-Keyheader
- Invalid API key
- Expired API key
- Incorrect header format
404 Not Found
Returned when the requested resource doesn't exist.
{
  "error": "Not Found",
  "message": "User not found"
}Common Causes:
- Invalid resource ID
- Resource has been deleted
- Incorrect endpoint URL
- Typos in path parameters
409 Conflict
Returned when the request conflicts with existing data.
{
  "error": "Conflict",
  "message": "Email already exists",
  "details": {
    "email": ["This email is already registered"]
  }
}Common Causes:
- Duplicate email addresses
- Duplicate organization slugs
- Constraint violations
- Race conditions
429 Too Many Requests
Returned when rate limits are exceeded.
{
  "error": "Rate Limit Exceeded",
  "message": "Too many requests. Please try again later.",
  "details": {
    "retry_after": 60,
    "limit": 1000,
    "remaining": 0,
    "reset_time": "2024-01-15T15:30:00Z"
  }
}Response Headers:
- X-RateLimit-Limit: Maximum requests allowed
- X-RateLimit-Remaining: Requests remaining in current window
- X-RateLimit-Reset: Time when limit resets
- Retry-After: Seconds to wait before retrying
500 Internal Server Error
Returned when an unexpected server error occurs.
{
  "error": "Internal Server Error",
  "message": "An unexpected error occurred. Please try again later."
}Error Handling Examples
JavaScript/TypeScript
async function handleApiRequest(url, options) {
  try {
    const response = await fetch(url, {
      ...options,
      headers: {
        'X-Invois-Key': 'your-api-key',
        'Content-Type': 'application/json',
        ...options.headers
      }
    });
    if (!response.ok) {
      const errorData = await response.json();
      
      switch (response.status) {
        case 400:
          console.error('Validation errors:', errorData.details);
          // Handle validation errors
          break;
        case 401:
          console.error('Authentication failed:', errorData.message);
          // Redirect to login or refresh API key
          break;
        case 404:
          console.error('Resource not found:', errorData.message);
          // Handle missing resource
          break;
        case 409:
          console.error('Conflict:', errorData.message);
          // Handle duplicate data
          break;
        case 429:
          console.error('Rate limit exceeded. Retry after:', errorData.details.retry_after);
          // Implement retry logic
          break;
        default:
          console.error('API error:', errorData.message);
      }
      
      throw new Error(errorData.message);
    }
    return await response.json();
  } catch (error) {
    console.error('Request failed:', error);
    throw error;
  }
}Python
import requests
import time
from typing import Dict, Any
def handle_api_request(url: str, method: str = 'GET', data: Dict[Any, Any] = None) -> Dict[Any, Any]:
    headers = {
        'X-Invois-Key': 'your-api-key',
        'Content-Type': 'application/json'
    }
    
    try:
        response = requests.request(method, url, json=data, headers=headers)
        
        if response.status_code == 429:
            # Handle rate limiting with exponential backoff
            retry_after = response.json().get('details', {}).get('retry_after', 60)
            print(f"Rate limited. Waiting {retry_after} seconds...")
            time.sleep(retry_after)
            return handle_api_request(url, method, data)  # Retry
        
        response.raise_for_status()
        return response.json()
        
    except requests.exceptions.HTTPError as e:
        error_data = response.json()
        
        if response.status_code == 400:
            print("Validation errors:", error_data.get('details', {}))
        elif response.status_code == 401:
            print("Authentication failed:", error_data.get('message'))
        elif response.status_code == 404:
            print("Resource not found:", error_data.get('message'))
        elif response.status_code == 409:
            print("Conflict:", error_data.get('message'))
        
        raise Exception(error_data.get('message', 'API request failed'))
    
    except requests.exceptions.RequestException as e:
        print(f"Request failed: {e}")
        raisePHP
<?php
function handleApiRequest($url, $method = 'GET', $data = null) {
    $headers = [
        'X-Invois-Key: your-api-key',
        'Content-Type: application/json'
    ];
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
    
    if ($data) {
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    }
    
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    
    $responseData = json_decode($response, true);
    
    if ($httpCode >= 400) {
        switch ($httpCode) {
            case 400:
                error_log('Validation errors: ' . print_r($responseData['details'], true));
                break;
            case 401:
                error_log('Authentication failed: ' . $responseData['message']);
                break;
            case 404:
                error_log('Resource not found: ' . $responseData['message']);
                break;
            case 409:
                error_log('Conflict: ' . $responseData['message']);
                break;
            case 429:
                $retryAfter = $responseData['details']['retry_after'] ?? 60;
                error_log("Rate limited. Waiting {$retryAfter} seconds...");
                sleep($retryAfter);
                return handleApiRequest($url, $method, $data); // Retry
        }
        
        throw new Exception($responseData['message'] ?? 'API request failed');
    }
    
    return $responseData;
}
?>Retry Logic and Best Practices
Exponential Backoff
For rate limiting and temporary errors, implement exponential backoff:
async function retryWithBackoff(fn, maxRetries = 3, baseDelay = 1000) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxRetries - 1) throw error;
      
      const delay = baseDelay * Math.pow(2, attempt);
      console.log(`Attempt ${attempt + 1} failed. Retrying in ${delay}ms...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}Rate Limit Handling
class ApiClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.rateLimitReset = null;
  }
  
  async request(url, options = {}) {
    // Check if we're still rate limited
    if (this.rateLimitReset && Date.now() < this.rateLimitReset) {
      const waitTime = this.rateLimitReset - Date.now();
      console.log(`Waiting ${waitTime}ms for rate limit reset...`);
      await new Promise(resolve => setTimeout(resolve, waitTime));
    }
    
    const response = await fetch(url, {
      ...options,
      headers: {
        'X-Invois-Key': this.apiKey,
        'Content-Type': 'application/json',
        ...options.headers
      }
    });
    
    // Update rate limit info from headers
    const remaining = response.headers.get('X-RateLimit-Remaining');
    const reset = response.headers.get('X-RateLimit-Reset');
    
    if (reset) {
      this.rateLimitReset = new Date(reset).getTime();
    }
    
    if (response.status === 429) {
      const errorData = await response.json();
      const retryAfter = errorData.details?.retry_after * 1000 || 60000;
      this.rateLimitReset = Date.now() + retryAfter;
      throw new Error('Rate limit exceeded');
    }
    
    return response;
  }
}Troubleshooting Common Issues
Authentication Problems
Issue: Getting 401 Unauthorized errors Solutions:
- Verify API key is correct and active
- Check that X-Invois-Keyheader is properly set
- Ensure API key has necessary permissions
- Contact administrator if key appears invalid
Validation Errors
Issue: Getting 400 Bad Request with validation details Solutions:
- Check all required fields are provided
- Verify data formats match API specifications
- Ensure string lengths meet requirements
- Validate enum values are from allowed sets
Rate Limiting
Issue: Getting 429 Too Many Requests errors Solutions:
- Implement exponential backoff retry logic
- Respect Retry-Afterheader values
- Monitor rate limit headers to avoid limits
- Consider caching responses to reduce API calls
Resource Not Found
Issue: Getting 404 Not Found errors Solutions:
- Verify resource IDs are correct
- Check that resources haven't been deleted
- Ensure proper URL construction
- Validate path parameters
Monitoring and Logging
Request Logging
Log all API requests and responses for debugging:
function logApiCall(url, method, requestData, response, error) {
  const logEntry = {
    timestamp: new Date().toISOString(),
    url,
    method,
    requestData,
    responseStatus: response?.status,
    responseData: response?.data,
    error: error?.message
  };
  
  console.log('API Call:', JSON.stringify(logEntry, null, 2));
}Error Tracking
Implement error tracking to monitor API issues:
function trackApiError(error, context) {
  // Send to error tracking service (e.g., Sentry, Bugsnag)
  errorTracker.captureException(error, {
    tags: {
      component: 'api-client',
      endpoint: context.url
    },
    extra: context
  });
}Next Steps
- Rate Limiting - Understand API limits
- Authentication - Review authentication setup
- Code Examples - See complete implementation examples