Technical15 min read

API Integration Best Practices: Complete Developer Guide with Real Examples

Master API integration with authentication, rate limiting, error handling, and performance optimization. Includes code examples and production patterns.

Published March 31, 2026

API integration done right accelerates development and ensures reliable applications. This guide covers authentication strategies, error handling patterns, performance optimization, and real-world integration examples from our experience building email testing tools.

API Integration Fundamentals

RESTful API Design Principles

Understanding REST principles is essential for effective API integration:

  • Resource-based URLs: /api/users/123 not /api/getUser?id=123
  • HTTP methods for actions: GET (read), POST (create), PUT/PATCH (update), DELETE (remove)
  • Stateless requests: Each request contains all necessary information
  • Standard HTTP status codes: 200 (success), 400 (client error), 500 (server error)
  • JSON format for data exchange with consistent structure
  • HATEOAS: Include links to related resources in responses

API Client Architecture

// Robust API client implementation

class APIClient {
  constructor(baseURL, options = {}) {
    this.baseURL = baseURL;
    this.timeout = options.timeout || 30000;
    this.retryAttempts = options.retryAttempts || 3;
    this.retryDelay = options.retryDelay || 1000;
    this.defaultHeaders = {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
      ...options.headers
    };
    this.interceptors = {
      request: [],
      response: []
    };
  }
  
  // Add request interceptor
  addRequestInterceptor(interceptor) {
    this.interceptors.request.push(interceptor);
  }
  
  // Add response interceptor
  addResponseInterceptor(interceptor) {
    this.interceptors.response.push(interceptor);
  }
  
  // Execute request with retry logic
  async request(endpoint, options = {}) {
    const url = `${this.baseURL}${endpoint}`;
    let config = {
      method: 'GET',
      headers: { ...this.defaultHeaders },
      timeout: this.timeout,
      ...options
    };
    
    // Apply request interceptors
    for (const interceptor of this.interceptors.request) {
      config = await interceptor(config);
    }
    
    let lastError;
    
    for (let attempt = 0; attempt <= this.retryAttempts; attempt++) {
      try {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), config.timeout);
        
        const response = await fetch(url, {
          ...config,
          signal: controller.signal
        });
        
        clearTimeout(timeoutId);
        
        // Apply response interceptors
        let processedResponse = response;
        for (const interceptor of this.interceptors.response) {
          processedResponse = await interceptor(processedResponse);
        }
        
        return processedResponse;
        
      } catch (error) {
        lastError = error;
        
        // Don't retry on client errors (4xx)
        if (error.response && error.response.status >= 400 && error.response.status < 500) {
          throw error;
        }
        
        // Wait before retry (exponential backoff)
        if (attempt < this.retryAttempts) {
          await this.delay(this.retryDelay * Math.pow(2, attempt));
        }
      }
    }
    
    throw lastError;
  }
  
  // Helper methods for common HTTP methods
  get(endpoint, params = {}) {
    const queryString = new URLSearchParams(params).toString();
    const url = queryString ? `${endpoint}?${queryString}` : endpoint;
    return this.request(url, { method: 'GET' });
  }
  
  post(endpoint, data = {}) {
    return this.request(endpoint, {
      method: 'POST',
      body: JSON.stringify(data)
    });
  }
  
  put(endpoint, data = {}) {
    return this.request(endpoint, {
      method: 'PUT',
      body: JSON.stringify(data)
    });
  }
  
  delete(endpoint) {
    return this.request(endpoint, { method: 'DELETE' });
  }
  
  delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

Authentication Strategies

API Key Authentication

Most common for service-to-service communication:

// API Key authentication implementation

class APIKeyAuth {
  constructor(apiKey, headerName = 'X-API-Key') {
    this.apiKey = apiKey;
    this.headerName = headerName;
  }
  
  // Request interceptor for API key
  requestInterceptor(config) {
    config.headers[this.headerName] = this.apiKey;
    return config;
  }
}

// Usage example
const client = new APIClient('https://api.example.com');
const auth = new APIKeyAuth('your-api-key-here');
client.addRequestInterceptor(auth.requestInterceptor.bind(auth));

// Alternative: Query parameter authentication
class QueryParamAuth {
  constructor(apiKey, paramName = 'api_key') {
    this.apiKey = apiKey;
    this.paramName = paramName;
  }
  
