Skip to content

Telefonie ​

De unde sosesc apelurile și pe unde pleacă: numere cumpărate printr-un cont de carrier gestionat, numere pe care le ai deja, conturile din spatele lor și rutele care decid prin ce trunk pleacă un apel.

Pentru trunk-ul tău de carrier, vezi Trunking SIP.

Ce poate face fiecare carrier ​

Sunt declarate nouă id-uri de furnizor. Șase au adaptor; zadarma, didlogic și chime aruncă un 501 în care se numesc și îți spun să aduci DID-ul peste un trunk SIP.

Fiecare adaptor își declară propria matrice de capabilități, și diferă enorm. Citește matricea, nu numele. GET /telephony/providers întoarce versiunea vie.

TwilioTelnyxPlivoDIDWWBYO SIPCustom SIP
Caută numere disponibile✅✅✅✖✖✖
Cumpără / eliberează✅✅✅✖✖✖
Listează numerele din cont✅✅✅✅✖◐
Configurează webhook-urile numărului✅✅✅✖——
Voce de intrare✅✅✖✅¹✅✅
Voce de ieșire✅✅✅✖✅✅
Media streams (ajungerea în pipeline-ul AI)✅⚠️²✖—✅³✅³
Înregistrare✅✅✖—✅✅
Înregistrare pe două canale✅✅✖—✅✅
Transfer✅✅✖✖✅✅
DTMF✅✅✅✖✅✅
Detecție de robot telefonic✅◐⁴✖✖✖✖
SMS / MMS✅✅✅ (SMS)✖✖✖
Semnătură de webhook✅✅✅✖✅✅
Trunking SIP◐⁵✅——✅✅

✅ implementat · ◐ parțial · ✖ neimplementat · — nu se aplică

  1. Purtată peste un trunk SIP CallAgent, nu peste API-ul propriu al DIDWW.
  2. Telnyx declară media_streams ca implementat și nimic din cod nu construiește o instrucțiune de media stream pentru Telnyx. placeOutboundCall() trimite un URL de webhook Call-Control și se oprește. Un apel Telnyx nu ajunge azi la un asistent. E o capabilitate declarată care contrazice runtime-ul — tratează runtime-ul ca autoritar.
  3. Declarat implementat, și blocat în practică de aceeași predare media care blochează SIP-ul. Vezi SIP și Ce nu este construit.
  4. Telnyx expune AMD doar pe leg-uri Call Control pornite cu answering_machine_detection setat la apelare, niciodată retroactiv.
  5. Twilio Elastic SIP Trunking se configurează în afara acestui API.

Rezumatul practic

Twilio e singurul carrier dovedit că duce un apel în pipeline-ul AI. Restul ori declară o capabilitate pe care nu o pot livra cap-coadă, ori nu o declară.

Comentariul propriu al DIDWW explică de ce calea de cumpărare lipsește, în loc să fie un stub: o comandă DIDWW e un flux în mai mulți pași (țară → oraș → grup de DID-uri → comandă → pool de capacitate) care are nevoie de decizii de produs pe care nu le-a luat nimeni, iar interfața nu are voie să ofere un buton Cumpără care merge pe jumătate.

Conturi de telefonie și credențiale ​

Un cont leagă o credențială de furnizor de o organizație. Credențialele sunt criptate cu o cheie separată de APP_KEY (CredentialCipher, decizia D-004), deci rotirea cheii aplicației nu atinge secretele de furnizor ale tenanților.

GET/POST         /api/v1/telephony/accounts
GET/PATCH/DELETE /api/v1/telephony/accounts/{id}
POST             /api/v1/telephony/accounts/{id}/health
GET              /api/v1/telephony/providers

Credențiala Twilio e account_sid + auth_token; cele Telnyx și DIDWW sunt api_key. Vezi Pornire rapidă, pasul 2.

Numere ​

API-ul ăsta e camelCase

Endpoint-urile de telefonie primesc nume de câmp în camelCase (friendlyName, destinationType, sipTrunkId). Endpoint-urile de asistenți, tools și apeluri primesc snake_case. E o inconsecvență a produsului; paginile de aici arată ce acceptă efectiv fiecare endpoint.

Înregistrează un număr pe care îl ai deja ​

bash
curl -X POST "$CALLAGENT_API_URL/telephony/phone-numbers" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "number": "+40312345901",
    "friendlyName": "Suport",
    "assistantId": "01JQ…",
    "capabilities": ["voice"]
  }'

Valorile din capabilities sunt voice, sms, mms, fax. Rândul se creează cu provider: "byo_sip", iar dacă dai assistantId (sau squadId) se setează pentru tine destination_type și destination_id.

