Rate limits.
Per-key request budgets on the control-plane REST API — creating agents, listing calls, starting campaigns. Limits are counted per API key in a fixed 60-second window. The default budget is 60 requests per minute per key.
Budgets#
The first request on a key opens a 60-second window; every request in that window counts against the key's budget. When the window expires, the count resets in full — there is no gradual token refill. Exceeding the budget returns 429 until the reset.
The limit is stored per key (rate_limit_rpm), so different keys — say, a high-volume backfill key and a low-volume production key — can carry different budgets. Need more than 60? Ask at support@whizztech.ai.
Rate limits vs. concurrency#
These are two different limits — don't confuse them:
- Rate limits cap how many API requestsyou make — creating agents, listing calls, starting campaigns, reading usage. That's what this page is about.
- Concurrency caps how many live calls run at once. That's a separate thing, governed by the concurrent lines included in your plan — not by the request budget here.
In other words: one POST /v1/calls is one request against your rate limit, but the call it starts occupies a concurrent line for its whole duration. A campaign can hold many lines at once while making very few API requests.
Headers#
Every authenticated response reports where you stand:
HTTP/1.1 200 OK
Content-Type: application/json
x-ratelimit-limit: 60
x-ratelimit-remaining: 57
x-ratelimit-reset: 1751795160| Field | Type | Description |
|---|---|---|
| x-ratelimit-limit | integer | The key's budget per 60-second window. |
| x-ratelimit-remaining | integer | Requests left in the current window. |
| x-ratelimit-reset | unix seconds | When the current window resets and the budget refills. |
| retry-after | seconds | Only on 429: how long to wait before retrying (minimum 1). |
The 429 body uses the standard error envelope with code rate_limit_exceeded.
Handling 429#
Honor retry-after. It is computed from the actual window reset, so waiting exactly that long always succeeds. A minimal client:
async function withRetry(fn, maxRetries = 2) {
for (let attempt = 0; ; attempt++) {
const res = await fn();
if (res.status !== 429 || attempt >= maxRetries) return res;
const retryAfter = Number(res.headers.get("retry-after") ?? 1);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
}
}- Poll with backoff, not a tight loop. A 1-second poll on 10 in-flight calls is 600 requests/min — 10x the default budget. Grow the interval (say 3s to 15s) to stay well under it.
- Prefer webhooks for completion. One
call.completeddelivery replaces an entire polling session — see Webhooks. - Watch remaining, not 429s. Every authenticated response carries the headers, so alerting when
x-ratelimit-remainingtrends toward zero is free and beats reacting after the fact. - Split keys by workload. Give bulk backfills their own key so a burst there can never starve production traffic.