  requestInterceptor(config) {
    const url = new URL(config.url);
    url.searchParams.set(this.paramName, this.apiKey);
    config.url = url.toString();
    return config;
  }
}

Bearer Token Authentication

// Bearer token with refresh logic

class BearerTokenAuth {
  constructor(tokenEndpoint, credentials) {
    this.tokenEndpoint = tokenEndpoint;
    this.credentials = credentials;
    this.accessToken = null;
    this.refreshToken = null;
    this.tokenExpiry = null;
  }
  
  // Get valid access token
  async getAccessToken() {
    if (!this.accessToken || this.isTokenExpired()) {
      await this.refreshAccessToken();
    }
    return this.accessToken;
  }
  
  // Check if token is expired
  isTokenExpired() {
    return !this.tokenExpiry || Date.now() >= this.tokenExpiry;
  }
  
  // Refresh access token
  async refreshAccessToken() {
    try {
      const response = await fetch(this.tokenEndpoint, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          grant_type: 'client_credentials',
          ...this.credentials
        })
      });
      
      if (!response.ok) {
        throw new Error(`Token refresh failed: ${response.status}`);
      }
      
      const tokenData = await response.json();
      this.accessToken = tokenData.access_token;
      this.refreshToken = tokenData.refresh_token;
      
      // Calculate expiry time (subtract buffer for safety)
      const expiresIn = tokenData.expires_in || 3600;
      this.tokenExpiry = Date.now() + (expiresIn - 300) * 1000;
      
    } catch (error) {
      throw new Error(`Authentication failed: ${error.message}`);
    }
  }
  
  // Request interceptor
  async requestInterceptor(config) {
    const token = await this.getAccessToken();
    config.headers['Authorization'] = `Bearer ${token}`;
    return config;
  }
  
  // Response interceptor to handle auth errors
  async responseInterceptor(response) {
    if (response.status === 401) {
      // Token expired, refresh and retry
      this.accessToken = null;
      throw new Error('Authentication expired');
    }
    return response;
  }
}

OAuth 2.0 Implementation

// OAuth 2.0 Authorization Code Flow

class OAuth2Client {
  constructor(clientId, clientSecret, redirectUri, authEndpoint, tokenEndpoint) {
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.redirectUri = redirectUri;
    this.authEndpoint = authEndpoint;
    this.tokenEndpoint = tokenEndpoint;
    this.scopes = [];
  }
  
  // Generate authorization URL
  getAuthorizationUrl(scopes = [], state = null) {
    const params = new URLSearchParams({
      response_type: 'code',
      client_id: this.clientId,
      redirect_uri: this.redirectUri,
      scope: scopes.join(' '),
      state: state || this.generateState()
    });
    
    return `${this.authEndpoint}?${params.toString()}`;
  }
  
  // Exchange authorization code for access token
  async exchangeCodeForToken(code, state = null) {
    try {
      const response = await fetch(this.tokenEndpoint, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'Authorization': `Basic ${btoa(`${this.clientId}:${this.clientSecret}`)}`
        },
        body: new URLSearchParams({
          grant_type: 'authorization_code',
          code: code,
          redirect_uri: this.redirectUri
        })
      });
      
      if (!response.ok) {
        throw new Error(`Token exchange failed: ${response.status}`);
      }
      
      const tokenData = await response.json();
      return {
        access_token: tokenData.access_token,
        refresh_token: tokenData.refresh_token,
        expires_in: tokenData.expires_in,
        token_type: tokenData.token_type,
        scope: tokenData.scope
      };
      
    } catch (error) {
      throw new Error(`OAuth token exchange failed: ${error.message}`);
    }
  }
  
  // Generate secure random state parameter
  generateState() {
    const array = new Uint8Array(32);
    crypto.getRandomValues(array);
    return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
  }
}

Rate Limiting and Throttling

Client-Side Rate Limiting

// Token bucket rate limiter

class TokenBucket {
  constructor(capacity, refillRate, refillPeriod = 1000) {
    this.capacity = capacity;
    this.tokens = capacity;
    this.refillRate = refillRate;
    this.refillPeriod = refillPeriod;
    this.lastRefill = Date.now();
    this.queue = [];
  }
  
  // Consume tokens
  async consume(tokens = 1) {
    this.refill();
    
    if (this.tokens >= tokens) {
      this.tokens -= tokens;
      return Promise.resolve();
    }
    
    // Queue the request
    return new Promise((resolve) => {
      this.queue.push({ tokens, resolve });
      this.processQueue();
    });
  }
  
