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
Plan Requests/Month Requests/Second Concurrent Connections Support Basic (Free) 100-1,000 1 RPS 2 Community Pro 10,000 10 RPS 10 Email Ultra 100,000 50 RPS 50 Priority Mega 1,000,000+ 100+ RPS Unlimited Dedicated
Note: Exact limits vary by specific API. Check each API's pricing page on RapidAPI for details.
All API responses include headers that inform you about your current rate limit status:
Standard Headers
x-ratelimit-requests-limit: 1000
x-ratelimit-requests-remaining: 847
x-ratelimit-requests-reset: 1640995200
Header Description Example x-ratelimit-requests-limit
Total requests allowed in current window 1000
x-ratelimit-requests-remaining
Requests remaining in current window 847
x-ratelimit-requests-reset
Unix timestamp when limit resets 1640995200
Code
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:
Code
{
"error" : "Too Many Requests" ,
"message" : "Rate limit exceeded. Please retry after some time." ,
"retryAfter" : 60
}
Implementing Retry Logic
Exponential Backoff
Code
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
Code
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:
Code
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:
Code
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:
Code
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:
Code
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:
Code
// 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:
Code
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:
Code
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
Code
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 ✅
Always check rate limit headers in responses
Implement exponential backoff for retries
Cache responses when appropriate
Use request queuing for bulk operations
Monitor your usage proactively
Implement circuit breakers for resilience
Batch requests when possible
Don'ts ❌
Don't ignore 429 responses - Always handle them
Don't retry immediately - Use backoff strategies
Don't hammer the API - Respect rate limits
Don't hardcode delays - Use adaptive timing
Don't waste quota - Cache when possible
Upgrading Your Plan
If you consistently hit rate limits, consider upgrading:
Monitor your usage patterns
Calculate required capacity
Visit RapidAPI
Select appropriate plan
Upgrade seamlessly without code changes
Next Steps
Last modified on August 3, 2025