Rate Limiting

The Flash Americas API implements rate limiting to ensure fair usage and optimal performance for all users. This guide explains how rate limits work and how to handle them in your applications.

Overview

Rate limiting protects the API from abuse and ensures consistent performance by:

  • Preventing overload from excessive requests
  • Ensuring fair access for all API users
  • Maintaining service quality and response times
  • Protecting against misuse and denial-of-service attacks

Rate Limit Tiers

Different API keys have different rate limits based on your subscription tier:

TierRequests per MinuteBurst LimitMonthly Quota
Sandbox10020010,000
Basic1,0002,000100,000
Professional5,00010,0001,000,000
EnterpriseCustomCustomCustom

Rate Limit Headers

Every API response includes rate limit headers to help you track your usage:

Rate limit response headershttp
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1609459200
X-RateLimit-Window: 60
X-RateLimit-Retry-After: 45

Header Descriptions

HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the window resets
X-RateLimit-WindowWindow duration in seconds
X-RateLimit-Retry-AfterSeconds to wait before retrying (only when rate limited)

Rate Limit Exceeded Response

When you exceed your rate limit, the API returns a 429 status code:

Rate limit exceeded responsejson
{
"success": false,
"error": {
  "code": "RATE_LIMIT_EXCEEDED",
  "message": "Rate limit exceeded. Please try again later.",
  "details": {
    "limit": 1000,
    "window": 60,
    "retryAfter": 45
  }
}
}

Handling Rate Limits

Basic Retry Logic

Implement exponential backoff when rate limits are exceeded:

Basic retry with exponential backoffjavascript
async function apiRequestWithRetry(url, options = {}, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
  try {
    const response = await fetch(url, {
      ...options,
      headers: {
        'Authorization': 'Bearer YOUR_API_KEY',
        'Content-Type': 'application/json',
        ...options.headers
      }
    });
    
    // Check if rate limited
    if (response.status === 429) {
      const retryAfter = response.headers.get('X-RateLimit-Retry-After');
      const delay = retryAfter ? parseInt(retryAfter) * 1000 : Math.pow(2, attempt) * 1000;
      
      if (attempt < maxRetries) {
        console.log(`Rate limited. Retrying in ${delay}ms...`);
        await sleep(delay);
        continue;
      }
      
      throw new Error('Rate limit exceeded. Max retries reached.');
    }
    
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }
    
    return response.json();
  } catch (error) {
    if (attempt === maxRetries) {
      throw error;
    }
    
    // Exponential backoff for other errors
    const delay = Math.pow(2, attempt) * 1000;
    await sleep(delay);
  }
}
}

function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

Advanced Rate Limit Management

Rate limit aware API clientjavascript
class FlashAmericasClient {
constructor(apiKey, baseURL = 'https://api.flashamericas.com/api/v1') {
  this.apiKey = apiKey;
  this.baseURL = baseURL;
  this.rateLimitInfo = {
    limit: null,
    remaining: null,
    resetTime: null,
    window: null
  };
}

async request(endpoint, options = {}) {
  // Check if we should wait before making request
  await this.waitIfNecessary();
  
  const url = `${this.baseURL}${endpoint}`;
  const response = await fetch(url, {
    ...options,
    headers: {
      'Authorization': `Bearer ${this.apiKey}`,
      'Content-Type': 'application/json',
      ...options.headers
    }
  });
  
  // Update rate limit info from headers
  this.updateRateLimitInfo(response.headers);
  
  if (response.status === 429) {
    throw new RateLimitError('Rate limit exceeded', this.rateLimitInfo);
  }
  
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }
  
  return response.json();
}

updateRateLimitInfo(headers) {
  this.rateLimitInfo = {
    limit: parseInt(headers.get('X-RateLimit-Limit')) || null,
    remaining: parseInt(headers.get('X-RateLimit-Remaining')) || null,
    resetTime: parseInt(headers.get('X-RateLimit-Reset')) || null,
    window: parseInt(headers.get('X-RateLimit-Window')) || null
  };
}

async waitIfNecessary() {
  const now = Math.floor(Date.now() / 1000);
  
  // If we have no remaining requests and haven't reset yet
  if (this.rateLimitInfo.remaining === 0 && 
      this.rateLimitInfo.resetTime && 
      now < this.rateLimitInfo.resetTime) {
    
    const waitTime = (this.rateLimitInfo.resetTime - now) * 1000;
    console.log(`Waiting ${waitTime}ms for rate limit reset...`);
    await sleep(waitTime);
  }
}