  // Refill tokens based on time elapsed
  refill() {
    const now = Date.now();
    const timePassed = now - this.lastRefill;
    const tokensToAdd = Math.floor((timePassed / this.refillPeriod) * this.refillRate);
    
    if (tokensToAdd > 0) {
      this.tokens = Math.min(this.capacity, this.tokens + tokensToAdd);
      this.lastRefill = now;
    }
  }
  
  // Process queued requests
  processQueue() {
    while (this.queue.length > 0) {
      this.refill();
      const request = this.queue[0];
      
      if (this.tokens >= request.tokens) {
        this.tokens -= request.tokens;
        this.queue.shift();
        request.resolve();
      } else {
        // Wait and try again
        setTimeout(() => this.processQueue(), 100);
        break;
      }
    }
  }
}

// Rate limiting API client
class RateLimitedAPIClient extends APIClient {
  constructor(baseURL, options = {}) {
    super(baseURL, options);
    
    // Rate limiter configuration
    const rateLimit = options.rateLimit || { capacity: 100, refillRate: 10 };
    this.rateLimiter = new TokenBucket(
      rateLimit.capacity,
      rateLimit.refillRate,
      rateLimit.refillPeriod
    );
    
    // Add rate limiting to requests
    this.addRequestInterceptor(this.rateLimitInterceptor.bind(this));
  }
  
  async rateLimitInterceptor(config) {
    await this.rateLimiter.consume(config.cost || 1);
    return config;
  }
}

Handling Rate Limit Responses

// Handle rate limit responses from server

class RateLimitHandler {
  constructor(client) {
    this.client = client;
    this.retryQueue = new Map();
  }
  
  // Response interceptor for rate limit handling
  async responseInterceptor(response) {
    if (response.status === 429) {
      const retryAfter = this.getRetryAfter(response);
      const requestId = this.generateRequestId(response.url);
      
      // Add to retry queue
      return new Promise((resolve, reject) => {
        this.retryQueue.set(requestId, {
          resolve,
          reject,
          retryAfter,
          originalRequest: response.config
        });
        
        this.scheduleRetry(requestId);
      });
    }
    
    return response;
  }
  
  // Extract retry-after header
  getRetryAfter(response) {
    const retryAfter = response.headers.get('Retry-After');
    
    if (retryAfter) {
      // Could be seconds or HTTP date
      const seconds = parseInt(retryAfter, 10);
      return isNaN(seconds) ? 
        new Date(retryAfter).getTime() - Date.now() :
        seconds * 1000;
    }
    
    // Fallback to exponential backoff
    return Math.pow(2, this.getRetryCount(response.url)) * 1000;
  }
  
  // Schedule retry for rate-limited request
  scheduleRetry(requestId) {
    const request = this.retryQueue.get(requestId);
    if (!request) return;
    
    setTimeout(async () => {
      try {
        const response = await this.client.request(
          request.originalRequest.url,
          request.originalRequest
        );
        request.resolve(response);
      } catch (error) {
        request.reject(error);
      } finally {
        this.retryQueue.delete(requestId);
      }
    }, request.retryAfter);
  }
  
  generateRequestId(url) {
    return `${url}-${Date.now()}`;
  }
  
  getRetryCount(url) {
    // Implementation depends on how you want to track retries
    return 1; // Simplified
  }
}

Error Handling Strategies

Comprehensive Error Classification

// API error classification and handling

class APIError extends Error {
  constructor(message, status, code, details = {}) {
    super(message);
    this.name = 'APIError';
    this.status = status;
    this.code = code;
    this.details = details;
    this.timestamp = new Date().toISOString();
    this.retryable = this.isRetryable();
  }
  
  isRetryable() {
    // Network errors and server errors are generally retryable
    if (this.status >= 500) return true;
    if (this.status === 429) return true; // Rate limited
    if (this.status === 408) return true; // Timeout
    if (this.status === 0) return true;   // Network error
    
    // Client errors are generally not retryable
    if (this.status >= 400 && this.status < 500) {
      // Exception: authentication errors might be retryable if token refresh is possible
      return this.status === 401;
    }
    
    return false;
  }
  
  toJSON() {
    return {
      name: this.name,
      message: this.message,
      status: this.status,
      code: this.code,
      details: this.details,
      timestamp: this.timestamp,
      retryable: this.retryable
    };
  }
}

