API Codex
API Guides

Rate Limiting & Quotas

API Codex implements rate limiting to ensure fair usage and maintain service quality for all users. This guide explains how rate limiting works and how to handle it effectively.

Overview

Rate limiting protects our APIs from abuse and ensures reliable performance for all users. Each subscription tier has different limits based on:

  • Requests per second (RPS)
  • Requests per month
  • Concurrent connections
  • Payload size limits

Subscription Tiers

Rate Limits by Plan

PlanRequests/MonthRequests/SecondConcurrent ConnectionsSupport
Basic (Free)100-1,0001 RPS2Community
Pro10,00010 RPS10Email
Ultra100,00050 RPS50Priority
Mega1,000,000+100+ RPSUnlimitedDedicated

Note: Exact limits vary by specific API. Check each API's pricing page on RapidAPI for details.

Rate Limit Headers

All API responses include headers that inform you about your current rate limit status:

Standard Headers

Code
x-ratelimit-requests-limit: 1000 x-ratelimit-requests-remaining: 847 x-ratelimit-requests-reset: 1640995200
HeaderDescriptionExample
x-ratelimit-requests-limitTotal requests allowed in current window1000
x-ratelimit-requests-remainingRequests remaining in current window847
x-ratelimit-requests-resetUnix timestamp when limit resets1640995200

Reading Rate Limit Headers

