Skip to main content
Arcanflows

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

PlanRequests/MinuteRequests/HourRequests/Day
Free601,00010,000
Pro30010,000100,000
Team60030,000300,000
Enterprise1,200+CustomUnlimited

By Endpoint Type

Endpoint TypeMultiplierExample
Read (GET)1xList agents
Write (POST/PUT)2xCreate agent
Delete2xDelete workflow
Execute5xExecute workflow
Chat10xChat 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

HeaderDescriptionExample
X-RateLimit-LimitMaximum requests per window300
X-RateLimit-RemainingRequests remaining in current window287
X-RateLimit-ResetUnix timestamp when window resets1702469460
X-RateLimit-WindowWindow size in seconds60

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

javascript
async 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

javascript
async 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

python
from 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

javascript
class 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:

javascript
async 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:

javascript
const 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:

javascript
import 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:

PlanChat Requests/Minute
Free6
Pro30
Team60
Enterprise120+

Workflow Execution

POST /v1/workflows/:id/execute
PlanExecutions/MinuteConcurrent
Free51
Pro305
Team6010
Enterprise300+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:

UpgradeLimit Increase
Free → Pro5x
Pro → Team2x
Team → EnterpriseCustom

Request Limit Increase

Enterprise customers can request custom limits:

  1. Contact [email protected]
  2. Describe your use case
  3. Provide expected request volume
  4. We'll configure custom limits

Burst Capacity

All plans include burst capacity for short spikes:

PlanBurst Capacity
Free2x for 10 seconds
Pro3x for 30 seconds
Team5x for 60 seconds
EnterpriseCustom

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

  1. Check concurrent requests: Multiple services using same key
  2. Verify plan limits: May have hit daily/hourly limits
  3. Check endpoint multipliers: Some endpoints cost more
  4. 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-Reset shows when oldest request expires