// Error handler with different strategies
class APIErrorHandler {
  constructor(options = {}) {
    this.logErrors = options.logErrors !== false;
    this.trackErrors = options.trackErrors !== false;
    this.userNotification = options.userNotification || this.defaultUserNotification;
  }
  
  async handleError(error, context = {}) {
    const apiError = this.normalizeError(error);
    
    // Log error for debugging
    if (this.logErrors) {
      this.logError(apiError, context);
    }
    
    // Track error for analytics
    if (this.trackErrors) {
      this.trackError(apiError, context);
    }
    
    // Determine user-facing response
    const userMessage = this.getUserMessage(apiError);
    
    return {
      error: apiError,
      userMessage,
      shouldRetry: apiError.retryable,
      context
    };
  }
  
  normalizeError(error) {
    if (error instanceof APIError) {
      return error;
    }
    
    // Network errors
    if (error.name === 'NetworkError' || error.code === 'NETWORK_ERROR') {
      return new APIError(
        'Network connection failed',
        0,
        'NETWORK_ERROR',
        { originalError: error.message }
      );
    }
    
    // Timeout errors
    if (error.name === 'TimeoutError' || error.code === 'TIMEOUT') {
      return new APIError(
        'Request timed out',
        408,
        'TIMEOUT',
        { originalError: error.message }
      );
    }
    
    // Generic error
    return new APIError(
      error.message || 'Unknown error occurred',
      500,
      'INTERNAL_ERROR',
      { originalError: error }
    );
  }
  
  getUserMessage(apiError) {
    const messages = {
      400: 'Invalid request. Please check your input and try again.',
      401: 'Authentication failed. Please check your credentials.',
      403: 'Access denied. You do not have permission for this action.',
      404: 'Resource not found. The requested item does not exist.',
      408: 'Request timed out. Please try again.',
      429: 'Too many requests. Please wait a moment and try again.',
      500: 'Server error. We are working to fix this issue.',
      503: 'Service temporarily unavailable. Please try again later.',
      NETWORK_ERROR: 'Connection failed. Please check your internet connection.',
      TIMEOUT: 'Request timed out. Please try again.'
    };
    
    return messages[apiError.status] ||
           messages[apiError.code] ||
           'An unexpected error occurred. Please try again.';
  }
  
  logError(error, context) {
    console.error('API Error:', {
      ...error.toJSON(),
      context,
      stack: error.stack
    });
  }
  
  trackError(error, context) {
    // Send to error tracking service (Sentry, Bugsnag, etc.)
    if (typeof window !== 'undefined' && window.gtag) {
      window.gtag('event', 'exception', {
        description: `${error.status}: ${error.message}`,
        fatal: false,
        custom_parameter: JSON.stringify({
          ...error.toJSON(),
          context
        })
      });
    }
  }
  
  defaultUserNotification(message, type = 'error') {
    // Default implementation - you might use toast notifications, etc.
    console.warn('User notification:', { message, type });
  }
}

Performance Optimization

Request Optimization Strategies

// Request optimization and caching

class OptimizedAPIClient extends APIClient {
  constructor(baseURL, options = {}) {
    super(baseURL, options);
    
    // Initialize caching
    this.cache = new Map();
    this.cacheTTL = options.cacheTTL || 300000; // 5 minutes default
    
    // Request deduplication
    this.pendingRequests = new Map();
    
    // Batch processing
    this.batchQueue = [];
    this.batchDelay = options.batchDelay || 50;
    this.maxBatchSize = options.maxBatchSize || 10;
  }
  
  // Cached GET request
  async getCached(endpoint, params = {}, cacheTTL = this.cacheTTL) {
    const cacheKey = this.generateCacheKey(endpoint, params);
    const cached = this.cache.get(cacheKey);
    
    if (cached && Date.now() - cached.timestamp < cacheTTL) {
      return cached.data;
    }
    
    const response = await this.get(endpoint, params);
    
    // Cache successful responses
    if (response.ok) {
      const data = await response.json();
      this.cache.set(cacheKey, {
        data: { ...response, json: () => Promise.resolve(data) },
        timestamp: Date.now()
      });
      return response;
    }
    
    return response;
  }
  