getRateLimitStatus() {
  return {
    ...this.rateLimitInfo,
    percentageUsed: this.rateLimitInfo.limit ? 
      ((this.rateLimitInfo.limit - this.rateLimitInfo.remaining) / this.rateLimitInfo.limit) * 100 : 
      null
  };
}
}

class RateLimitError extends Error {
constructor(message, rateLimitInfo) {
  super(message);
  this.name = 'RateLimitError';
  this.rateLimitInfo = rateLimitInfo;
}
}

Rate Limit Strategies

Request Queuing

Implement a queue to manage API requests:

Request queue implementationjavascript
class RequestQueue {
constructor(client, maxConcurrent = 5, delayBetweenRequests = 100) {
  this.client = client;
  this.maxConcurrent = maxConcurrent;
  this.delayBetweenRequests = delayBetweenRequests;
  this.queue = [];
  this.processing = 0;
}

async enqueue(endpoint, options = {}) {
  return new Promise((resolve, reject) => {
    this.queue.push({
      endpoint,
      options,
      resolve,
      reject
    });
    
    this.processQueue();
  });
}

async processQueue() {
  if (this.processing >= this.maxConcurrent || this.queue.length === 0) {
    return;
  }
  
  const request = this.queue.shift();
  this.processing++;
  
  try {
    const result = await this.client.request(request.endpoint, request.options);
    request.resolve(result);
  } catch (error) {
    request.reject(error);
  } finally {
    this.processing--;
    
    // Add delay between requests
    if (this.delayBetweenRequests > 0) {
      await sleep(this.delayBetweenRequests);
    }
    
    // Process next item in queue
    this.processQueue();
  }
}
}

// Usage
const client = new FlashAmericasClient('YOUR_API_KEY');
const queue = new RequestQueue(client);

// Queue multiple requests
const quotes = await queue.enqueue('/quotes', {
method: 'POST',
body: JSON.stringify(quoteRequest)
});

Caching Responses

Implement caching to reduce API calls:

Response cachingjavascript
class CachedApiClient extends FlashAmericasClient {
constructor(apiKey, cacheOptions = {}) {
  super(apiKey);
  this.cache = new Map();
  this.cacheTTL = cacheOptions.ttl || 300000; // 5 minutes default
  this.cacheableEndpoints = cacheOptions.cacheableEndpoints || [
    '/quotes',
    '/tracking',
    '/shipments'
  ];
}

async request(endpoint, options = {}) {
  const cacheKey = this.getCacheKey(endpoint, options);
  
  // Check cache for GET requests on cacheable endpoints
  if (options.method === 'GET' || !options.method) {
    if (this.shouldCache(endpoint)) {
      const cached = this.getFromCache(cacheKey);
      if (cached) {
        return cached;
      }
    }
  }
  
  // Make API request
  const response = await super.request(endpoint, options);
  
  // Cache successful responses
  if (this.shouldCache(endpoint) && response.success) {
    this.setCache(cacheKey, response);
  }
  
  return response;
}

getCacheKey(endpoint, options) {
  const method = options.method || 'GET';
  const body = options.body || '';
  return `${method}:${endpoint}:${btoa(body)}`;
}

shouldCache(endpoint) {
  return this.cacheableEndpoints.some(pattern => 
    endpoint.startsWith(pattern)
  );
}

getFromCache(key) {
  const item = this.cache.get(key);
  if (!item) return null;
  
  if (Date.now() > item.expiry) {
    this.cache.delete(key);
    return null;
  }
  
  return item.data;
}

setCache(key, data) {
  this.cache.set(key, {
    data,
    expiry: Date.now() + this.cacheTTL
  });
}

clearCache() {
  this.cache.clear();
}
}

Monitoring Rate Limits

Rate Limit Dashboard

Create a simple dashboard to monitor your API usage:

