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:
| Plan | Requests per minute |
|---|---|
| Free | 60 |
| Paid | 600 |
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:
| Header | Description |
|---|---|
X-RateLimit-Limit | Requests allowed per minute |
X-RateLimit-Remaining | Requests left in the current minute |
X-RateLimit-Reset | Unix 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"
}
}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
- Slow down when
X-RateLimit-Remaininggets low instead of waiting for a 429. - Use webhooks instead of polling post status.
- Send writes with an
Idempotency-Key, so a retry after a 429 or a timeout never creates a duplicate.