POST /telephony/phone-numbers/import e versiunea în masă: listează fiecare număr din contul tău de furnizor și creează rândurile.

Cumpără unul ​

bash
curl -X POST "$CALLAGENT_API_URL/telephony/phone-numbers/search" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "provider": "twilio", "country": "RO", "type": "local", "limit": 20 }'

provider și un country din două litere sunt obligatorii. type e local, mobile, toll_free sau national și e implicit local. Apoi:

bash
curl -X POST "$CALLAGENT_API_URL/telephony/phone-numbers/purchase" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "provider": "twilio", "number": "+40312345678", "friendlyName": "Suport" }'

O cumpărare are trei pași: cumperi la carrier, inserezi rândul, trimiți webhook-urile. Dacă pasul cu webhook-urile eșuează, numărul nu se dă înapoi — e al tău, deci rândul rămâne, cu webhooks_in_sync: false și cu eșecul adăugat în notes. Rulează configure din nou.

Configurare și eliberare ​

POST /api/v1/telephony/phone-numbers/{id}/configure   trimite URL-urile de webhook la carrier
POST /api/v1/telephony/phone-numbers/{id}/release     eliberează la carrier
DELETE /api/v1/telephony/phone-numbers/{id}           șterge doar rândul (soft delete)

configure nu primește niciun body. Răspunde { "configured": true, "webhooks_in_sync": true } sau un 502. Nu alege o destinație — aia e treaba lui PATCH.

Îndreaptă un număr undeva ​

bash
curl -X PATCH "$CALLAGENT_API_URL/telephony/phone-numbers/$NUMBER_ID" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "destinationType": "assistant",
    "destinationId": "01JQ…",
    "recordingMode": "dual_channel",
    "maxConcurrentCalls": 10
  }'

Tipurile de destinație, toate zece:

TipAre nevoie de
assistant, squad, queue, ivr, ring_group, extension, voicemaildestinationId
external, sip_uridestinationValue
hookhookUrl și câmpurile de hook de mai jos

Alte câmpuri de PATCH care merită știute: inboundEnabled, outboundEnabled, outboundCallerIdNumber / Name, smsEnabled, smsAssistantId, recordingMode (disabled · mono · dual_channel · inherit), recordingConsentRequired, callerIdPolicyId, status (pending · active · suspended · released · error), metadata.

hookSecret nu e atribuit niciodată pe calea obișnuită — coloana ține text criptat, iar scrierea unui secret brut în atribut l-ar stoca în clar.

Cum e rutat un apel de intrare ​

Prima potrivire câștigă:

  1. o rută de intrare care se potrivește pe număr, după prioritate;
  2. hook-ul dinamic al numărului, dacă hookEnabled;
  3. destinația statică a numărului;
  4. doar pentru un apel pe trunk SIP, destinația de rezervă a trunk-ului.

Căutarea numărului trece intenționat peste tenanți, pentru că încă nu e legat niciun tenant, iar organizația e legată apoi din rândul care s-a potrivit — niciodată din ceva aflat în request. Un număr care nu se potrivește cu nimic e respins, nu rutat către o destinație implicită.

Hook-uri dinamice ​

În loc de o destinație fixă, un număr poate întreba serverul tău în momentul apelului:

CâmpCe înseamnă
hookUrlUnde întreabă.
hookSecretSemnează request-ul, ca la un webhook.
hookTimeoutMs200–3000. Peste atât, se aplică rezerva.
hookFallbackstatic · reject · voicemail — ce se face când serverul tău e lent sau greșit.

Alege rezerva deliberat. static e iertător; reject e cinstit; nimic nu e mai rău decât un apelant care aude tăcere pentru că un hook a expirat.

Apeluri de ieșire ​

Două endpoint-uri inițiază un apel, și nu sunt același lucru:

EndpointCând îl folosești
POST /api/v1/callsCel normal. Condus de un asistent, cu verificări de admitere, idempotent, produce o înregistrare completă de apel. snake_case.
POST /api/v1/telephony/callsInițiere printr-un cont de telefonie, direct, pentru apelare în stil PBX care nu e un apel cu asistent.
bash
curl -X POST "$CALLAGENT_API_URL/calls" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "assistant_id": "01JQ…",
    "phone_number_id": "01JQ…",
    "to": "+40712345678",
    "idempotency_key": "comanda-4471-callback",
    "answering_machine_detection": true,
    "ring_timeout_seconds": 30
  }'

Body-ul complet: to (obligatoriu), from, assistant_id, assistant_version_id, assistant_overrides, squad_id, phone_number_id, sip_trunk_id, customer_id, campaign_id, transport (pstn · sip · web · mobile), provider, variables, metadata, idempotency_key, answering_machine_detection, ring_timeout_seconds (5–300).

