API Guides
Error Handling Guide
Proper error handling is crucial for building reliable applications. This guide covers all error scenarios you might encounter when using API Codex APIs and how to handle them effectively.
Error Response Format
All API Codex APIs return consistent error responses:
Code
{ "error": "Error type or code", "message": "Human-readable error description", "details": "Additional context about the error", "timestamp": "2024-01-15T10:30:00Z" }
HTTP Status Codes
Success Codes (2xx)
Code | Status | 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 | Status | Description | Common Causes |
---|---|---|---|
400 | Bad Request | Invalid request parameters | Missing required fields, invalid data types |
401 | Unauthorized | Authentication failed | Invalid or missing API key |
403 | Forbidden | Access denied | Subscription expired, insufficient permissions |
404 | Not Found | Resource not found | Invalid endpoint, deleted resource |
405 | Method Not Allowed | HTTP method not supported | Using POST on GET-only endpoint |
422 | Unprocessable Entity | Request validation failed | Invalid email format, domain doesn't exist |
429 | Too Many Requests | Rate limit exceeded | Too many requests in time window |
Server Error Codes (5xx)
Code | Status | Description | Action Required |
---|---|---|---|
500 | Internal Server Error | Server-side error | Retry with exponential backoff |
502 | Bad Gateway | Invalid upstream response | Retry after brief delay |
503 | Service Unavailable | Service temporarily down | Check status page, retry later |
504 | Gateway Timeout | Request timeout | Retry with smaller payload |
Common Error Scenarios
Authentication Errors
Invalid API Key
Code
{ "error": "INVALID_API_KEY", "message": "The provided API key is invalid", "statusCode": 401 }
Solution:
Code
function handleAuthError(error) { if (error.statusCode === 401) { console.error('Authentication failed. Please check your API key.'); // Suggestions for debugging const checks = [ 'Verify API key is correct (no extra spaces)', 'Check if key is active in RapidAPI dashboard', 'Ensure subscription is active', 'Verify you\'re using the correct host header' ]; console.log('Debugging checklist:'); checks.forEach((check, i) => console.log(`${i + 1}. ${check}`)); } }
Expired Subscription
Code
{ "error": "SUBSCRIPTION_EXPIRED", "message": "Your subscription has expired", "statusCode": 403 }
Solution:
Code
async function handleSubscriptionError(error) { if (error.error === 'SUBSCRIPTION_EXPIRED') { console.error('Subscription expired. Please renew your plan.'); // Notify user or admin await notifyAdmin({ issue: 'API Subscription Expired', action: 'Renewal required', url: 'https://rapidapi.com/organization/apicodex' }); // Fallback to cached data if available return getCachedData(); } }
Validation Errors
Missing Required Parameters
Code
{ "error": "MISSING_PARAMETER", "message": "Required parameter 'domain' is missing", "statusCode": 400 }
Solution:
Code
class APIRequestValidator { constructor(requiredParams) { this.requiredParams = requiredParams; } validate(params) { const errors = []; for (const param of this.requiredParams) { if (!params[param]) { errors.push({ field: param, message: `Required parameter '${param}' is missing` }); } } if (errors.length > 0) { throw new ValidationError(errors); } return true; } } class ValidationError extends Error { constructor(errors) { super('Validation failed'); this.name = 'ValidationError'; this.errors = errors; } } // Usage const validator = new APIRequestValidator(['domain', 'type']); try { validator.validate({ domain: 'example.com' }); // Missing 'type' } catch (error) { if (error instanceof ValidationError) { console.error('Validation errors:', error.errors); } }
Invalid Data Format
Code
{ "error": "INVALID_FORMAT", "message": "Invalid email format: 'not-an-email'", "statusCode": 422 }
Solution:
Code
class DataValidator { static validateEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { throw new Error(`Invalid email format: ${email}`); } return true; } static validateDomain(domain) { const domainRegex = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/i; if (!domainRegex.test(domain)) { throw new Error(`Invalid domain format: ${domain}`); } return true; } static validateIP(ip) { const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/; const ipv6Regex = /^([\da-f]{1,4}:){7}[\da-f]{1,4}$/i; if (!ipv4Regex.test(ip) && !ipv6Regex.test(ip)) { throw new Error(`Invalid IP address: ${ip}`); } return true; } } // Pre-validate before API call try { DataValidator.validateEmail('[email protected]'); DataValidator.validateDomain('example.com'); DataValidator.validateIP('192.168.1.1'); // Make API call const response = await makeAPICall(params); } catch (error) { console.error('Validation error:', error.message); }
Rate Limiting Errors
See our comprehensive Rate Limiting Guide for detailed handling strategies.
Comprehensive Error Handler
JavaScript/Node.js
Code
class APIErrorHandler { constructor(options = {}) { this.maxRetries = options.maxRetries || 3; this.retryDelay = options.retryDelay || 1000; this.onError = options.onError || (() => {}); } async handleRequest(requestFn) { let lastError; for (let attempt = 0; attempt < this.maxRetries; attempt++) { try { const response = await requestFn(); if (!response.ok) { const error = await this.parseError(response); // Determine if error is retryable if (this.isRetryable(response.status)) { lastError = error; await this.wait(attempt); continue; } // Non-retryable error throw error; } return response; } catch (error) { lastError = error; // Network errors are retryable if (error.name === 'NetworkError' || error.code === 'ECONNREFUSED') { await this.wait(attempt); continue; } throw error; } } throw lastError; } async parseError(response) { let errorData; try { errorData = await response.json(); } catch { errorData = { message: response.statusText }; } const error = new APIError( errorData.message || 'Unknown error', response.status, errorData ); // Log error for monitoring this.logError(error); // Call custom error handler this.onError(error); return error; } isRetryable(statusCode) { // Retry on server errors and rate limiting return statusCode >= 500 || statusCode === 429; } async wait(attempt) { const delay = this.retryDelay * Math.pow(2, attempt); const jitter = Math.random() * 1000; await new Promise(resolve => setTimeout(resolve, delay + jitter)); } logError(error) { const logEntry = { timestamp: new Date().toISOString(), status: error.statusCode, message: error.message, data: error.data, stack: error.stack }; console.error('API Error:', JSON.stringify(logEntry, null, 2)); // Send to monitoring service if (typeof window === 'undefined') { // Server-side: Send to logging service this.sendToLoggingService(logEntry); } } sendToLoggingService(logEntry) { // Implement your logging service integration // Example: Sentry, LogRocket, DataDog, etc. } } class APIError extends Error { constructor(message, statusCode, data) { super(message); this.name = 'APIError'; this.statusCode = statusCode; this.data = data; } } // Usage const errorHandler = new APIErrorHandler({ maxRetries: 3, retryDelay: 1000, onError: (error) => { // Custom error handling if (error.statusCode === 401) { // Redirect to authentication window.location.href = '/auth'; } } }); const response = await errorHandler.handleRequest(() => fetch(apiUrl, { headers }) );
Python
Code
import time import json import logging from typing import Optional, Dict, Any, Callable from dataclasses import dataclass import requests from requests.exceptions import RequestException @dataclass class APIError(Exception): """Custom API error class""" message: str status_code: int data: Dict[str, Any] def __str__(self): return f"APIError({self.status_code}): {self.message}" class APIErrorHandler: """Comprehensive error handler for API requests""" def __init__( self, max_retries: int = 3, retry_delay: float = 1.0, on_error: Optional[Callable] = None ): self.max_retries = max_retries self.retry_delay = retry_delay self.on_error = on_error or (lambda e: None) self.logger = logging.getLogger(__name__) def handle_request(self, request_fn: Callable) -> requests.Response: """Execute request with error handling and retries""" last_error = None for attempt in range(self.max_retries): try: response = request_fn() if not response.ok: error = self.parse_error(response) if self.is_retryable(response.status_code): last_error = error self.wait(attempt) continue raise error return response except RequestException as e: last_error = APIError( message=str(e), status_code=0, data={'error': 'Network error'} ) self.logger.warning(f"Network error (attempt {attempt + 1}): {e}") self.wait(attempt) continue raise last_error def parse_error(self, response: requests.Response) -> APIError: """Parse error response into APIError""" try: error_data = response.json() except json.JSONDecodeError: error_data = {'message': response.text or response.reason} error = APIError( message=error_data.get('message', 'Unknown error'), status_code=response.status_code, data=error_data ) self.log_error(error) self.on_error(error) return error def is_retryable(self, status_code: int) -> bool: """Determine if error is retryable""" return status_code >= 500 or status_code == 429 def wait(self, attempt: int): """Wait with exponential backoff""" delay = self.retry_delay * (2 ** attempt) time.sleep(delay) def log_error(self, error: APIError): """Log error for monitoring""" self.logger.error( f"API Error: {error}", extra={ 'status_code': error.status_code, 'error_data': error.data } ) # Usage example def make_api_call(): return requests.get( 'https://advanced-dns-lookup-api.p.rapidapi.com/v1/check', headers={ 'x-rapidapi-key': API_KEY, 'x-rapidapi-host': 'advanced-dns-lookup-api.p.rapidapi.com' }, params={'name': 'example.com'} ) error_handler = APIErrorHandler( max_retries=3, on_error=lambda e: print(f"Custom handler: {e}") ) try: response = error_handler.handle_request(make_api_call) data = response.json() print("Success:", data) except APIError as e: print(f"API request failed: {e}")
Error Recovery Strategies
1. Graceful Degradation
Code
class GracefulAPIClient { constructor() { this.cache = new Map(); this.fallbackData = {}; } async getData(key, fetchFn) { try { // Try to fetch fresh data const data = await fetchFn(); // Cache successful response this.cache.set(key, { data, timestamp: Date.now() }); return data; } catch (error) { console.warn('API call failed, attempting fallback strategies'); // Strategy 1: Return cached data if available const cached = this.cache.get(key); if (cached) { console.log('Returning cached data'); return { ...cached.data, _cached: true, _cachedAt: cached.timestamp }; } // Strategy 2: Return fallback/default data if (this.fallbackData[key]) { console.log('Returning fallback data'); return { ...this.fallbackData[key], _fallback: true }; } // Strategy 3: Return minimal error response return { error: true, message: 'Service temporarily unavailable', retry: true }; } } setFallback(key, data) { this.fallbackData[key] = data; } }
2. Circuit Breaker Implementation
Code
class CircuitBreaker { constructor(options = {}) { this.failureThreshold = options.failureThreshold || 5; this.successThreshold = options.successThreshold || 2; this.timeout = options.timeout || 60000; this.state = 'CLOSED'; this.failures = 0; this.successes = 0; this.nextAttempt = Date.now(); } async execute(fn) { if (this.state === 'OPEN') { if (Date.now() < this.nextAttempt) { throw new Error('Circuit breaker is OPEN. Service unavailable.'); } this.state = 'HALF_OPEN'; console.log('Circuit breaker: Attempting recovery (HALF_OPEN)'); } try { const result = await fn(); return this.onSuccess(result); } catch (error) { return this.onFailure(error); } } onSuccess(result) { if (this.state === 'HALF_OPEN') { this.successes++; if (this.successes >= this.successThreshold) { this.state = 'CLOSED'; this.failures = 0; this.successes = 0; console.log('Circuit breaker: Service recovered (CLOSED)'); } } return result; } onFailure(error) { this.failures++; if (this.state === 'HALF_OPEN') { this.state = 'OPEN'; this.nextAttempt = Date.now() + this.timeout; console.log(`Circuit breaker: Recovery failed (OPEN until ${new Date(this.nextAttempt)})`); } else if (this.failures >= this.failureThreshold) { this.state = 'OPEN'; this.nextAttempt = Date.now() + this.timeout; console.log(`Circuit breaker: Too many failures (OPEN until ${new Date(this.nextAttempt)})`); } throw error; } getState() { return { state: this.state, failures: this.failures, successes: this.successes, nextAttempt: this.state === 'OPEN' ? new Date(this.nextAttempt) : null }; } }
3. Retry with Jitter
Code
class RetryWithJitter { constructor(options = {}) { this.maxRetries = options.maxRetries || 3; this.baseDelay = options.baseDelay || 1000; this.maxDelay = options.maxDelay || 30000; this.jitterRange = options.jitterRange || 0.3; } async execute(fn) { let lastError; for (let attempt = 0; attempt < this.maxRetries; attempt++) { try { return await fn(); } catch (error) { lastError = error; if (attempt < this.maxRetries - 1) { const delay = this.calculateDelay(attempt); console.log(`Retry attempt ${attempt + 1} after ${delay}ms`); await this.sleep(delay); } } } throw lastError; } calculateDelay(attempt) { // Exponential backoff let delay = Math.min(this.baseDelay * Math.pow(2, attempt), this.maxDelay); // Add jitter (±30% by default) const jitter = delay * this.jitterRange * (Math.random() * 2 - 1); delay = Math.round(delay + jitter); return Math.max(0, delay); } sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } }
Error Monitoring
Custom Error Logger
Code
class ErrorMonitor { constructor() { this.errors = []; this.metrics = { total: 0, byStatus: {}, byType: {} }; } logError(error) { const errorEntry = { timestamp: Date.now(), message: error.message, statusCode: error.statusCode || 0, type: error.name || 'Unknown', stack: error.stack, context: this.getContext() }; this.errors.push(errorEntry); this.updateMetrics(errorEntry); // Keep only last 1000 errors if (this.errors.length > 1000) { this.errors.shift(); } // Send to external monitoring if configured this.sendToMonitoring(errorEntry); } updateMetrics(error) { this.metrics.total++; // Count by status code const status = error.statusCode.toString(); this.metrics.byStatus[status] = (this.metrics.byStatus[status] || 0) + 1; // Count by error type this.metrics.byType[error.type] = (this.metrics.byType[error.type] || 0) + 1; } getContext() { return { userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'Node.js', timestamp: new Date().toISOString(), environment: process.env.NODE_ENV || 'development' }; } sendToMonitoring(error) { // Integrate with monitoring services if (typeof window !== 'undefined' && window.Sentry) { window.Sentry.captureException(error); } // Custom webhook if (process.env.ERROR_WEBHOOK_URL) { fetch(process.env.ERROR_WEBHOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(error) }).catch(console.error); } } getReport() { const now = Date.now(); const hour = 3600000; const recentErrors = this.errors.filter(e => now - e.timestamp < hour); return { totalErrors: this.metrics.total, lastHour: recentErrors.length, byStatus: this.metrics.byStatus, byType: this.metrics.byType, recentErrors: recentErrors.slice(-10) }; } } // Global error monitor const errorMonitor = new ErrorMonitor(); // Integrate with error handler window.addEventListener('unhandledrejection', event => { errorMonitor.logError({ message: event.reason.message || 'Unhandled promise rejection', statusCode: 0, name: 'UnhandledRejection', stack: event.reason.stack }); });
Testing Error Scenarios
Unit Testing Errors
Code
// Jest example describe('Error Handler', () => { it('should retry on 500 errors', async () => { const mockFetch = jest.fn() .mockRejectedValueOnce({ status: 500 }) .mockRejectedValueOnce({ status: 500 }) .mockResolvedValueOnce({ ok: true, data: 'success' }); const handler = new APIErrorHandler({ maxRetries: 3 }); const result = await handler.handleRequest(mockFetch); expect(mockFetch).toHaveBeenCalledTimes(3); expect(result.data).toBe('success'); }); it('should not retry on 400 errors', async () => { const mockFetch = jest.fn() .mockRejectedValueOnce({ status: 400, message: 'Bad Request' }); const handler = new APIErrorHandler({ maxRetries: 3 }); await expect(handler.handleRequest(mockFetch)) .rejects.toThrow('Bad Request'); expect(mockFetch).toHaveBeenCalledTimes(1); }); });
Best Practices
Do's ✅
- Always validate input before making API calls
- Implement retry logic with exponential backoff
- Log all errors for debugging and monitoring
- Use circuit breakers to prevent cascade failures
- Provide meaningful error messages to users
- Cache successful responses for fallback
- Monitor error rates and set up alerts
Don'ts ❌
- Don't ignore errors - Always handle them
- Don't retry infinitely - Set reasonable limits
- Don't expose sensitive information in error messages
- Don't retry non-retryable errors (4xx except 429)
- Don't break on non-critical errors - Degrade gracefully
Next Steps
- Review Rate Limiting for handling 429 errors
- Learn Authentication best practices
- Explore Best Practices for production
- Browse our API Catalog to start building
Last modified on