Limite de rate
Ce e limitat, ce nu e și cum arată un 429.
API-ul
/api/v1 e limitat per cheie de API, nu per organizație și nu per IP.
| Implicit | 300 de request-uri pe minut per cheie |
| Suprascris de | api_rate_limit_per_minute al planului tău, dacă planul îl declară |
| Neautentificat | 60 pe minut per IP — accesibil doar înainte să se rezolve o cheie |
| Cheia bucket-ului | api_key:<id-ul cheii> |
Limitarea pe cheie și nu pe IP e intenționată: mai mulți tenanți pot împărți un IP de ieșire, iar un client zgomotos nu are voie să îl limiteze pe altul. Consecința care merită știută: o organizație cu trei chei de API primește trei bucket-uri separate de 300/minut. Dacă împarți traficul între servicii, împarte și cheile.
Headere
Fiecare răspuns poartă:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 292Un 429 adaugă:
Retry-After: 37
X-RateLimit-Reset: 1735689637Retry-After e în secunde. Respectă-l, în loc să faci backoff după un calendar propriu — e exact timpul până la resetarea bucket-ului.
Body-ul de 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
async function call(path: string, init: RequestInit, attempt = 0): Promise<Response> {
const response = await fetch(`${base}${path}`, init);
if (response.status !== 429 || attempt >= 5) return response;
const retryAfter = Number(response.headers.get('Retry-After') ?? '1');
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
return call(path, init, attempt + 1);
}Două lucruri pe care le face bine, spre deosebire de o reîncercare naivă: folosește Retry-After în loc să ghicească, și limitează numărul de încercări, ca o cheie permanent limitată să producă o eroare, nu o blocare.
Urmărește X-RateLimit-Remaining și încetinește înainte să ajungi la zero. Un client care reacționează doar la 429 petrece limitat o parte din fiecare minut.
Celelalte limitatoare
| Limitator | Se aplică la | Limită | Cheie |
|---|---|---|---|
api | Tot ce e sub /api/v1, și POST /mcp | 300/min (sau ce spune planul) | cheia de API |
internal | Canalul gateway → control plane | niciuna | — |
webhooks | Callback-uri semnate de furnizori, care sosesc (Twilio, Telnyx, Stripe) | 1000/min | IP-ul sursă |
auth | Login și resetare de parolă | 5/min | e-mail cu litere mici + IP |
demo-showcase | Demo-ul public de vânzări | 600/min | IP-ul sursă |
Canalul intern e intenționat nelimitat: raportează sute de evenimente pe apel, iar limitarea lui ar arunca istoricul apelurilor, nu ar proteja ceva.
Limitatorul webhooks e pentru callback-uri care sosesc la CallAgent. Livrarea de webhook-uri către endpoint-ul tău nu e limitată — e guvernată de reîncercări și backoff.
Limite care nu sunt limite de request-uri
Astea opresc un apel, nu un request, și nu sunt 429-uri:
| Limită | Unde mușcă | Ce vezi |
|---|---|---|
| Concurență | included_concurrency + burst_concurrency, plus per campanie, per număr și per trunk | ended_reason: concurrency_limit |
| Plafon zilnic de cheltuială | Înainte de apel | ended_reason: insufficient_credit |
| Plafoane de volum ale planului | Minute și apeluri lunare | ended_reason: insufficient_credit |
| Plafoane de destinație | Orare, zilnice și de minute pe zi, per destinație | ended_reason: destination_blocked |
| Capacitatea cozii | max_waiting_calls pe o coadă, implicit 100 | destinația de overflow a cozii |
| Drepturile planului | Numărul de asistenți și altele | HTTP 402 plan_limit_exceeded, cu limit și used |
Observă 402-ul: un plafon de plan e o limită comercială, iar un 429 ar spune clientului să reîncerce ceva ce nu va reuși niciodată.
Există o tabelă de suprascrieri per tenant și nu face nimic
rate_limit_overrides are coloanele organization_id, bucket, per_minute, burst, reason și expires_at, și niciun cod nu o citește. Singura suprascriere care funcționează e api_rate_limit_per_minute al planului. Nu-ți face planuri pe limite personalizate per bucket.
Limitele furnizorilor sunt separate
Un 429 de la OpenAI, Deepgram sau ElevenLabs e al lor, nu al nostru. Iese la suprafață ca:
{ "error": { "type": "provider_error", "code": "provider_rate_limit",
"provider": "deepgram", "retryable": true } }cu HTTP 429 și un Retry-After: 5. Într-un apel viu se ocupă în schimb lanțul de fallback: etapa trece la următorul furnizor și emite provider.fallback cu reason: rate_limit. Configurează un fallback pentru orice furnizor pe care te bazezi.
Mai departe
- Când ceva eșuează — restul suprafeței de erori.
- Convențiile API — paginare, formate, id-uri.