Best Practices

Best Practices: Rate Limits, Caching, and Reliability

Production patterns to ship fast apps that scale with sports traffic.

2025-10-21· 5 min read

Overview

This guide covers production best practices for integrating with the API 4 Sports platform. Learn how to handle rate limits, implement efficient caching, and build reliable applications.

API Rate Limits

Rate limits scale with your subscription plan. When exceeded, the API returns HTTP 429 (Too Many Requests). Implement proper handling:

Exponential Backoff with Jitter

javascript
async function fetchWithBackoff(url, maxRetries = 5) {
  const headers = { 'X-Api-Key': process.env.API_KEY };
  
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch(url, { headers });
    
    if (response.status === 429) {
      // Calculate backoff with jitter
      const baseDelay = Math.pow(2, attempt) * 1000;
      const jitter = Math.random() * 1000;
      const delay = baseDelay + jitter;
      
      console.log(`Rate limited. Waiting ${Math.round(delay)}ms...`);
      await new Promise(r => setTimeout(r, delay));
      continue;
    }
    
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    
    return await response.json();
  }
  
  throw new Error('Max retries exceeded');
}

Python Retry Logic

python
import requests
import time
import random

def fetch_with_backoff(url, max_retries=5):
    headers = {"X-Api-Key": "your_api_key_here"}
    
    for attempt in range(max_retries):
        response = requests.get(url, headers=headers)
        
        if response.status_code == 429:
            base_delay = (2 ** attempt)
            jitter = random.uniform(0, 1)
            delay = base_delay + jitter
            print(f"Rate limited. Waiting {delay:.2f}s...")
            time.sleep(delay)
            continue
        
        response.raise_for_status()
        return response.json()
    
    raise Exception("Max retries exceeded")

Caching Strategy

Implement layered caching based on data volatility. Different endpoints require different TTLs.

Recommended TTLs by Endpoint

  • /football/leagues — Cache for 1-24 hours (rarely changes)
  • /football/countries — Cache for 1-24 hours
  • /football/leagues/{id}/teams — Cache for 1-6 hours
  • /football/leagues/{id}/standings — Cache for 5-30 minutes
  • /football/matches — Cache for 1-5 minutes
  • /football/live — Cache for 5-10 seconds (real-time)
  • /football/live-odds — Cache for 2-5 seconds (real-time)

In-Memory Cache Implementation

javascript
class APICache {
  constructor() {
    this.cache = new Map();
  }
  
  // TTL in milliseconds
  set(key, value, ttl) {
    this.cache.set(key, {
      value,
      expiry: Date.now() + ttl
    });
  }
  
  get(key) {
    const item = this.cache.get(key);
    if (!item) return null;
    if (Date.now() > item.expiry) {
      this.cache.delete(key);
      return null;
    }
    return item.value;
  }
  
  async fetchWithCache(url, ttl) {
    const cached = this.get(url);
    if (cached) return cached;
    
    const response = await fetch(url, {
      headers: { 'X-Api-Key': process.env.API_KEY }
    });
    const data = await response.json();
    
    this.set(url, data, ttl);
    return data;
  }
}

// Usage
const cache = new APICache();

// Cache leagues for 1 hour
const leagues = await cache.fetchWithCache(
  'https://api.api4sports.com/api/football/leagues',
  60 * 60 * 1000 // 1 hour
);

// Cache live matches for 10 seconds
const live = await cache.fetchWithCache(
  'https://api.api4sports.com/api/football/live',
  10 * 1000 // 10 seconds
);

Stale-While-Revalidate Pattern

javascript
class SWRCache {
  constructor() {
    this.cache = new Map();
  }
  
  async fetch(url, options = {}) {
    const { maxAge = 60000, staleAge = 300000 } = options;
    const cached = this.cache.get(url);
    const now = Date.now();
    
    // Return fresh cache immediately
    if (cached && (now - cached.timestamp) < maxAge) {
      return cached.data;
    }
    
    // Return stale cache and revalidate in background
    if (cached && (now - cached.timestamp) < staleAge) {
      this.revalidate(url);
      return cached.data;
    }
    
    // Cache miss - fetch fresh data
    return await this.revalidate(url);
  }
  
  async revalidate(url) {
    const response = await fetch(url, {
      headers: { 'X-Api-Key': process.env.API_KEY }
    });
    const data = await response.json();
    
    this.cache.set(url, {
      data,
      timestamp: Date.now()
    });
    
    return data;
  }
}

// Usage
const swr = new SWRCache();
const standings = await swr.fetch(
  'https://api.api4sports.com/api/football/leagues/207/standings',
  { maxAge: 5 * 60 * 1000, staleAge: 30 * 60 * 1000 }
);

Pagination Best Practices

Most list endpoints support pagination. Use it to avoid over-fetching and reduce response times.

Paginated Fetch Example

