Docs

Rate Limits

Requests per minute by plan, the headers that report them, and how to handle 429.

Limits

Each API key has a budget of requests per minute, set by your organization's plan:

PlanRequests per minute
Free60
Paid600

The window is a fixed calendar minute. Limits apply per API key, so two keys each have their own budget. Subscriptions from before the current pricing keep their limit; GET /api/v1/me shows the limit of your key.

MCP tool calls count against the same limits: per API key, or, for assistants signed in with OAuth, per connected app and user.

Response headers

Every response carries the current state:

HeaderDescription
X-RateLimit-LimitRequests allowed per minute
X-RateLimit-RemainingRequests left in the current minute
X-RateLimit-ResetUnix time (seconds) when the minute ends

When you hit the limit

The API answers 429 with a Retry-After header in seconds:

{
    "error": {
        "type": "rate_limit_error",
        "code": "rate_limited",
        "message": "Too many requests, retry after the time in Retry-After",
        "docUrl": "https://feedhorizon.com/docs/api/errors#rate_limit_error",
        "requestId": "req_m2x81a0kfd3h9s"
    }
}
Node.js
async function apiCall(url, options, maxRetries = 3) {
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
        const res = await fetch(url, options);
        if (res.status !== 429) return res;
        const wait = Number(res.headers.get('Retry-After') ?? 60);
        await new Promise((r) => setTimeout(r, wait * 1000));
    }
    throw new Error('Rate limit: retries exhausted');
}

Tips

  1. Slow down when X-RateLimit-Remaining gets low instead of waiting for a 429.
  2. Use webhooks instead of polling post status.
  3. Send writes with an Idempotency-Key, so a retry after a 429 or a timeout never creates a duplicate.