Rate limits
Generous quotas. Predictable headers.
Per-tier request budgets, the X-RateLimit-* response headers Link AI returns on every request, and the recommended back-off + retry strategy for staying under quota.
Link AI rate-limits every workspace independently. Limits are generous on purpose — most integrations never see a 429. When you do, the response is deterministic and easy to recover from.
Per-plan budgets
Two distinct budgets apply: request rate (per minute, against the REST API) and concurrent voice calls (the cap on outbound calls in flight at any one moment).
| Plan | Requests / minute | Concurrent voice calls |
|---|---|---|
| Starter | 120 | 5 |
| Growth | 600 | 25 |
| Scale | 3,000 | 100 |
| Enterprise | Custom | Custom |
Mailer endpoints additionally inherit campaign-level send budgets (per the configured throttle), but those caps live on the campaign object — not the API surface — so they do not return 429.
Response headers
Every response carries three headers describing the current bucket state. Read them before you start tuning — they are cheaper than trial-and-error.
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Total requests allowed in the current 60-second window. |
X-RateLimit-Remaining | Requests left in the current window. |
X-RateLimit-Reset | Unix timestamp (seconds) when the window resets. |
Retry-After | Only on 429. Seconds the client should wait before the next attempt. |
HTTP/1.1 200 OK
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 587
X-RateLimit-Reset: 1747823640
Content-Type: application/jsonWhen you hit a 429
A 429 Too Many Requests response means the workspace has spent its budget for the current window. The body always carries a structured error and the headers always include Retry-After.
{
"error": {
"code": "rate_limited",
"message": "Workspace exceeded 600 requests/minute. Retry after 38s.",
"retry_after_seconds": 38,
"request_id": "req_01HFE9X..."
}
}Recommended back-off
async function fetchWithBackoff(
url: string,
init: RequestInit,
maxAttempts = 5,
): Promise<Response> {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const res = await fetch(url, init);
if (res.status !== 429) return res;
// Honour the server's hint when present, otherwise exponential.
const hint = Number(res.headers.get("retry-after") ?? "0");
const backoffMs = hint > 0
? hint * 1000
: Math.min(30_000, 500 * 2 ** (attempt - 1));
await new Promise((r) => setTimeout(r, backoffMs));
}
throw new Error("Exceeded retry budget");
}Best practices
- Batch list reads. Use
limit=100instead of polling one-by-one. Pagination is free of the rate counter beyond the single request that yields each page. - Coalesce webhook side effects. If a webhook triggers a bulk fan-out against the API, queue the work locally — never spawn 1,000 parallel requests from a single delivery.
- Use idempotency keys. A safe retry costs nothing; an accidental duplicate call costs minutes and reputation. See API conventions.
- Monitor the headers. Alarm on
X-RateLimit-Remainingdropping below 10% ofX-RateLimit-Limitfor more than a minute. That is the early signal of an integration that is about to start failing.