Admiterea ​

Înainte să existe un leg, în ordinea asta:

  1. Politica de destinație — organizația activă și neblocată, direcția activată, allowlist, blocklist, politica de țară, clasa numărului (premium 1900/1976, satelit 870–874 și 881–883, Coreea de Nord 850, plus restul tabelelor de prefixe cu cost mare), plafoane orare, zilnice și de minute pe zi.
  2. Rutare — trebuie să se potrivească o rută de ieșire; o rută blocked aruncă route_blocked.
  3. Caller ID — un pool dintr-o politică de caller ID, sau numărul cerut de tine doar dacă organizația ta îl deține; altfel unverified_caller_id (403).
  4. Concurență — included_concurrency + burst_concurrency, plus dimensiunile per campanie, per număr și per trunk. Un refuz e concurrency_limit (429).
  5. Inițiere — iar dacă carrier-ul aruncă, slotul de concurență se eliberează imediat, pentru că nimeni nu va trimite vreodată un hangup pentru un apel care nu a început.

Separat, admiterea de facturare rulează cu verificările ieftine întâi: statusul organizației, expirarea trialului și verificarea telefonului, blocajul de dunning, plafonul zilnic de cheltuială, plafoanele de volum ale planului, fondurile, concurența. Nu debitează un cost estimat în avans; consumul e contorizat continuu.

Refuzurile ajung ca ended_reason pe apel, nu ca un 500 opac:

ended_reasonCauzat de
concurrency_limitRefuz de concurență.
destination_blockedRută blocată, blocklist, țară blocată, prefix blocat, premium, satelit, cost mare, orice plafon.
telephony_errorDestinație lipsă, nicio rută, from lipsă, caller ID neverificat, furnizor necunoscut, neimplementat, credențială lipsă, eroare de conectare.
provider_errorOrice altceva a spus carrier-ul.

Idempotență ​

Dacă omiți o cheie, ți se generează una. Cu o cheie, o repetare întoarce rândul de apel existent și nu mai sună o dată — impus de un index unic pe (organization_id, idempotency_key), nu doar de logica aplicației.

O singură rezervă: repetarea nu e comparată cu request-ul original. Refolosirea unei chei cu un body diferit întoarce apelul original, nu o eroare.

Rute ​

GET/POST     /api/v1/telephony/inbound-routes
PATCH/DELETE /api/v1/telephony/inbound-routes/{id}
GET/POST     /api/v1/telephony/outbound-routes
PATCH/DELETE /api/v1/telephony/outbound-routes/{id}

Rutele de intrare se potrivesc pe un număr și bat destinația proprie a numărului. Rutele de ieșire aleg trunk-ul sau contul prin care pleacă un număr apelat, iar o rută poate fi blocked ca să refuze o destinație direct.

Transferuri ​

Există opt moduri de transfer: blind, warm, assistant_warm, dynamic, sip, phone, queue, on_hold_specialist. Consultative: warm, assistant_warm și on_hold_specialist.

Contractul le scrie altfel

TransferPlanSchema din @callagent/contracts folosește blind, warm, warm_with_summary, warm_with_assistant, hold_specialist. Enum-ul PHP care conduce serviciul de transfer folosește cele opt de mai sus. Nu se mapează unu la unu. Peste API-ul REST folosește ortografia din PHP.

Transferul warm e implementat cu comenzi FreeSWITCH (hold, originate, park, bridge) și nu are echivalent Twilio. Pe un apel Twilio, un transfer „warm" e o intrare în conferință, iar o consultare privată între doi operatori nu e oferită, în loc să fie simulată. Vezi Contact center.

API ​

GET/POST         /api/v1/telephony/phone-numbers
POST             /api/v1/telephony/phone-numbers/search
POST             /api/v1/telephony/phone-numbers/purchase
POST             /api/v1/telephony/phone-numbers/import
GET/PATCH/DELETE /api/v1/telephony/phone-numbers/{id}
POST             /api/v1/telephony/phone-numbers/{id}/configure
POST             /api/v1/telephony/phone-numbers/{id}/release

GET/POST         /api/v1/telephony/accounts
GET/PATCH/DELETE /api/v1/telephony/accounts/{id}
POST             /api/v1/telephony/accounts/{id}/health
GET              /api/v1/telephony/providers

GET/POST         /api/v1/telephony/inbound-routes
PATCH/DELETE     /api/v1/telephony/inbound-routes/{id}
GET/POST         /api/v1/telephony/outbound-routes
PATCH/DELETE     /api/v1/telephony/outbound-routes/{id}

POST             /api/v1/telephony/calls

Parametri și răspunsuri complete: numere, conturi, rute, apeluri de ieșire.

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