Rate Limits
Understand API rate limits, headers, and best practices for handling throttling.
Overview
Rate limits protect the API from abuse and ensure fair usage for all users. Limits vary by plan and endpoint type.
Rate Limit Tiers
By Plan
| Plan | Requests/Minute | Requests/Hour | Requests/Day |
|---|---|---|---|
| Free | 60 | 1,000 | 10,000 |
| Pro | 300 | 10,000 | 100,000 |
| Team | 600 | 30,000 | 300,000 |
| Enterprise | 1,200+ | Custom | Unlimited |
By Endpoint Type
| Endpoint Type | Multiplier | Example |
|---|---|---|
| Read (GET) | 1x | List agents |
| Write (POST/PUT) | 2x | Create agent |
| Delete | 2x | Delete workflow |
| Execute | 5x | Execute workflow |
| Chat | 10x | Chat with agent |
Example: On a Pro plan (300/min), you can make:
- 300 GET requests per minute, OR
- 150 POST requests per minute, OR
- 30 chat requests per minute
Rate Limit Headers
Every API response includes rate limit information:
HTTP/1.1 200 OK
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 287
X-RateLimit-Reset: 1702469460
X-RateLimit-Window: 60
Header Descriptions
| Header | Description | Example |
|---|---|---|
X-RateLimit-Limit | Maximum requests per window | 300 |
X-RateLimit-Remaining | Requests remaining in current window | 287 |
X-RateLimit-Reset | Unix timestamp when window resets | 1702469460 |
X-RateLimit-Window | Window size in seconds | 60 |
Rate Limit Exceeded
When you exceed the rate limit, you'll receive a 429 Too Many Requests response:
json{ "error": { "code": "rate_limited", "message": "Rate limit exceeded. Please retry after 30 seconds.", "details": { "limit": 300, "window": 60, "retry_after": 30 } } }
Response Headers
HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1702469460
Handling Rate Limits
Basic Retry Logic
javascriptasync function fetchWithRetry(url, options, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { const response = await fetch(url, options); if (response.status === 429) { const retryAfter = response.headers.get('Retry-After') || 30; console.log(`Rate limited. Retrying after ${retryAfter}s...`); await sleep(retryAfter * 1000); continue; } return response; } throw new Error('Max retries exceeded'); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
Exponential Backoff
javascriptasync function fetchWithBackoff(url, options) { const maxRetries = 5; let delay = 1000; // Start with 1 second for (let attempt = 0; attempt < maxRetries; attempt++) { try { const response = await fetch(url, options); if (response.status === 429) { const retryAfter = response.headers.get('Retry-After'); const waitTime = retryAfter ? retryAfter * 1000 : delay; console.log(`Rate limited. Waiting ${waitTime}ms...`); await sleep(waitTime); delay *= 2; // Double the delay for next attempt continue; } return response; } catch (error) { if (attempt === maxRetries - 1) throw error; await sleep(delay); delay *= 2; } } }
Python with Tenacity
pythonfrom tenacity import retry, stop_after_attempt, wait_exponential import requests @retry( stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=1, max=60) ) def api_request(url, headers): response = requests.get(url, headers=headers) if response.status_code == 429: retry_after = int(response.headers.get('Retry-After', 30)) raise Exception(f"Rate limited. Retry after {retry_after}s") response.raise_for_status() return response.json()
Rate Limit Aware Client
javascriptclass RateLimitedClient { constructor(apiKey, maxRequestsPerMinute = 300) { this.apiKey = apiKey; this.maxRequests = maxRequestsPerMinute; this.requests = []; } async fetch(url, options = {}) { await this.waitForCapacity(); this.requests.push(Date.now()); this.cleanOldRequests(); const response = await fetch(url, { ...options, headers: { ...options.headers, 'Authorization': `Bearer ${this.apiKey}`, }, }); if (response.status === 429) { const retryAfter = response.headers.get('Retry-After') || 60; await this.sleep(retryAfter * 1000); return this.fetch(url, options); } return response; } async waitForCapacity() { this.cleanOldRequests(); if (this.requests.length >= this.maxRequests) { const oldestRequest = this.requests[0]; const waitTime = 60000 - (Date.now() - oldestRequest); if (waitTime > 0) { await this.sleep(waitTime); } } } cleanOldRequests() { const oneMinuteAgo = Date.now() - 60000; this.requests = this.requests.filter(t => t > oneMinuteAgo); } sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } // Usage const client = new RateLimitedClient('<your_token>'); const response = await client.fetch('https://api.arcanflows.io/v1/agents');
Best Practices
1. Monitor Rate Limit Headers
Always check headers to avoid hitting limits:
javascriptasync function apiCall(url) { const response = await fetch(url, { headers }); const remaining = response.headers.get('X-RateLimit-Remaining'); const reset = response.headers.get('X-RateLimit-Reset'); if (remaining < 10) { console.warn(`Low rate limit: ${remaining} requests remaining`); console.warn(`Resets at: ${new Date(reset * 1000)}`); } return response.json(); }
2. Batch Requests
Instead of individual requests, use batch endpoints:
javascript// ❌ 100 individual requests for (const id of agentIds) { await fetch(`/v1/agents/${id}`); } // ✅ Single batch request await fetch('/v1/agents/batch', { method: 'POST', body: JSON.stringify({ ids: agentIds }), });
3. Use Webhooks
Instead of polling, subscribe to events:
javascript// ❌ Polling every 5 seconds setInterval(async () => { const executions = await fetch('/v1/workflows/123/executions'); }, 5000); // ✅ Webhook on completion // Configure webhook for workflow.execution.completed event
4. Cache Responses
Cache static data to reduce API calls:
javascriptconst cache = new Map(); const CACHE_TTL = 5 * 60 * 1000; // 5 minutes async function getAgent(id) { const cacheKey = `agent:${id}`; const cached = cache.get(cacheKey); if (cached && Date.now() - cached.timestamp < CACHE_TTL) { return cached.data; } const response = await fetch(`/v1/agents/${id}`); const data = await response.json(); cache.set(cacheKey, { data, timestamp: Date.now() }); return data; }
5. Queue and Throttle
Process requests in a controlled queue:
javascriptimport Bottleneck from 'bottleneck'; const limiter = new Bottleneck({ maxConcurrent: 10, minTime: 200, // 5 requests per second }); const results = await Promise.all( ids.map(id => limiter.schedule(() => fetch(`/v1/agents/${id}`) )) );
Endpoint-Specific Limits
Chat Endpoint
The chat endpoint has stricter limits due to higher resource usage:
| Plan | Chat Requests/Minute |
|---|---|
| Free | 6 |
| Pro | 30 |
| Team | 60 |
| Enterprise | 120+ |
Workflow Execution
POST /v1/workflows/:id/execute
| Plan | Executions/Minute | Concurrent |
|---|---|---|
| Free | 5 | 1 |
| Pro | 30 | 5 |
| Team | 60 | 10 |
| Enterprise | 300+ | 50+ |
Bulk Operations
POST /v1/agents/batch
POST /v1/workflows/batch
Limited to 100 items per request. Counts as 1 request regardless of batch size.
Increasing Your Limits
Upgrade Plan
Higher plans include increased rate limits:
| Upgrade | Limit Increase |
|---|---|
| Free → Pro | 5x |
| Pro → Team | 2x |
| Team → Enterprise | Custom |
Request Limit Increase
Enterprise customers can request custom limits:
- Contact [email protected]
- Describe your use case
- Provide expected request volume
- We'll configure custom limits
Burst Capacity
All plans include burst capacity for short spikes:
| Plan | Burst Capacity |
|---|---|
| Free | 2x for 10 seconds |
| Pro | 3x for 30 seconds |
| Team | 5x for 60 seconds |
| Enterprise | Custom |
Monitoring Usage
Dashboard Metrics
View rate limit usage in your dashboard:
- Requests per minute/hour/day
- Rate limit hits
- Top endpoints by usage
- Usage trends
API Usage Endpoint
GET /v1/usage
json{ "data": { "period": "2025-12-07", "requests": { "total": 15420, "by_endpoint": { "agents": 8500, "workflows": 4200, "forms": 2720 } }, "rate_limits": { "hits": 3, "current_usage": 0.72 } } }
Troubleshooting
Unexpected 429 Errors
- Check concurrent requests: Multiple services using same key
- Verify plan limits: May have hit daily/hourly limits
- Check endpoint multipliers: Some endpoints cost more
- Review burst usage: Short spikes can trigger limits
Limits Reset Unexpectedly
- Rate limit windows are rolling, not fixed
- Each request removes from a 60-second sliding window
X-RateLimit-Resetshows when oldest request expires