Rate Limits
API request limits and how to handle them.
Overview
Rate limits protect the API from abuse and ensure fair usage. Limits are applied per API key and per IP address.
Default Limits
| Scope | Limit | Window |
|---|---|---|
| Global (per key) | 100 requests | 1 minute |
| Per endpoint | 20 requests | 1 minute |
| Bulk operations | 10 requests | 1 minute |
Rate Limit Headers
Every response includes rate limit information:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1706000000| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed |
X-RateLimit-Remaining | Requests remaining in window |
X-RateLimit-Reset | Unix timestamp when limit resets |
Exceeded Response
When rate limited, you'll receive:
HTTP/1.1 429 Too Many Requests
Retry-After: 45{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests",
"details": {
"limit": 100,
"window": "60s",
"retryAfter": 45
}
}
}Handling Rate Limits
Check Headers
const response = await fetch('/api/v1/components', {
headers: { 'Authorization': 'Bearer sk_live_xxx' }
});
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));
const resetTime = parseInt(response.headers.get('X-RateLimit-Reset'));
if (remaining < 10) {
console.warn(`Only ${remaining} requests left, resets at ${new Date(resetTime * 1000)}`);
}Retry Logic
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
console.log(`Rate limited, waiting ${retryAfter}s`);
await new Promise(r => setTimeout(r, retryAfter * 1000));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}Best Practices
1. Use Exponential Backoff
const delays = [1000, 2000, 4000, 8000]; // ms
async function requestWithBackoff(fn) {
for (let attempt = 0; attempt < delays.length; attempt++) {
try {
return await fn();
} catch (error) {
if (error.status !== 429) throw error;
await new Promise(r => setTimeout(r, delays[attempt]));
}
}
throw new Error('Rate limit exceeded after retries');
}2. Batch Requests
Instead of multiple individual requests:
// Bad: 10 separate requests
for (const id of componentIds) {
await fetch(`/api/v1/components/${id}`);
}
// Good: Use list endpoint with filter
await fetch(`/api/v1/components?ids=${componentIds.join(',')}`);3. Cache Responses
const cache = new Map();
const TTL = 60000; // 1 minute
async function getCached(url) {
const cached = cache.get(url);
if (cached && Date.now() - cached.timestamp < TTL) {
return cached.data;
}
const response = await fetch(url);
const data = await response.json();
cache.set(url, { data, timestamp: Date.now() });
return data;
}4. Monitor Usage
let requestCount = 0;
function trackRequest() {
requestCount++;
if (requestCount % 50 === 0) {
console.log(`Made ${requestCount} requests this session`);
}
}Endpoint-Specific Limits
Some endpoints have stricter limits:
| Endpoint | Limit | Notes |
|---|---|---|
POST /incidents | 10/min | Incident creation |
POST /subscribers/import | 5/min | Bulk import |
POST /components/{id}/check | 60/hour | Manual health checks |
GET /public/* | 1000/min | Public endpoints |
Bulk Operations
For bulk updates, use dedicated bulk endpoints when available:
# Instead of 100 individual updates:
POST /api/v1/components/bulk
{
"updates": [
{ "id": "comp_1", "status": "operational" },
{ "id": "comp_2", "status": "operational" }
]
}Webhook Rate Limiting
Outgoing webhooks are also rate limited:
| Type | Limit |
|---|---|
| Per subscriber | 10/min |
| Per organization | 100/min |
Public API Limits
Public endpoints have higher limits but stricter abuse prevention:
| Endpoint | Limit |
|---|---|
/public/status/{slug} | 1000/min |
/public/status/{slug}/incidents | 1000/min |
/public/status/{slug}/feed | 100/min |
Increasing Limits
For higher rate limits:
- Contact support with your use case
- Implement caching on your end
- Use webhooks instead of polling
Monitoring
Track rate limit usage in your application:
class RateLimitMonitor {
constructor() {
this.limits = new Map();
}
update(endpoint, headers) {
this.limits.set(endpoint, {
limit: parseInt(headers.get('X-RateLimit-Limit')),
remaining: parseInt(headers.get('X-RateLimit-Remaining')),
reset: parseInt(headers.get('X-RateLimit-Reset'))
});
}
getStatus() {
return Object.fromEntries(this.limits);
}
isNearLimit(endpoint, threshold = 0.1) {
const info = this.limits.get(endpoint);
if (!info) return false;
return info.remaining / info.limit < threshold;
}
}