Rate Limiting
The system enforces multiple levels of rate limiting to prevent abuse and ensure fair usage across all API users.
Rate Limit Types
| Limit | Default | Scope | Reset Period |
|---|---|---|---|
| Monthly Global | 1,000 requests | All API users combined | Monthly |
| Daily Per User | 100 requests | Per API user | Daily |
| Hourly Per IP | 50 requests | Per IP address | Hourly |
Limit Hierarchy
Limits are enforced in order of precedence:
- Global Monthly: System-wide protection
- User Daily: Individual user quotas
- 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
| Header | Description | Example |
|---|---|---|
X-RateLimit-Limit | Maximum requests in current window | 100 |
X-RateLimit-Remaining | Requests remaining in window | 95 |
X-RateLimit-Reset | Unix timestamp when limit resets | 1642204800 |
X-RateLimit-Type | Which limit type is being reported | user_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 Code | Limit Type | Description |
|---|---|---|
GLOBAL_MONTHLY_LIMIT_EXCEEDED | Monthly Global | System-wide monthly limit reached |
USER_DAILY_LIMIT_EXCEEDED | Daily Per User | User's daily quota exhausted |
IP_HOURLY_LIMIT_EXCEEDED | Hourly Per IP | IP 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
-
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
-
Inconsistent Limits
- Rate limits reset at specific times (midnight for daily, month start for monthly)
- Multiple concurrent requests may hit limits simultaneously
-
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.