javascriptCode
async function checkRateLimits() { const response = await fetch(apiUrl, { headers: { 'x-rapidapi-key': API_KEY, 'x-rapidapi-host': API_HOST } }); // Extract rate limit information const rateLimit = { limit: parseInt(response.headers.get('x-ratelimit-requests-limit')), remaining: parseInt(response.headers.get('x-ratelimit-requests-remaining')), reset: parseInt(response.headers.get('x-ratelimit-requests-reset')) }; // Calculate time until reset const resetDate = new Date(rateLimit.reset * 1000); const timeUntilReset = resetDate - new Date(); console.log(`Rate Limit: ${rateLimit.remaining}/${rateLimit.limit}`); console.log(`Resets in: ${Math.ceil(timeUntilReset / 1000 / 60)} minutes`); // Warn if approaching limit if (rateLimit.remaining < rateLimit.limit * 0.1) { console.warn('⚠️ Approaching rate limit!'); } return rateLimit; }

Handling Rate Limits

429 Too Many Requests

When you exceed your rate limit, you'll receive a 429 status code:

jsonCode
{ "error": "Too Many Requests", "message": "Rate limit exceeded. Please retry after some time.", "retryAfter": 60 }

Implementing Retry Logic

Exponential Backoff

javascriptCode
class RateLimitHandler { constructor(maxRetries = 3, initialDelay = 1000) { this.maxRetries = maxRetries; this.initialDelay = initialDelay; } async executeWithRetry(requestFn) { let lastError; for (let attempt = 0; attempt < this.maxRetries; attempt++) { try { const response = await requestFn(); if (response.status === 429) { const retryAfter = this.getRetryAfter(response); const delay = retryAfter || this.calculateBackoff(attempt); console.log(`Rate limited. Waiting ${delay}ms before retry...`); await this.sleep(delay); continue; } return response; } catch (error) { lastError = error; const delay = this.calculateBackoff(attempt); await this.sleep(delay); } } throw lastError || new Error('Max retries exceeded'); } calculateBackoff(attempt) { // Exponential backoff with jitter const exponentialDelay = this.initialDelay * Math.pow(2, attempt); const jitter = Math.random() * 1000; return exponentialDelay + jitter; } getRetryAfter(response) { const retryAfter = response.headers.get('Retry-After'); if (retryAfter) { // Convert to milliseconds return parseInt(retryAfter) * 1000; } return null; } sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } // Usage const rateLimitHandler = new RateLimitHandler(); const makeAPICall = async () => { return rateLimitHandler.executeWithRetry(async () => { return fetch(apiUrl, { headers: { 'x-rapidapi-key': API_KEY, 'x-rapidapi-host': API_HOST } }); }); };

Python Implementation

pythonCode
import time import random from typing import Callable, Any import requests class RateLimitHandler: def __init__(self, max_retries: int = 3, initial_delay: float = 1.0): self.max_retries = max_retries self.initial_delay = initial_delay def execute_with_retry(self, request_fn: Callable) -> Any: """Execute request with automatic retry on rate limit""" last_exception = None for attempt in range(self.max_retries): try: response = request_fn() if response.status_code == 429: retry_after = self.get_retry_after(response) delay = retry_after or self.calculate_backoff(attempt) print(f"Rate limited. Waiting {delay:.2f}s before retry...") time.sleep(delay) continue response.raise_for_status() return response except requests.exceptions.RequestException as e: last_exception = e delay = self.calculate_backoff(attempt) time.sleep(delay) raise last_exception or Exception("Max retries exceeded") def calculate_backoff(self, attempt: int) -> float: """Calculate exponential backoff with jitter""" exponential_delay = self.initial_delay * (2 ** attempt) jitter = random.uniform(0, 1) return exponential_delay + jitter def get_retry_after(self, response: requests.Response) -> float: """Extract Retry-After header value""" retry_after = response.headers.get('Retry-After') if retry_after: return float(retry_after) return None # Usage handler = RateLimitHandler() def make_api_request(): return requests.get( 'https://advanced-dns-lookup-api.p.rapidapi.com/v1/check', headers={ 'x-rapidapi-key': API_KEY, 'x-rapidapi-host': 'advanced-dns-lookup-api.p.rapidapi.com' }, params={'name': 'example.com'} ) response = handler.execute_with_retry(make_api_request) print(response.json())

Request Queue Management

Implement a queue to manage request rates:

javascriptCode
class RequestQueue { constructor(rateLimit = 10, interval = 1000) { this.rateLimit = rateLimit; this.interval = interval; this.queue = []; this.processing = false; this.requestCount = 0; this.windowStart = Date.now(); } async add(requestFn) { return new Promise((resolve, reject) => { this.queue.push({ requestFn, resolve, reject }); this.process(); }); } async process() { if (this.processing) return; this.processing = true; while (this.queue.length > 0) { // Check if we need to wait for rate limit window const now = Date.now(); const timeInWindow = now - this.windowStart; if (timeInWindow >= this.interval) { // Reset window this.windowStart = now; this.requestCount = 0; } if (this.requestCount >= this.rateLimit) { // Wait for remaining time in window const waitTime = this.interval - timeInWindow; await this.sleep(waitTime); continue; } // Process request const { requestFn, resolve, reject } = this.queue.shift(); this.requestCount++; try { const result = await requestFn(); resolve(result); } catch (error) { reject(error); } } this.processing = false; } sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } // Usage const queue = new RequestQueue(10, 1000); // 10 requests per second // Add multiple requests const requests = domains.map(domain => queue.add(() => fetch(`${API_URL}?name=${domain}`, { headers }) .then(res => res.json()) ) ); const results = await Promise.all(requests);

Rate Limiting Strategies

1. Client-Side Throttling

Implement throttling to prevent hitting rate limits:

javascriptCode
class Throttler { constructor(maxRequests, perMilliseconds) { this.maxRequests = maxRequests; this.perMilliseconds = perMilliseconds; this.requests = []; } async throttle() { const now = Date.now(); // Remove old requests outside the time window this.requests = this.requests.filter( time => now - time < this.perMilliseconds ); if (this.requests.length >= this.maxRequests) { // Calculate wait time const oldestRequest = this.requests[0]; const waitTime = this.perMilliseconds - (now - oldestRequest); if (waitTime > 0) { await new Promise(resolve => setTimeout(resolve, waitTime)); return this.throttle(); // Recursive call after waiting } } this.requests.push(now); } } // Usage const throttler = new Throttler(10, 1000); // 10 requests per second async function makeThrottledRequest(url) { await throttler.throttle(); return fetch(url, { headers }); }

2. Adaptive Rate Limiting

Adjust request rate based on remaining quota:

javascriptCode
class AdaptiveRateLimiter { constructor() { this.rateLimit = null; this.lastCheck = Date.now(); this.baseDelay = 100; // Base delay between requests (ms) } updateFromHeaders(headers) { this.rateLimit = { limit: parseInt(headers.get('x-ratelimit-requests-limit')), remaining: parseInt(headers.get('x-ratelimit-requests-remaining')), reset: parseInt(headers.get('x-ratelimit-requests-reset')) * 1000 }; } calculateDelay() { if (!this.rateLimit) return this.baseDelay; const now = Date.now(); const timeUntilReset = Math.max(0, this.rateLimit.reset - now); if (this.rateLimit.remaining === 0) { // No requests left, wait until reset return timeUntilReset; } // Calculate optimal delay to spread remaining requests const optimalDelay = timeUntilReset / this.rateLimit.remaining; // Adjust delay based on usage percentage const usagePercent = 1 - (this.rateLimit.remaining / this.rateLimit.limit); if (usagePercent > 0.8) { // Slow down when approaching limit return Math.max(optimalDelay * 2, this.baseDelay); } else if (usagePercent < 0.2) { // Can go faster when plenty of quota available return Math.min(optimalDelay, this.baseDelay); } return optimalDelay; } async wait() { const delay = this.calculateDelay(); if (delay > 0) { await new Promise(resolve => setTimeout(resolve, delay)); } } } // Usage const limiter = new AdaptiveRateLimiter(); async function makeAdaptiveRequest(url) { await limiter.wait(); const response = await fetch(url, { headers }); limiter.updateFromHeaders(response.headers); return response; }

3. Circuit Breaker Pattern

Prevent cascading failures when rate limited:

javascriptCode
class CircuitBreaker { constructor(threshold = 5, timeout = 60000) { this.threshold = threshold; this.timeout = timeout; this.failures = 0; this.nextAttempt = Date.now(); this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN } async execute(requestFn) { if (this.state === 'OPEN') { if (Date.now() < this.nextAttempt) { throw new Error('Circuit breaker is OPEN'); } this.state = 'HALF_OPEN'; } try { const response = await requestFn(); if (response.status === 429) { this.recordFailure(); throw new Error('Rate limited'); } this.recordSuccess(); return response; } catch (error) { this.recordFailure(); throw error; } } recordSuccess() { this.failures = 0; this.state = 'CLOSED'; } recordFailure() { this.failures++; if (this.failures >= this.threshold) { this.state = 'OPEN'; this.nextAttempt = Date.now() + this.timeout; console.log(`Circuit breaker OPEN. Will retry at ${new Date(this.nextAttempt)}`); } } getState() { return { state: this.state, failures: this.failures, nextAttempt: this.state === 'OPEN' ? new Date(this.nextAttempt) : null }; } } // Usage const breaker = new CircuitBreaker(); async function makeProtectedRequest(url) { try { return await breaker.execute(() => fetch(url, { headers }) ); } catch (error) { console.error('Request failed:', error.message); console.log('Circuit state:', breaker.getState()); throw error; } }

Optimization Techniques

1. Request Batching

Combine multiple operations into single requests where possible:

javascriptCode
// Instead of multiple individual requests const results = []; for (const domain of domains) { const result = await fetch(`${API_URL}?name=${domain}`, { headers }); results.push(await result.json()); } // Use batch processing if available const batchRequest = { domains: domains, type: 'A' }; const response = await fetch(`${API_URL}/batch`, { method: 'POST', headers: { ...headers, 'Content-Type': 'application/json' }, body: JSON.stringify(batchRequest) });

2. Response Caching

Cache responses to reduce API calls:

javascriptCode
class CachedAPIClient { constructor(ttl = 3600000) { // 1 hour default TTL this.cache = new Map(); this.ttl = ttl; } getCacheKey(url, params) { return `${url}?${new URLSearchParams(params).toString()}`; } async request(url, params = {}) { const cacheKey = this.getCacheKey(url, params); const cached = this.cache.get(cacheKey); // Check if cached and not expired if (cached && Date.now() - cached.timestamp < this.ttl) { console.log('Cache hit:', cacheKey); return cached.data; } // Make actual request const response = await fetch(`${url}?${new URLSearchParams(params)}`, { headers }); const data = await response.json(); // Cache the response this.cache.set(cacheKey, { data, timestamp: Date.now() }); // Clean old cache entries this.cleanCache(); return data; } cleanCache() { const now = Date.now(); for (const [key, value] of this.cache.entries()) { if (now - value.timestamp > this.ttl) { this.cache.delete(key); } } } }

3. Parallel Processing with Limits

Process multiple requests in parallel while respecting rate limits:

javascriptCode
class ParallelProcessor { constructor(concurrency = 5) { this.concurrency = concurrency; } async processAll(items, processFn) { const results = []; const executing = []; for (const item of items) { const promise = processFn(item).then(result => { // Remove from executing array executing.splice(executing.indexOf(promise), 1); return result; }); results.push(promise); if (items.length >= this.concurrency) { executing.push(promise); if (executing.length >= this.concurrency) { await Promise.race(executing); } } } return Promise.all(results); } } // Usage const processor = new ParallelProcessor(5); // Max 5 concurrent requests const results = await processor.processAll(domains, async (domain) => { const response = await fetch(`${API_URL}?name=${domain}`, { headers }); return response.json(); });

Monitoring & Alerts

Rate Limit Monitor

javascriptCode
class RateLimitMonitor { constructor(alertThreshold = 0.8) { this.alertThreshold = alertThreshold; this.metrics = []; } recordRequest(headers) { const limit = parseInt(headers.get('x-ratelimit-requests-limit')); const remaining = parseInt(headers.get('x-ratelimit-requests-remaining')); const reset = parseInt(headers.get('x-ratelimit-requests-reset')); const usage = (limit - remaining) / limit; this.metrics.push({ timestamp: Date.now(), limit, remaining, usage, reset }); // Check for alerts if (usage > this.alertThreshold) { this.sendAlert({ level: 'warning', message: `Rate limit usage at ${(usage * 100).toFixed(1)}%`, remaining, resetIn: new Date(reset * 1000) }); } if (remaining === 0) { this.sendAlert({ level: 'critical', message: 'Rate limit exceeded!', resetIn: new Date(reset * 1000) }); } } sendAlert(alert) { console.warn(`⚠️ RATE LIMIT ALERT [${alert.level}]:`, alert.message); // Implement your alert mechanism here (email, Slack, etc.) } getMetrics() { const recent = this.metrics.slice(-100); // Last 100 requests if (recent.length === 0) return null; const avgUsage = recent.reduce((sum, m) => sum + m.usage, 0) / recent.length; const maxUsage = Math.max(...recent.map(m => m.usage)); return { requestCount: recent.length, averageUsage: avgUsage, maxUsage, currentRemaining: recent[recent.length - 1].remaining }; } }

Best Practices

Do's ✅

  1. Always check rate limit headers in responses
  2. Implement exponential backoff for retries
  3. Cache responses when appropriate
  4. Use request queuing for bulk operations
  5. Monitor your usage proactively
  6. Implement circuit breakers for resilience
  7. Batch requests when possible

Don'ts ❌

  1. Don't ignore 429 responses - Always handle them
  2. Don't retry immediately - Use backoff strategies
  3. Don't hammer the API - Respect rate limits
  4. Don't hardcode delays - Use adaptive timing
  5. Don't waste quota - Cache when possible

Upgrading Your Plan

If you consistently hit rate limits, consider upgrading:

  1. Monitor your usage patterns
  2. Calculate required capacity
  3. Visit RapidAPI
  4. Select appropriate plan
  5. Upgrade seamlessly without code changes

Next Steps

Last modified on