Skip to content

Erori ​

Există două vocabulare de erori în CallAgent, iar confundarea lor este cea mai frecventă greșeală de integrare. Citește această secțiune înainte de tabele.

error.typeerror.code
UndeOrice răspuns HTTP non-2xx de la /api/v1Eșecuri de provider, evenimente de apel, respingeri de tool
Răspunde la„ce fel de eșec a fost acest request?"„ce anume a mers prost în pipeline-ul de voce?"
Dimensiune10 valori din rendererul central, plus câteva locale55 de valori, comune pe toată platforma
Mereu prezentDa, pe o eroare randatăNu — doar pe erorile care au unul

Dacă scrii un client HTTP, ramifică pe error.type și pe codul de status.error.code este un detaliu în plus atunci când există, și lipsește frecvent. Dacă tratezi un eveniment de apel sau un payload de webhook, error.code este cel de care ai nevoie.

Forma erorii HTTP ​

json
{
  "error": {
    "type": "validation_error",
    "message": "The request payload failed validation.",
    "errors": { "name": ["The name field is required."] },
    "documentation_url": "https://docs.callagent.dev/api/errors#validation",
    "request_id": "01JQ8Z9K3T4V5W6X7Y8Z9A0B1C"
  }
}

type și message sunt mereu prezente. errors, code, retryable, provider, provider_request_id, limit, used și request_id apar acolo unde se aplică.

request_id nu apare pe fiecare răspuns

Apare pe erorile randate de handlerul central. Nu apare pe niciun 2xx și nu apare pe erorile ridicate de middleware-ul de autentificare și de câteva controllere care își construiesc propriul body — inclusiv pe o cheie API greșită, cea mai frecventă eroare pe care o vei vedea. Niciun răspuns nu poartă nici header-ul X-Request-Id.

Poți furniza tu unul: trimite X-Request-Id pe request și eroarea randată ți-l va întoarce, trunchiat la 64 de caractere. Aceasta este modalitatea sigură de a corela un eșec cu propriile tale log-uri.

Tipurile ​

typeStatusCe înseamnă
authentication_error401 403Cheie lipsă, invalidă, revocată sau expirată; sau o cheie nepermisă de la acest origin ori de la această adresă.
organization_error403Organizația este suspendată sau altfel inactivă.
permission_error403Autentificat, dar cheia nu poartă scope-ul necesar. Mesajul numește scope-ul.
not_found404Nu există o astfel de resursă — sau aparține altei organizații. Cele două sunt deliberat imposibil de deosebit.
validation_error422Body-ul a picat validarea. errors este o mapă indexată pe câmpuri.
plan_limit_exceeded402Un plafon de plan. code numește entitlement-ul, cu limit și used.
rate_limit_error429Prea multe request-uri. retryable: true, plus Retry-After.
provider_error502 504 429Un provider din amonte a eșuat. Poartă provider, code, retryable și adesea provider_request_id.
ssrf_blocked422Un URL furnizat de tine s-a rezolvat undeva unde nu are voie să ajungă. code numește motivul.
http_errorvariazăO eroare HTTP fără o mapare mai specifică.
server_error500Vina noastră. Deja raportată la noi. request_id este ce îi trebuie support-ului.

Un 500 nu întoarce niciodată mesajul excepției. message este întotdeauna Something went wrong on our side. The incident has been recorded.

Tipuri ridicate local de unele handlere ​

Acestea ocolesc rendererul central, așa că nu poartă request_id și uneori nici documentation_url. Tratează-le la fel ca pe echivalentul de mai sus.

typeStatusUnde
retrieval_error422Interogare în knowledge base
invalid_integration422Conectarea unei integrări
invalid_state422Replay al livrării unui webhook — atenție, numele sugerează 409, dar este 422
forbidden403Verificări de permisiuni în contact centre
billing_unavailable503Stripe nu este configurat pe acest deployment
telephony_error409 422 502Eșecuri de carrier și de provizionare. Un envelope diferit: {type, message, provider, code, retryable}, fără documentation_url și fără request_id.
not_implemented501Un provider de telefonie fără adapter pentru această capabilitate

Tratarea erorilor HTTP ​

Ramifică întâi pe status, apoi pe tip. Nu deduce din codul de status dacă se poate face retry — ai încredere în retryable atunci când este prezent.