javascript
async function fetchAllPages(baseUrl, itemsPerPage = 50) {
  const allItems = [];
  let page = 1;
  let hasMore = true;
  
  while (hasMore) {
    const url = `${baseUrl}?itemsPerPage=${itemsPerPage}&page=${page}`;
    const response = await fetch(url, {
      headers: { 'X-Api-Key': process.env.API_KEY }
    });
    const data = await response.json();
    
    allItems.push(...(data.data || data.result || []));
    
    // Check if there are more pages
    const pagination = data.pagination;
    hasMore = pagination && page < pagination.last_page;
    page++;
  }
  
  return allItems;
}

// Usage - Fetch all leagues
const allLeagues = await fetchAllPages(
  'https://api.api4sports.com/api/football/leagues'
);
console.log(`Total leagues: ${allLeagues.length}`);

Error Handling Matrix

  • 200 OK — Success, cache the response
  • 401 Unauthorized — Check API key configuration
  • 403 Forbidden — Verify subscription includes this resource
  • 404 Not Found — Resource doesn't exist, don't retry
  • 422 Validation Error — Check request parameters
  • 429 Rate Limited — Implement backoff and retry
  • 5xx Server Error — Retry with backoff, alert if persistent

Comprehensive Error Handler

javascript
class APIClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseUrl = 'https://api.api4sports.com/api';
  }
  
  async request(endpoint, options = {}) {
    const url = `${this.baseUrl}${endpoint}`;
    
    try {
      const response = await fetch(url, {
        headers: { 'X-Api-Key': this.apiKey },
        ...options
      });
      
      switch (response.status) {
        case 200:
          return { success: true, data: await response.json() };
        
        case 401:
          return { success: false, error: 'Invalid API key', retry: false };
        
        case 403:
          return { success: false, error: 'Access denied - check subscription', retry: false };
        
        case 404:
          return { success: false, error: 'Resource not found', retry: false };
        
        case 422:
          const body = await response.json();
          return { success: false, error: 'Validation failed', details: body, retry: false };
        
        case 429:
          return { success: false, error: 'Rate limited', retry: true };
        
        default:
          if (response.status >= 500) {
            return { success: false, error: `Server error: ${response.status}`, retry: true };
          }
          return { success: false, error: `Unexpected status: ${response.status}`, retry: false };
      }
    } catch (error) {
      return { success: false, error: error.message, retry: true };
    }
  }
  
  async getLeagues() {
    return this.request('/football/leagues');
  }
  
  async getLiveMatches() {
    return this.request('/football/live');
  }
  
  async getMatches(from, to) {
    return this.request(`/football/matches?from=${from}&to=${to}`);
  }
}

// Usage
const client = new APIClient(process.env.API_KEY);
const result = await client.getLiveMatches();

if (result.success) {
  console.log('Matches:', result.data.result);
} else {
  console.error('Error:', result.error);
  if (result.retry) {
    console.log('Will retry...');
  }
}

Observability & Monitoring

  • Log all API requests with correlation IDs
  • Track response times and error rates
  • Alert on sustained error rates > 1%
  • Monitor cache hit rates
  • Set up dashboards for request volume by endpoint

Request Logging Middleware

javascript
const crypto = require('crypto');

class LoggingAPIClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.metrics = {
      requests: 0,
      errors: 0,
      totalLatency: 0
    };
  }
  
  async request(url) {
    const requestId = crypto.randomUUID().slice(0, 8);
    const startTime = Date.now();
    
    console.log(`[${requestId}] Request: ${url}`);
    
    try {
      const response = await fetch(url, {
        headers: { 'X-Api-Key': this.apiKey }
      });
      
      const latency = Date.now() - startTime;
      this.metrics.requests++;
      this.metrics.totalLatency += latency;
      
      console.log(`[${requestId}] Response: ${response.status} (${latency}ms)`);
      
      if (!response.ok) {
        this.metrics.errors++;
      }
      
      return await response.json();
    } catch (error) {
      const latency = Date.now() - startTime;
      this.metrics.errors++;
      this.metrics.requests++;
      
      console.error(`[${requestId}] Error: ${error.message} (${latency}ms)`);
      throw error;
    }
  }
  
  getStats() {
    return {
      totalRequests: this.metrics.requests,
      errorRate: this.metrics.errors / this.metrics.requests,
      avgLatency: this.metrics.totalLatency / this.metrics.requests
    };
  }
}

Security Best Practices

  • Never expose API keys in client-side code
  • Use environment variables for all secrets
  • Implement server-side proxy for browser apps
  • Rotate keys periodically and on suspected leaks
  • Use HTTPS for all API requests

Production Checklist

  • API key stored securely in environment variables
  • Rate limit handling with exponential backoff
  • Caching layer implemented with appropriate TTLs
  • Error handling for all status codes
  • Pagination used for list endpoints
  • Monitoring and alerting configured
  • Health checks for API connectivity