  // Request deduplication
  async getDeduped(endpoint, params = {}) {
    const requestKey = this.generateCacheKey(endpoint, params);
    
    // If same request is already pending, return the same promise
    if (this.pendingRequests.has(requestKey)) {
      return this.pendingRequests.get(requestKey);
    }
    
    // Create new request
    const promise = this.get(endpoint, params)
      .finally(() => {
        this.pendingRequests.delete(requestKey);
      });
    
    this.pendingRequests.set(requestKey, promise);
    return promise;
  }
  
  // Batch multiple requests
  async batch(requests) {
    if (requests.length === 0) return [];
    if (requests.length === 1) return [await this.request(requests[0].endpoint, requests[0].options)];
    
    // Check if API supports batch endpoints
    if (this.supportsBatch(requests)) {
      return this.sendBatchRequest(requests);
    }
    
    // Parallel execution with concurrency limit
    const concurrencyLimit = 5;
    const results = [];
    
    for (let i = 0; i < requests.length; i += concurrencyLimit) {
      const batch = requests.slice(i, i + concurrencyLimit);
      const batchPromises = batch.map(req => 
        this.request(req.endpoint, req.options).catch(error => ({ error }))
      );
      const batchResults = await Promise.all(batchPromises);
      results.push(...batchResults);
    }
    
    return results;
  }
  
  // Auto-batching for similar requests
  async autoBatch(endpoint, options = {}) {
    return new Promise((resolve, reject) => {
      this.batchQueue.push({ endpoint, options, resolve, reject });
      
      // Process batch after delay or when max size reached
      if (this.batchQueue.length >= this.maxBatchSize) {
        this.processBatchQueue();
      } else if (this.batchQueue.length === 1) {
        setTimeout(() => this.processBatchQueue(), this.batchDelay);
      }
    });
  }
  
  async processBatchQueue() {
    if (this.batchQueue.length === 0) return;
    
    const queue = [...this.batchQueue];
    this.batchQueue = [];
    
    try {
      const results = await this.batch(queue);
      
      queue.forEach((request, index) => {
        const result = results[index];
        if (result.error) {
          request.reject(result.error);
        } else {
          request.resolve(result);
        }
      });
    } catch (error) {
      queue.forEach(request => request.reject(error));
    }
  }
  
  supportsBatch(requests) {
    // Check if all requests are similar and API supports batching
    const firstEndpoint = requests[0].endpoint.split('/')[0];
    return requests.every(req => 
      req.endpoint.split('/')[0] === firstEndpoint &&
      req.options?.method === 'GET'
    );
  }
  
  async sendBatchRequest(requests) {
    // Implementation depends on your API's batch format
    const batchPayload = {
      requests: requests.map(req => ({
        method: req.options?.method || 'GET',
        url: req.endpoint,
        params: req.options?.params
      }))
    };
    
    const response = await this.post('/batch', batchPayload);
    const data = await response.json();
    
    return data.responses || [];
  }
  
  generateCacheKey(endpoint, params) {
    const paramString = new URLSearchParams(params).toString();
    return `${endpoint}${paramString ? `?${paramString}` : ''}`;
  }
  
  // Clear expired cache entries
  clearExpiredCache() {
    const now = Date.now();
    for (const [key, value] of this.cache.entries()) {
      if (now - value.timestamp > this.cacheTTL) {
        this.cache.delete(key);
      }
    }
  }
}

Real-World Integration Examples

Email Testing API Integration

// Complete email testing API client

class EmailTestingAPI {
  constructor(apiKey, options = {}) {
    this.client = new OptimizedAPIClient('https://api.whatismyip.io', {
      timeout: 30000,
      retryAttempts: 3,
      cacheTTL: 300000, // 5 minutes for most lookups
      ...options
    });
    
    // Add authentication
    this.client.addRequestInterceptor((config) => {
      config.headers['X-API-Key'] = apiKey;
      return config;
    });
    
    // Add error handling
    this.errorHandler = new APIErrorHandler({
      logErrors: true,
      trackErrors: true
    });
    
    this.client.addResponseInterceptor(async (response) => {
      if (!response.ok) {
        const error = await this.parseErrorResponse(response);
        throw error;
      }
      return response;
    });
  }
  