js
async function call(url, init) {
  const requestId = crypto.randomUUID();
  const response = await fetch(url, {
    ...init,
    headers: { ...init.headers, 'X-Request-Id': requestId },
  });

  if (response.ok) return response.json();

  const { error } = await response.json();

  switch (response.status) {
    case 401:
    case 403:
      // Fără retry. Repară cheia, scope-urile ei sau restricția de origin.
      throw new Error(`${error.type}: ${error.message}`);

    case 422:
      // error.errors este câmp -> [mesaje] când type este validation_error.
      throw new Error(`${error.message} ${JSON.stringify(error.errors ?? {})}`);

    case 402:
      // Plafon de plan: error.code numește entitlement-ul, cu limit și used.
      throw new Error(`${error.code}: ${error.used}/${error.limit}`);

    case 429:
      return retryAfter(Number(response.headers.get('Retry-After') ?? 1));

    default:
      if (error.retryable) return retryAfter(5);
      // Dă-i support-ului request_id; dacă lipsește, folosește-l pe cel trimis de noi.
      throw new Error(`${error.type}: ${error.message} (${error.request_id ?? requestId})`);
  }
}
php
<?php

function handle(int $status, array $body, array $headers, string $requestId): never
{
    $error = $body['error'] ?? ['type' => 'unknown', 'message' => 'No error body.'];

    // Fără retry: repară cheia, scope-urile ei sau restricția de origin.
    if ($status === 401 || $status === 403) {
        throw new RuntimeException("{$error['type']}: {$error['message']}");
    }

    if ($status === 422) {
        throw new InvalidArgumentException(
            $error['message'] . ' ' . json_encode($error['errors'] ?? []),
        );
    }

    // Plafon de plan: `code` numește entitlement-ul, cu `limit` și `used`.
    if ($status === 402) {
        throw new RuntimeException("{$error['code']}: {$error['used']}/{$error['limit']}");
    }

    if ($status === 429) {
        throw new RetryLater((int) ($headers['retry-after'] ?? 1));
    }

    if (($error['retryable'] ?? false) === true) {
        throw new RetryLater(5);
    }

    // Dă-i support-ului request_id; dacă lipsește, folosește-l pe cel trimis de noi.
    throw new RuntimeException(
        "{$error['type']}: {$error['message']} (" . ($error['request_id'] ?? $requestId) . ')',
    );
}
bash
# -i ca să vezi statusul și header-ele de rate limit alături de body.
curl -i -X POST "$CALLAGENT_API_URL/assistants" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Request-Id: $(uuidgen)" \
  -d '{"name": "Support Line"}'

# Cele două câmpuri care merită citite la orice eșec:
#   .error.type       ce fel de eșec a fost
#   .error.request_id ce va cere support-ul

Vocabularul comun error.code ​

55 de coduri, definite o singură dată ca CallAgentErrorCode în @callagent/contracts și folosite identic în răspunsurile HTTP, în evenimentele de apel și în payload-urile de webhook — astfel încât provider_timeout înseamnă același lucru oriunde îl întâlnești.

Retryable ​

Zece coduri sunt retryable, iar retryable dintr-un răspuns este derivat din această listă, nu setat de mână:

provider_timeout · provider_rate_limit · provider_unavailable · provider_bad_response · mcp_connection_failed · mcp_timeout · tool_timeout · storage_failure · media_failure · rate_limited

Tot restul eșuează identic la a doua încercare. Fă retry pentru cele zece; repară-le pe celelalte.

Provideri ​

CodHTTPSemnificație
provider_timeout504Providerul nu a răspuns la timp.
provider_auth502Providerul a respins credențialul.
provider_rate_limit429Providerul ne aplică throttling.
provider_unavailable503Providerul este picat sau inaccesibil.
provider_bad_response500Providerul a răspuns cu ceva inutilizabil.
provider_quota_exceeded402Cota contului la provider este consumată.
provider_unsupported500Providerul nu suportă ce i s-a cerut.
all_providers_failed503Providerul principal și toate cele de rezervă au eșuat.
circuit_open503Breakerul este deschis; providerul este lăsat să se odihnească.

Siguranță outbound ​

CodHTTPSemnificație
ssrf_blocked403Un URL furnizat de tenant s-a rezolvat undeva unde nu are voie să ajungă.
sandbox_denied403Un tool rulat în sandbox a cerut ceva ce sandbox-ul refuză.
sandbox_timeout504Un tool rulat în sandbox a depășit limita de timp.
response_too_large500Răspunsul unui tool a depășit plafonul de dimensiune.

ssrf_blocked este 403 aici și 422 peste HTTP

Vocabularul comun mapează ssrf_blocked la 403; rendererul HTTP al control plane-ului returnează 422 pentru același eșec, pe motiv că URL-ul trimis de tine este un request greșit, nu unul interzis. Peste HTTP, așteaptă-te la 422 cu error.type: "ssrf_blocked".

Tools și MCP ​

CodHTTPSemnificație
tool_rejected500Apelul a fost refuzat — vezi motivul respingerii.
tool_timeout504Tool-ul a depășit timeoutSeconds.
tool_failed500Tool-ul a rulat și a eșuat.
tool_not_found404Nu există un astfel de tool pe acest assistant.
invalid_tool_arguments422Argumentele modelului nu s-au potrivit cu schema de parametri.
mcp_unauthorized502Serverul MCP a respins credențialul.
mcp_connection_failed500Serverul MCP nu a putut fi contactat.
mcp_tool_not_found404Tool-ul numit nu se află pe acel server.
mcp_timeout504Apelul MCP și-a depășit timeout-ul.
mcp_permission_denied403Politica sau nivelul de risc a blocat apelul.

