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:
| Tier | Requests per Minute | Burst Limit | Monthly Quota |
|---|---|---|---|
| Sandbox | 100 | 200 | 10,000 |
| Basic | 1,000 | 2,000 | 100,000 |
| Professional | 5,000 | 10,000 | 1,000,000 |
| Enterprise | Custom | Custom | Custom |
Rate Limit Headers
Every API response includes rate limit headers to help you track your usage:
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: 45Header Descriptions
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the current window |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp when the window resets |
X-RateLimit-Window | Window duration in seconds |
X-RateLimit-Retry-After | Seconds 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:
{
"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:
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
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:
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:
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:
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
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:
// 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:
- Analyze usage patterns using rate limit monitoring
- Contact sales at sales@flashamericas.com
- Discuss your requirements and get a custom quote
- Upgrade your account for higher limits
Next Steps
- Implement webhooks to reduce polling and API calls
- Set up authentication with proper API key management
- View examples for rate limit handling implementations
- Monitor performance with proper error handling