  // DMARC record checking
  async checkDMARC(domain, options = {}) {
    try {
      const response = await this.client.getCached(
        '/api/dmarc-check',
        { domain, ...options },
        60000 // Cache for 1 minute only (DNS changes)
      );
      
      const data = await response.json();
      return {
        domain,
        dmarc_record: data.dmarc_record,
        policy: data.policy,
        subdomain_policy: data.subdomain_policy,
        alignment: data.alignment,
        valid: data.valid,
        warnings: data.warnings || [],
        recommendations: data.recommendations || []
      };
      
    } catch (error) {
      const handled = await this.errorHandler.handleError(error, {
        operation: 'checkDMARC',
        domain
      });
      throw handled.error;
    }
  }
  
  // SPF record validation
  async validateSPF(domain, ip = null) {
    try {
      const params = { domain };
      if (ip) params.ip = ip;
      
      const response = await this.client.getCached(
        '/api/spf-check',
        params,
        60000
      );
      
      const data = await response.json();
      return {
        domain,
        spf_record: data.spf_record,
        valid: data.valid,
        dns_lookups: data.dns_lookups,
        mechanisms: data.mechanisms,
        warnings: data.warnings || [],
        ip_authorized: data.ip_authorized,
        suggestions: data.suggestions || []
      };
      
    } catch (error) {
      const handled = await this.errorHandler.handleError(error, {
        operation: 'validateSPF',
        domain,
        ip
      });
      throw handled.error;
    }
  }
  
  // SMTP server testing
  async testSMTP(host, port = 25, options = {}) {
    try {
      const response = await this.client.get('/api/smtp-test', {
        host,
        port,
        tls: options.tls || false,
        auth: options.auth || false,
        timeout: options.timeout || 10000
      });
      
      const data = await response.json();
      return {
        host,
        port,
        status: data.status,
        response_time: data.response_time,
        tls_support: data.tls_support,
        auth_methods: data.auth_methods || [],
        capabilities: data.capabilities || [],
        error: data.error,
        certificate_info: data.certificate_info
      };
      
    } catch (error) {
      const handled = await this.errorHandler.handleError(error, {
        operation: 'testSMTP',
        host,
        port
      });
      throw handled.error;
    }
  }
  
  // Batch email validation
  async validateEmailsBatch(emails) {
    if (emails.length === 0) return [];
    
    try {
      // Use batch endpoint for efficiency
      const response = await this.client.post('/api/email-validation/batch', {
        emails,
        checks: ['syntax', 'domain', 'mx', 'disposable']
      });
      
      const data = await response.json();
      return data.results || [];
      
    } catch (error) {
      // Fallback to individual validation if batch fails
      if (error.status === 404) {
        return this.validateEmailsIndividually(emails);
      }
      
      const handled = await this.errorHandler.handleError(error, {
        operation: 'validateEmailsBatch',
        count: emails.length
      });
      throw handled.error;
    }
  }
  
  // Individual validation fallback
  async validateEmailsIndividually(emails) {
    const requests = emails.map(email => ({
      endpoint: '/api/email-validation',
      options: {
        method: 'GET',
        params: { email }
      }
    }));
    
    return this.client.batch(requests);
  }
  
  // Parse error responses
  async parseErrorResponse(response) {
    let errorData = { message: 'Unknown error' };
    
    try {
      errorData = await response.json();
    } catch {
      errorData.message = `HTTP ${response.status}: ${response.statusText}`;
    }
    
    return new APIError(
      errorData.message,
      response.status,
      errorData.code || `HTTP_${response.status}`,
      {
        url: response.url,
        details: errorData.details,
        timestamp: new Date().toISOString()
      }
    );
  }
  
  // Health check
  async healthCheck() {
    try {
      const response = await this.client.get('/api/health');
      return await response.json();
    } catch (error) {
      return { status: 'error', error: error.message };
    }
  }
}

Usage Examples and Patterns

// Using the email testing API in applications

// Initialize client
const emailAPI = new EmailTestingAPI('your-api-key', {
  timeout: 15000,
  retryAttempts: 2
});

// Example 1: Domain email configuration check
async function checkDomainEmailConfig(domain) {
  try {
    const [dmarc, spf, mx] = await Promise.all([
      emailAPI.checkDMARC(domain),
      emailAPI.validateSPF(domain),
      emailAPI.checkMX(domain)
    ]);
    
    return {
      domain,
      dmarc: {
        configured: dmarc.valid,
        policy: dmarc.policy,
        issues: dmarc.warnings
      },
      spf: {
        configured: spf.valid,
        lookups: spf.dns_lookups,
        issues: spf.warnings
      },
      mx: {
        records: mx.mx_records,
        accessible: mx.mx_records.every(r => r.status === 'accessible')
      },
      overall_score: calculateEmailScore(dmarc, spf, mx)
    };
    
  } catch (error) {
    console.error('Domain check failed:', error.message);
    return {
      domain,
      error: error.message,
      retryable: error.retryable
    };
  }
}

