Skip to main content

Rate Limiting

The system enforces multiple levels of rate limiting to prevent abuse and ensure fair usage across all API users.

Rate Limit Types

LimitDefaultScopeReset Period
Monthly Global1,000 requestsAll API users combinedMonthly
Daily Per User100 requestsPer API userDaily
Hourly Per IP50 requestsPer IP addressHourly

Limit Hierarchy

Limits are enforced in order of precedence:

  1. Global Monthly: System-wide protection
  2. User Daily: Individual user quotas
  3. IP Hourly: IP-based abuse prevention

Rate Limit Headers

Every API response includes rate limit information in headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1642204800
X-RateLimit-Type: user_daily

Header Descriptions

HeaderDescriptionExample
X-RateLimit-LimitMaximum requests in current window100
X-RateLimit-RemainingRequests remaining in window95
X-RateLimit-ResetUnix timestamp when limit resets1642204800
X-RateLimit-TypeWhich limit type is being reporteduser_daily

Rate Limit Exceeded Response

When limits are exceeded, you'll receive a 429 status code:

{
"error": "Daily API request limit exceeded for user",
"code": "USER_DAILY_LIMIT_EXCEEDED",
"retry_after": 1642204800,
"limit_info": {
"limit": 100,
"window": "daily",
"reset_time": "2024-01-16T00:00:00Z"
}
}

Error Codes by Limit Type

Error CodeLimit TypeDescription
GLOBAL_MONTHLY_LIMIT_EXCEEDEDMonthly GlobalSystem-wide monthly limit reached
USER_DAILY_LIMIT_EXCEEDEDDaily Per UserUser's daily quota exhausted
IP_HOURLY_LIMIT_EXCEEDEDHourly Per IPIP address hourly limit reached

Handling Rate Limits

1. Monitor Usage

Track your rate limit usage proactively:

function trackRateLimit(response) {
const limit = parseInt(response.headers['x-ratelimit-limit']);
const remaining = parseInt(response.headers['x-ratelimit-remaining']);
const reset = parseInt(response.headers['x-ratelimit-reset']);

const usagePercent = ((limit - remaining) / limit) * 100;

if (usagePercent > 80) {
console.warn(`Rate limit usage at ${usagePercent.toFixed(1)}%`);
}

return {
limit,
remaining,
reset: new Date(reset * 1000),
usagePercent
};
}

2. Implement Exponential Backoff

When rate limited, use exponential backoff for retries:

async function submitWithBackoff(data, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await submitData(data);
return response;
} catch (error) {
if (error.response?.status === 429) {
if (attempt === maxRetries - 1) {
throw error; // Final attempt failed
}

// Calculate backoff delay
const baseDelay = 1000; // 1 second
const delay = baseDelay * Math.pow(2, attempt);
const jitter = Math.random() * 1000; // Add randomness

console.log(`Rate limited, retrying in ${delay + jitter}ms`);
await sleep(delay + jitter);
continue;
}
throw error; // Non-rate-limit error
}
}
}

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

3. Respect Retry-After Header

Use the retry_after value from error responses:

async function handleRateLimit(error) {
if (error.response?.status === 429) {
const retryAfter = error.response.data.retry_after;

if (retryAfter) {
const waitTime = (retryAfter - Math.floor(Date.now() / 1000)) * 1000;

if (waitTime > 0) {
console.log(`Waiting ${waitTime}ms before retry`);
await sleep(waitTime);
return true; // Can retry
}
}
}
return false; // Cannot retry
}

4. Queue Management

Implement request queuing to stay within limits:

