Rate limits
Un singur limiter acoperă /api/v1. Numără request-uri pe minut, per cheie API.
Cifrele
| Cine apelează | Limita | Numărată după |
|---|---|---|
| Un request autentificat | limita planului organizației, sau 300/min când planul nu setează una | id-ul cheii API |
| Un request neautentificat | 60/min | IP-ul clientului |
Valoarea implicită de 300 vine din callagent.limits.api_rate_limit_per_minute și poate fi schimbată per deployment cu variabila de mediu API_RATE_LIMIT.
Bucket-ul este ținut pe cheie, nu pe organizație. Două chei din aceeași organizație primesc bugete independente, ceea ce este cel mai ieftin mod de a opri un job de fundal vorbăreț să îți înfometeze fluxul de checkout: dă-i propria lui cheie.
Bucket-ul anonim de 60/min este practic inaccesibil pe /api/v1
Autentificarea rulează înaintea limiter-ului, deci un request fără cheie este respins cu 401 înainte să fie numărat vreodată. Bucket-ul anonim există pentru suprafețele montate fără middleware-ul de cheie.
Limite per plan
Un plan poate ridica sau coborî plafonul. Limita este un entitlement al planului, deci urmează organizația, nu cheia.
| Plan | Request-uri / minut |
|---|---|
| Starter | 60 |
| Growth | 300 |
| Enterprise | 1200 |
| Fără plan | 300 (valoarea implicită a platformei) |
O organizație fără plan nu este nelimitată — cade pe valoarea implicită a platformei. Organizațiilor individuale li se poate da un override fără a schimba planul.
Îți poți citi propriul plafon și cât ai consumat din el cu GET /billing/plans, care returnează un obiect limits cu limit, used și unlimited pentru fiecare entitlement aplicat. Merită verificat înainte de un import în masă, în loc să descoperi plafonul ca 402 la jumătatea lui.
Headere
Fiecare răspuns care trece prin limiter poartă bugetul:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 287Un 429 poartă încă două:
Retry-After: 34
X-RateLimit-Reset: 1767268800Retry-After este numărul de secunde de așteptat. X-RateLimit-Reset este un timestamp Unix absolut — sunt unități diferite, și exact aici se greșește de obicei.
Corpul unui 429
{
"error": {
"type": "rate_limit_error",
"message": "Too many requests. Slow down and retry after the interval in the Retry-After header.",
"retryable": true,
"documentation_url": "https://docs.callagent.dev/api/rate-limits",
"request_id": "01JQ…"
}
}Cum îl tratezi
Așteaptă Retry-After și încearcă din nou. Nu-ți calcula propriul backoff din codul de status — serverul știe când se resetează fereastra, tu nu.
async function callWithRetry(url, init, attempts = 5) {
for (let attempt = 0; attempt < attempts; attempt += 1) {
const response = await fetch(url, init);
if (response.status !== 429) return response;
// Secunde, nu milisecunde, și nu un timestamp.
const wait = Number(response.headers.get('Retry-After') ?? 1);
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
}
throw new Error('Rate limited after every attempt.');
}<?php
function call_with_retry(callable $send, int $attempts = 5): array
{
for ($attempt = 0; $attempt < $attempts; $attempt++) {
[$status, $headers, $body] = $send();
if ($status !== 429) {
return [$status, $headers, $body];
}
// Secunde, nu milisecunde, și nu un timestamp.
sleep((int) ($headers['retry-after'] ?? 1));
}
throw new RuntimeException('Rate limited after every attempt.');
}# --retry 5 tratează 429 și 5xx; --retry-delay este un prag minim, curl respectă Retry-After.
curl --retry 5 --retry-delay 1 --retry-all-errors \
-X GET "$CALLAGENT_API_URL/calls" \
-H "Authorization: Bearer $CALLAGENT_API_KEY"Cum rămâi sub ea
Citește X-RateLimit-Remaining și încetinește înainte să ajungi la zero. Un client care reacționează doar la 429 petrece rate-limitat o parte din fiecare minut.
Folosește câte o cheie per workload. Bucket-ul este per cheie, deci un job de batch și o integrare interactivă pe chei separate nu se pot înfometa reciproc.
Urmărește links.next în loc să faci polling. Un endpoint de listare returnează un paginator; parcurgerea lui costă un request per pagină, în timp ce re-cererea primei pagini într-o buclă costă un request per poll.
Nu face polling după starea apelului. Webhooks împing evenimentele call.*; o buclă de polling pe GET /calls/{call} este cel mai frecvent mod în care o integrare își epuizează bugetul.
Alte limite, care nu sunt aceasta
Împart cuvântul „limită” și nimic altceva:
- Plafoanele de resurse ale planului — câți asistenți, cozi, campanii, baze de cunoștințe, tools, chei API, numere de telefon și locuri de operator poți avea. Depășirea unuia este un
402cuerror.type: "plan_limit_exceeded"șierror.codenumind entitlement-ul (max_assistants,max_api_keys,phone_numbers, …), pluslimitșiused. - Concurența — câte apeluri pot fi în desfășurare simultan. Depășirea ei refuză apelul, nu request-ul.
- Rate limit-urile providerilor — un provider de transcriere sau de model care ne throttle-uiește. Asta apare ca
error.type: "provider_error"cuerror.code: "rate_limited"și propriulRetry-After, care este fix 5 secunde, nu o fereastră calculată. - Dimensiunile de pagină per endpoint —
limitpe endpoint-urile Engage șiper_pagepe cele Platform și Billing, fiecare plafonat la 100. Nu este un rate limit; este doar un plafon pentru un singur răspuns.