// Example 2: Email server testing workflow
async function testEmailServer(config) {
  const results = [];
  
  // Test common ports
  const ports = [25, 465, 587];
  
  for (const port of ports) {
    try {
      const result = await emailAPI.testSMTP(config.host, port, {
        tls: port !== 25,
        auth: true,
        timeout: 10000
      });
      
      results.push({
        port,
        status: result.status,
        tls: result.tls_support,
        auth_methods: result.auth_methods,
        response_time: result.response_time,
        recommended: port === 587 && result.tls_support
      });
      
    } catch (error) {
      results.push({
        port,
        status: 'failed',
        error: error.message
      });
    }
  }
  
  return {
    host: config.host,
    results,
    recommended_port: results.find(r => r.recommended)?.port || 587,
    all_tests_passed: results.every(r => r.status === 'connected')
  };
}

// Example 3: Bulk email validation with progress
async function validateEmailList(emails, onProgress = null) {
  const batchSize = 100;
  const results = [];
  
  for (let i = 0; i < emails.length; i += batchSize) {
    const batch = emails.slice(i, i + batchSize);
    
    try {
      const batchResults = await emailAPI.validateEmailsBatch(batch);
      results.push(...batchResults);
      
      if (onProgress) {
        onProgress({
          processed: results.length,
          total: emails.length,
          percentage: Math.round((results.length / emails.length) * 100)
        });
      }
      
    } catch (error) {
      console.error(`Batch ${i / batchSize + 1} failed:`, error.message);
      
      // Add error entries for failed batch
      batch.forEach(email => {
        results.push({
          email,
          valid: false,
          error: error.message
        });
      });
    }
    
    // Small delay between batches to be respectful
    await new Promise(resolve => setTimeout(resolve, 100));
  }
  
  return {
    total: emails.length,
    valid: results.filter(r => r.valid).length,
    invalid: results.filter(r => !r.valid && !r.error).length,
    errors: results.filter(r => r.error).length,
    results
  };
}

// Example 4: Monitoring and alerting
class EmailMonitor {
  constructor(domains, apiKey) {
    this.domains = domains;
    this.api = new EmailTestingAPI(apiKey);
    this.lastResults = new Map();
  }
  
  async monitor() {
    const results = new Map();
    
    for (const domain of this.domains) {
      try {
        const [dmarc, spf] = await Promise.all([
          this.api.checkDMARC(domain),
          this.api.validateSPF(domain)
        ]);
        
        const current = { dmarc, spf, timestamp: Date.now() };
        const previous = this.lastResults.get(domain);
        
        results.set(domain, current);
        
        // Check for changes
        if (previous && this.hasSignificantChanges(previous, current)) {
          await this.sendAlert(domain, previous, current);
        }
        
      } catch (error) {
        console.error(`Monitoring failed for ${domain}:`, error.message);
        await this.sendErrorAlert(domain, error);
      }
    }
    
    this.lastResults = results;
    return results;
  }
  
  hasSignificantChanges(previous, current) {
    // Check if DMARC policy changed
    if (previous.dmarc.policy !== current.dmarc.policy) {
      return true;
    }
    
    // Check if SPF record changed
    if (previous.spf.spf_record !== current.spf.spf_record) {
      return true;
    }
    
    // Check if validation status changed
    if (previous.dmarc.valid !== current.dmarc.valid ||
        previous.spf.valid !== current.spf.valid) {
      return true;
    }
    
    return false;
  }
  
  async sendAlert(domain, previous, current) {
    const changes = this.identifyChanges(previous, current);
    console.log(`ALERT: Email configuration changed for ${domain}:`, changes);
    
    // Send to monitoring system, Slack, email, etc.
  }
  
  async sendErrorAlert(domain, error) {
    console.log(`ERROR: Monitoring failed for ${domain}:`, error.message);
  }
}

API integration success depends on proper error handling, rate limiting awareness, and defensive programming practices.

Testing and Debugging

API Testing Strategies

// Comprehensive API testing approach

// Mock API responses for testing
class MockAPIClient {
  constructor(responses = {}) {
    this.responses = responses;
    this.requests = [];
  }
  