class RateLimitedQueue {
constructor(requestsPerSecond = 1) {
this.queue = [];
this.processing = false;
this.interval = 1000 / requestsPerSecond;
this.lastRequest = 0;
}

async add(requestFunction) {
return new Promise((resolve, reject) => {
this.queue.push({ requestFunction, resolve, reject });
this.process();
});
}

async process() {
if (this.processing || this.queue.length === 0) {
return;
}

this.processing = true;

while (this.queue.length > 0) {
const now = Date.now();
const timeSinceLastRequest = now - this.lastRequest;

if (timeSinceLastRequest < this.interval) {
await sleep(this.interval - timeSinceLastRequest);
}

const { requestFunction, resolve, reject } = this.queue.shift();

try {
const result = await requestFunction();
resolve(result);
} catch (error) {
reject(error);
}

this.lastRequest = Date.now();
}

this.processing = false;
}
}

// Usage
const queue = new RateLimitedQueue(0.5); // 0.5 requests per second

queue.add(() => submitData(data1));
queue.add(() => submitData(data2));
queue.add(() => submitData(data3));

Best Practices

1. Batch Operations

When possible, batch multiple operations:

// Instead of multiple single submissions
for (const item of items) {
await submitData(item); // Uses many API calls
}

// Consider batching if supported
const batchData = {
submissions: items.map(item => ({
workflow_id: workflowId,
data: item
}))
};
await submitBatch(batchData); // Uses one API call

2. Cache Responses

Cache responses to avoid repeated requests:

class ApiCache {
constructor(ttl = 300000) { // 5 minutes default
this.cache = new Map();
this.ttl = ttl;
}

get(key) {
const item = this.cache.get(key);
if (item && Date.now() - item.timestamp < this.ttl) {
return item.data;
}
this.cache.delete(key);
return null;
}

set(key, data) {
this.cache.set(key, {
data,
timestamp: Date.now()
});
}
}

3. Monitor and Alert

Set up monitoring for rate limit usage:

function monitorRateLimit(response) {
const remaining = parseInt(response.headers['x-ratelimit-remaining']);
const limit = parseInt(response.headers['x-ratelimit-limit']);
const usagePercent = ((limit - remaining) / limit) * 100;

// Alert thresholds
if (usagePercent > 90) {
sendAlert('CRITICAL: Rate limit usage above 90%');
} else if (usagePercent > 75) {
sendAlert('WARNING: Rate limit usage above 75%');
}

// Log metrics
logMetric('api.rate_limit.usage_percent', usagePercent);
logMetric('api.rate_limit.remaining', remaining);
}

4. Graceful Degradation

Implement fallback strategies when rate limited:

async function submitWithFallback(data) {
try {
return await submitData(data);
} catch (error) {
if (error.response?.status === 429) {
// Store for later processing
await storeForLaterProcessing(data);
return { queued: true, message: 'Submission queued due to rate limit' };
}
throw error;
}
}

Rate Limit Configuration

Rate limits can be configured via environment variables:

# Global monthly limit (all users combined)
API_GLOBAL_MONTHLY_LIMIT=1000

# Per-user daily limit
API_USER_DAILY_LIMIT=100

# Per-IP hourly limit
API_IP_HOURLY_LIMIT=50

# Enable/disable rate limiting
API_RATE_LIMITING_ENABLED=true

Troubleshooting Rate Limits

Common Issues

  1. Unexpected Rate Limits

    • Check if multiple systems share the same API user
    • Verify IP address isn't shared with other services
    • Review request patterns for efficiency
  2. Inconsistent Limits

    • Rate limits reset at specific times (midnight for daily, month start for monthly)
    • Multiple concurrent requests may hit limits simultaneously
  3. Performance Impact

    • Implement proper retry logic to avoid cascading delays
    • Use queuing to smooth out request patterns
    • Monitor response times during high usage

Debugging Tools

// Rate limit debugging utility
function debugRateLimit(response) {
const headers = response.headers;

console.log('Rate Limit Debug Info:', {
limit: headers['x-ratelimit-limit'],
remaining: headers['x-ratelimit-remaining'],
reset: new Date(headers['x-ratelimit-reset'] * 1000),
type: headers['x-ratelimit-type'],
resetIn: headers['x-ratelimit-reset'] - Math.floor(Date.now() / 1000)
});
}

Proper rate limit handling ensures your integration remains stable and responsive while respecting system resources and fair usage policies.