Handle API Rate Limiting (Retry Strategies) | OpsBlu Docs

Handle API Rate Limiting (Retry Strategies)

How to diagnose and handle API rate limiting and throttling errors. Covers rate limit headers, backoff strategies, request queuing, and...

What This Means

Rate limiting is a mechanism that restricts the number of API requests a client can make within a given time period. Rate limiting issues occur when:

  • Applications exceed API quota limits
  • Requests are throttled or rejected (HTTP 429 errors)
  • Users experience degraded functionality
  • Data synchronization fails due to rate limits
  • Batch operations trigger rate limit protection
  • Inefficient API usage exhausts available quota

Common scenarios include third-party API integrations, analytics data collection, social media APIs, payment processors, and cloud service APIs.

How to Diagnose

Check Response Headers

# Inspect rate limit headers
curl -I https://api.example.com/v1/resource

# Common rate limit headers:
# X-RateLimit-Limit: 1000
# X-RateLimit-Remaining: 500
# X-RateLimit-Reset: 1640000000
# Retry-After: 3600

Monitor HTTP Status Codes

  • 429 Too Many Requests - Rate limit exceeded
  • 503 Service Unavailable - Temporary throttling
  • Check response body for rate limit details

Logging and Monitoring

// Log rate limit information
fetch('https://api.example.com/v1/resource')
  .then(response => {
    console.log('Rate Limit:', response.headers.get('X-RateLimit-Limit'));
    console.log('Remaining:', response.headers.get('X-RateLimit-Remaining'));
    console.log('Reset:', response.headers.get('X-RateLimit-Reset'));

    if (response.status === 429) {
      const retryAfter = response.headers.get('Retry-After');
      console.error('Rate limited. Retry after:', retryAfter, 'seconds');
    }
  });

Common Indicators

  • Frequent 429 errors in logs
  • Increasing API failure rates during peak usage
  • Delayed or failed background jobs
  • User-reported "service unavailable" errors

General Fixes

  1. Implement exponential backoff - Retry with increasing delays

    async function fetchWithRetry(url, maxRetries = 3) {
      for (let i = 0; i < maxRetries; i++) {
        const response = await fetch(url);
    
        if (response.status === 429) {
          const retryAfter = response.headers.get('Retry-After') || Math.pow(2, i);
          await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
          continue;
        }
    
        return response;
      }
      throw new Error('Max retries exceeded');
    }
    
  2. Use request queuing - Control concurrent requests

    class RateLimiter {
      constructor(maxRequests, timeWindow) {
        this.maxRequests = maxRequests;
        this.timeWindow = timeWindow;
        this.queue = [];
        this.timestamps = [];
      }
    
      async throttle(fn) {
        while (this.timestamps.length >= this.maxRequests) {
          const oldestTimestamp = this.timestamps[0];
          const waitTime = this.timeWindow - (Date.now() - oldestTimestamp);
          if (waitTime > 0) await new Promise(r => setTimeout(r, waitTime));
          this.timestamps.shift();
        }
    
        this.timestamps.push(Date.now());
        return fn();
      }
    }
    
  3. Cache API responses - Reduce redundant requests

    const cache = new Map();
    
    async function fetchWithCache(url, ttl = 60000) {
      if (cache.has(url)) {
        const { data, timestamp } = cache.get(url);
        if (Date.now() - timestamp < ttl) return data;
      }
    
      const response = await fetch(url);
      const data = await response.json();
      cache.set(url, { data, timestamp: Date.now() });
      return data;
    }
    
  4. Batch API requests - Combine multiple requests when possible

    // Instead of multiple individual requests
    // GET /api/user/1, GET /api/user/2, GET /api/user/3
    
    // Use batch endpoint
    // POST /api/users/batch { ids: [1, 2, 3] }
    
  5. Monitor rate limit headers - Proactively slow down before hitting limits

    function checkRateLimit(response) {
      const remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));
      const limit = parseInt(response.headers.get('X-RateLimit-Limit'));
    
      if (remaining < limit * 0.1) {
        console.warn('Approaching rate limit. Consider slowing down.');
      }
    }
    
  6. Use webhooks instead of polling - Reduce API calls by receiving push notifications

  7. Implement request deduplication - Prevent duplicate concurrent requests

    const pendingRequests = new Map();
    
    async function dedupedFetch(url) {
      if (pendingRequests.has(url)) {
        return pendingRequests.get(url);
      }
    
      const promise = fetch(url).finally(() => pendingRequests.delete(url));
      pendingRequests.set(url, promise);
      return promise;
    }
    
  8. Upgrade API tier - Consider higher-tier plans for increased limits

  9. Distribute requests - Spread requests over time instead of bursts

Platform-Specific Guides

Platform Guide
Stripe API Rate Limits
GitHub API Rate Limiting
Twitter API Rate Limits
Google APIs Usage Limits
AWS API Gateway Throttling

Further Reading