Rate limit monitoringjavascript
class RateLimitMonitor {
constructor(client) {
  this.client = client;
  this.stats = {
    totalRequests: 0,
    rateLimitHits: 0,
    averageRemaining: [],
    hourlyUsage: new Map()
  };
}

async makeRequest(endpoint, options = {}) {
  const startTime = Date.now();
  
  try {
    const response = await this.client.request(endpoint, options);
    this.recordSuccess();
    return response;
  } catch (error) {
    if (error instanceof RateLimitError) {
      this.recordRateLimitHit();
    }
    throw error;
  } finally {
    this.recordRequestTime(Date.now() - startTime);
  }
}

recordSuccess() {
  this.stats.totalRequests++;
  const rateLimitInfo = this.client.getRateLimitStatus();
  if (rateLimitInfo.remaining !== null) {
    this.stats.averageRemaining.push(rateLimitInfo.remaining);
    
    // Keep only last 100 measurements
    if (this.stats.averageRemaining.length > 100) {
      this.stats.averageRemaining.shift();
    }
  }
  
  this.recordHourlyUsage();
}

recordRateLimitHit() {
  this.stats.rateLimitHits++;
  this.stats.totalRequests++;
}

recordRequestTime(duration) {
  // Could track request times for performance monitoring
}

recordHourlyUsage() {
  const hour = new Date().getHours();
  const current = this.stats.hourlyUsage.get(hour) || 0;
  this.stats.hourlyUsage.set(hour, current + 1);
}

getStats() {
  const avgRemaining = this.stats.averageRemaining.length > 0 ?
    this.stats.averageRemaining.reduce((a, b) => a + b) / this.stats.averageRemaining.length :
    0;
    
  return {
    totalRequests: this.stats.totalRequests,
    rateLimitHits: this.stats.rateLimitHits,
    rateLimitHitRate: this.stats.totalRequests > 0 ? 
      (this.stats.rateLimitHits / this.stats.totalRequests * 100).toFixed(2) + '%' : 
      '0%',
    averageRemaining: Math.round(avgRemaining),
    currentStatus: this.client.getRateLimitStatus(),
    hourlyUsage: Object.fromEntries(this.stats.hourlyUsage)
  };
}
}

Best Practices

1. Respect Rate Limits

  • Monitor headers and adjust request frequency accordingly
  • Implement backoff strategies for rate limit errors
  • Cache responses when possible to reduce API calls
  • Use webhooks instead of polling for real-time updates

2. Optimize Request Patterns

  • Batch operations when the API supports it
  • Use appropriate endpoints (don't use detailed endpoints when summary data suffices)
  • Implement pagination properly for large datasets
  • Avoid redundant requests by caching and storing data locally

3. Handle Errors Gracefully

Graceful error handlingjavascript
async function robustApiCall(client, endpoint, options = {}, maxRetries = 3) {
let lastError;

for (let attempt = 0; attempt <= maxRetries; attempt++) {
  try {
    return await client.request(endpoint, options);
  } catch (error) {
    lastError = error;
    
    if (error instanceof RateLimitError) {
      // Use the retry-after header if available
      const delay = error.rateLimitInfo.retryAfter * 1000 || 
                   Math.pow(2, attempt) * 1000;
      
      if (attempt < maxRetries) {
        console.log(`Rate limited. Waiting ${delay}ms before retry ${attempt + 1}/${maxRetries}`);
        await sleep(delay);
        continue;
      }
    } else if (error.message.includes('timeout') || 
               error.message.includes('network')) {
      // Network errors - retry with exponential backoff
      if (attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 1000;
        await sleep(delay);
        continue;
      }
    } else {
      // Non-retryable error
      throw error;
    }
  }
}

throw lastError;
}

Rate Limit Testing

Test your rate limiting implementation:

Rate limit testingjavascript
// Test rate limit handling
async function testRateLimit() {
const client = new FlashAmericasClient('YOUR_TEST_API_KEY');
const requests = [];

// Make many concurrent requests to trigger rate limit
for (let i = 0; i < 150; i++) {
  requests.push(
    client.request('/health')
      .then(result => ({ success: true, result }))
      .catch(error => ({ success: false, error: error.message }))
  );
}

const results = await Promise.all(requests);

const successful = results.filter(r => r.success).length;
const rateLimited = results.filter(r => 
  r.error && r.error.includes('Rate limit')
).length;

console.log(`Successful: ${successful}`);
console.log(`Rate limited: ${rateLimited}`);
console.log(`Total: ${results.length}`);
}

Upgrading Rate Limits

If you consistently hit rate limits, consider upgrading your plan:

  1. Analyze usage patterns using rate limit monitoring
  2. Contact sales at sales@flashamericas.com
  3. Discuss your requirements and get a custom quote
  4. Upgrade your account for higher limits

Next Steps