Knowledge ​

CodHTTPSemnificație
knowledge_unavailable500Retrieval-ul a eșuat.
knowledge_insufficient500Nu a venit înapoi nimic relevant, iar assistantul este în mod knowledge-only.

Admisie ​

Verificate înainte ca un apel să fie creat, deci acestea apar pe un request de apel, nu în mijlocul conversației.

CodHTTPSemnificație
concurrency_exceeded429Nu mai există spațiu de concurență.
insufficient_credits402Portofelul nu poate finanța apelul.
organization_suspended403Organizația nu are voie să inițieze apeluri.
destination_blocked403Politica de destinații a refuzat numărul.
rate_limited429Prea multe request-uri.

POST /calls returnează 201 chiar și când originarea eșuează

Serviciul de apeluri outbound prinde eșecurile de carrier și de admisie, marchează apelul ca eșuat și răspunde totuși 201. O respingere pe concurență, o destinație blocată și o pană de carrier arată toate ca „creat" dacă te uiți doar la codul de status. Citește status și ended_reason din body-ul răspunsului, nu codul de status, ca să știi dacă apelul chiar va avea loc.

Telefonie ​

CodHTTPSemnificație
telephony_rejected500Carrierul a refuzat apelul.
sip_error500Un eșec la nivel SIP; codul de răspuns se află pe eveniment.
transfer_failed500Transferul nu s-a finalizat.
no_answer500Nu a răspuns nimeni.
media_failure500Audio-ul a încetat să circule.

Contact centre ​

CodHTTPSemnificație
no_operator_available503Niciun operator nu a putut prelua apelul.
queue_full503Coada este la max_waiting_calls.

Stocare ​

CodHTTPSemnificație
storage_failure500Scrierea sau citirea unui artifact a eșuat.
recording_unavailable500Înregistrarea nu există sau a fost ștearsă.

Configurare și lookup ​

CodHTTPSemnificație
invalid_configuration422Configurația assistantului, a tool-ului sau a trunk-ului nu este validă.
assistant_not_found404
phone_number_not_found404
call_not_found404
invalid_state_transition409Ceva a cerut unui apel să treacă într-o stare la care nu poate ajunge.

Transport și platformă ​

CodHTTPSemnificație
unauthorized401Cheie lipsă sau invalidă.
forbidden403Autentificat, dar fără permisiune — de obicei un scope lipsă.
not_found404
validation_failed422Body-ul request-ului a picat validarea; vezi errors.
idempotency_conflict409Aceeași cheie de idempotență a fost folosită pentru un alt request.
aborted500Operațiunea a fost anulată în timpul execuției.
internal_error500Varianta de rezervă. Dacă vezi asta, request_id este ce îi trebuie support-ului.

Motive de respingere a unui tool ​

Un apel de tool respins poartă un rejectionReason dintr-un vocabular separat, mai restrâns. Acesta este ce i se comunică modelului, ca să poată ocoli refuzul în loc să reîncerce:

user_rejected · forbidden_tool · permission_denied · confirmation_required · policy_blocked · ssrf_blocked · rate_limited · invalid_arguments · not_found · timeout · sandbox_denied

Vezi Tools.

Unde duce documentation_url ​

Erorile randate poartă un documentation_url. Nu-l urma.

Fiecare este un URL absolut către https://docs.callagent.dev, hard-codat la locul unde este ridicat în control plane — paisprezece literale de string separate, nu o singură valoare de configurare. Acel hostname nu este acest site și nu este servit de acest deployment, deci link-ul nu poate fi salvat printr-un redirect de aici: un client care îl urmează nu ajunge niciodată la origin-ul nostru. Nici căile nu s-ar potrivi (/api/authentication, /api/scopes, /errors/{code} — nu avem niciun arbore /api/ sau /errors/), dar hostname-ul este motivul pentru care este inaccesibil.

Tratează documentation_url ca pe un identificator stabil pentru tipul de eșec — este de încredere pentru asta, și poți ramifica pe el fără probleme — și mapează-l singur:

documentation_url se termină înCitește această pagină
/api/authentication, /api/client-auth, /api/api-keysAutentificare
/api/scopesAutentificare → Scopes
/api/rate-limitsRate limits
/api/errors, /api/errors#validation, /api/errors#serverAceastă pagină
/billing/plan-limitsRate limits → Alte limite
/webhooks#redeliveryWebhooks → Inspectare și replay
/errors/{code}Vocabularul comun de mai sus

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