  async request(endpoint, options = {}) {
    this.requests.push({ endpoint, options, timestamp: Date.now() });
    
    const key = `${options.method || 'GET'} ${endpoint}`;
    const response = this.responses[key];
    
    if (!response) {
      throw new APIError(`No mock response for ${key}`, 404, 'NOT_FOUND');
    }
    
    // Simulate delay
    if (response.delay) {
      await new Promise(resolve => setTimeout(resolve, response.delay));
    }
    
    // Simulate error
    if (response.error) {
      throw new APIError(response.error.message, response.error.status, response.error.code);
    }
    
    return {
      ok: response.status >= 200 && response.status < 300,
      status: response.status || 200,
      headers: new Map(Object.entries(response.headers || {})),
      json: () => Promise.resolve(response.data)
    };
  }
  
  getRequests() {
    return this.requests;
  }
  
  clearRequests() {
    this.requests = [];
  }
}

// Test suite for API integration
describe('EmailTestingAPI', () => {
  let api;
  let mockClient;
  
  beforeEach(() => {
    mockClient = new MockAPIClient({
      'GET /api/dmarc-check': {
        status: 200,
        data: {
          domain: 'example.com',
          dmarc_record: 'v=DMARC1; p=reject; rua=mailto:[email protected]',
          policy: 'reject',
          valid: true
        }
      },
      'GET /api/spf-check': {
        status: 200,
        data: {
          domain: 'example.com',
          spf_record: 'v=spf1 include:_spf.google.com ~all',
          valid: true,
          dns_lookups: 3
        }
      }
    });
    
    api = new EmailTestingAPI('test-key');
    api.client = mockClient;
  });
  
  describe('checkDMARC', () => {
    it('should return DMARC information for valid domain', async () => {
      const result = await api.checkDMARC('example.com');
      
      expect(result.domain).toBe('example.com');
      expect(result.valid).toBe(true);
      expect(result.policy).toBe('reject');
      expect(result.dmarc_record).toContain('v=DMARC1');
    });
    
    it('should handle API errors gracefully', async () => {
      mockClient.responses['GET /api/dmarc-check'] = {
        error: { message: 'Domain not found', status: 404, code: 'DOMAIN_NOT_FOUND' }
      };
      
      await expect(api.checkDMARC('invalid.com')).rejects.toThrow('Domain not found');
    });
    
    it('should cache results appropriately', async () => {
      await api.checkDMARC('example.com');
      await api.checkDMARC('example.com'); // Should use cache
      
      const requests = mockClient.getRequests();
      expect(requests).toHaveLength(1); // Only one actual request
    });
  });
  
  describe('batch operations', () => {
    it('should handle batch validation efficiently', async () => {
      const emails = ['[email protected]', '[email protected]', '[email protected]'];
      
      mockClient.responses['POST /api/email-validation/batch'] = {
        status: 200,
        data: {
          results: emails.map(email => ({ email, valid: true, reason: 'valid' }))
        }
      };
      
      const results = await api.validateEmailsBatch(emails);
      
      expect(results).toHaveLength(3);
      expect(results.every(r => r.valid)).toBe(true);
    });
  });
  
  describe('error handling', () => {
    it('should retry on retryable errors', async () => {
      let callCount = 0;
      const originalRequest = mockClient.request.bind(mockClient);
      
      mockClient.request = async (...args) => {
        callCount++;
        if (callCount < 3) {
          throw new APIError('Service unavailable', 503, 'SERVICE_UNAVAILABLE');
        }
        return originalRequest(...args);
      };
      
      const result = await api.checkDMARC('example.com');
      expect(result.valid).toBe(true);
      expect(callCount).toBe(3); // Initial call + 2 retries
    });
  });
});

Conclusion: API Integration Excellence

Successful API integration requires attention to authentication, error handling, performance, and user experience. The patterns and examples in this guide provide a solid foundation for building reliable, scalable applications that depend on external APIs.

  • Design robust error handling from the start
  • Implement proper authentication and security practices
  • Use caching and batching to optimize performance
  • Plan for rate limiting and service availability
  • Test thoroughly with mocks and real integration scenarios
  • Monitor API usage and performance in production
#api integration#rest api#authentication#rate limiting#error handling#performance optimization

Related Articles

Related Tools

Check Your IP Address

Use our free tools to check your IP address and test for leaks.