Skip to content

Rate limits ​

Un singur limiter acoperă /api/v1. Numără request-uri pe minut, per cheie API.

Cifrele ​

Cine apeleazăLimitaNumărată după
Un request autentificatlimita planului organizației, sau 300/min când planul nu setează unaid-ul cheii API
Un request neautentificat60/minIP-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.

PlanRequest-uri / minut
Starter60
Growth300
Enterprise1200
Fără plan300 (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:

http
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 287

Un 429 poartă încă două:

http
Retry-After: 34
X-RateLimit-Reset: 1767268800

Retry-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 ​

json
{
  "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.

js
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
<?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.');
}
bash
# --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 402 cu error.type: "plan_limit_exceeded" și error.code numind entitlement-ul (max_assistants, max_api_keys, phone_numbers, …), plus limit și used.
  • 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" cu error.code: "rate_limited" și propriul Retry-After, care este fix 5 secunde, nu o fereastră calculată.
  • Dimensiunile de pagină per endpoint — limit pe endpoint-urile Engage și per_page pe cele Platform și Billing, fiecare plafonat la 100. Nu este un rate limit; este doar un plafon pentru un singur răspuns.

Scrisă după cod. Ce nu este încă implementat este marcat ca atare, niciodată descris ca funcțional.