API Codex
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:

jsonCode
{ "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)

CodeStatusDescription
200OKRequest successful
201CreatedResource created successfully
202AcceptedRequest accepted for processing
204No ContentRequest successful, no content to return

Client Error Codes (4xx)

CodeStatusDescriptionCommon Causes
400Bad RequestInvalid request parametersMissing required fields, invalid data types
401UnauthorizedAuthentication failedInvalid or missing API key
403ForbiddenAccess deniedSubscription expired, insufficient permissions
404Not FoundResource not foundInvalid endpoint, deleted resource
405Method Not AllowedHTTP method not supportedUsing POST on GET-only endpoint
422Unprocessable EntityRequest validation failedInvalid email format, domain doesn't exist
429Too Many RequestsRate limit exceededToo many requests in time window

Server Error Codes (5xx)

CodeStatusDescriptionAction Required
500Internal Server ErrorServer-side errorRetry with exponential backoff
502Bad GatewayInvalid upstream responseRetry after brief delay
503Service UnavailableService temporarily downCheck status page, retry later
504Gateway TimeoutRequest timeoutRetry with smaller payload

Common Error Scenarios

Authentication Errors

Invalid API Key

jsonCode
{ "error": "INVALID_API_KEY", "message": "The provided API key is invalid", "statusCode": 401 }

Solution:

javascriptCode
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

jsonCode
{ "error": "SUBSCRIPTION_EXPIRED", "message": "Your subscription has expired", "statusCode": 403 }

Solution:

javascriptCode
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

jsonCode
{ "error": "MISSING_PARAMETER", "message": "Required parameter 'domain' is missing", "statusCode": 400 }

Solution:

javascriptCode
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

jsonCode
{ "error": "INVALID_FORMAT", "message": "Invalid email format: 'not-an-email'", "statusCode": 422 }

Solution:

javascriptCode
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

javascriptCode
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

pythonCode
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

javascriptCode
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

javascriptCode
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

javascriptCode
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

javascriptCode
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

javascriptCode
// 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 ✅

  1. Always validate input before making API calls
  2. Implement retry logic with exponential backoff
  3. Log all errors for debugging and monitoring
  4. Use circuit breakers to prevent cascade failures
  5. Provide meaningful error messages to users
  6. Cache successful responses for fallback
  7. Monitor error rates and set up alerts

Don'ts ❌

  1. Don't ignore errors - Always handle them
  2. Don't retry infinitely - Set reasonable limits
  3. Don't expose sensitive information in error messages
  4. Don't retry non-retryable errors (4xx except 429)
  5. Don't break on non-critical errors - Degrade gracefully

Next Steps

Last modified on