{"openapi":"3.1.0","info":{"title":"isahar Voice — API public","version":"1.0.0","description":"API public pentru datele contului tău isahar Voice: apeluri, agenți, cozi, numere, plus câteva operațiuni de scriere controlate (notă pe apel, click-to-dial). Autentificare cu o cheie API (Cont → Chei API din panoul de administrare). Secretul cheii se arată o singură dată, la creare — nu poate fi recuperat după aceea, doar rotit. Scrierea necesită scope-uri explicite (notes:write, calls:dial), niciodată acordate implicit unei chei noi, inclusiv pentru cheile vechi (fără scope-uri = doar citire, niciodată acces complet). Contul tău poate fi notificat și proactiv, prin webhooks — vezi secțiunea Webhooks de mai jos.","license":{"name":"Licență proprietară isahar Voice","url":"https://voice.isahar.io/termeni-si-conditii/"}},"servers":[{"url":"https://api.voice.isahar.io","description":"Producție"}],"security":[{"basicAuth":[]}],"tags":[{"name":"Meta","description":"Verificare autentificare și descoperire endpoint-uri."},{"name":"Calls","description":"Istoricul apelurilor contului."},{"name":"Notes","description":"Notă pe apel — scriere controlată, scope notes:write."},{"name":"Agents","description":"Agenții contului și prezența lor curentă."},{"name":"Dialing","description":"Click-to-dial — cere confirmarea explicită a agentului, nu inițiază apelul direct. Scope calls:dial."},{"name":"Queues","description":"Cozile de apeluri ale contului."},{"name":"Numbers","description":"Numerele de telefon ale contului."},{"name":"Contacts","description":"Sincronizare în masă a contactelor din agenda contului — scope contacts:write."},{"name":"Webhooks","description":"Notificări trimise DE isahar Voice către URL-urile tale, la evenimente din cont — configurate din Integrări → Webhooks."}],"paths":{"/v1/ping":{"get":{"tags":["Meta"],"summary":"Verifică autentificarea","description":"Confirmă că cheia API și secretul sunt valide. Nu necesită niciun scope — orice cheie validă (indiferent de scope-urile ei) poate apela acest endpoint.","operationId":"ping","responses":{"200":{"description":"Autentificare validă.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PingResponse"},"example":{"ok":true,"api_version":"v1","tenant_id":"acme"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/calls":{"get":{"tags":["Calls"],"summary":"Listă apeluri","description":"Necesită scope-ul `calls:read`.","operationId":"listCalls","security":[{"basicAuth":[]}],"x-required-scopes":["calls:read"],"parameters":[{"name":"from","in":"query","description":"Data de început (ISO 8601), filtrează pe started_at.","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","description":"Data de sfârșit (ISO 8601), filtrează pe started_at.","schema":{"type":"string","format":"date-time"}},{"name":"direction","in":"query","schema":{"type":"string","enum":["INBOUND","OUTBOUND"]}},{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/CallStatus"}},{"name":"agent_id","in":"query","schema":{"type":"string"}},{"name":"queue_id","in":"query","schema":{"type":"string"}},{"name":"phone_number","in":"query","description":"Format E.164 (ex: +40712345678). Se caută atât în numărul extern cât și în cel intern.","schema":{"type":"string"}},{"$ref":"#/components/parameters/Page"},{"$ref":"#/components/parameters/PerPage"},{"name":"order","in":"query","description":"Ordinea după started_at.","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}}],"responses":{"200":{"description":"Listă paginată de apeluri.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/calls/{id}":{"get":{"tags":["Calls"],"summary":"Detaliu apel","description":"Necesită scope-ul `calls:read`.","operationId":"getCall","security":[{"basicAuth":[]}],"x-required-scopes":["calls:read"],"parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Apelul cerut.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Call"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/calls/{call_id}/notes":{"post":{"tags":["Notes"],"summary":"Adaugă o notă pe apel","description":"Necesită scope-ul `notes:write` (acordat explicit — nu este inclus în accesul de citire, nici în accesul implicit al cheilor vechi fără scope-uri). Creează/înlocuiește nota curentă a apelului — același model folosit de aplicația web (Call.metadata.note), o singură versiune curentă, fără istoric de editări. Nu suportă editare sau ștergere prin API. Necesită header-ul `Idempotency-Key` (vezi secțiunea dedicată mai jos).","operationId":"createCallNote","security":[{"basicAuth":[]}],"x-required-scopes":["notes:write"],"parameters":[{"$ref":"#/components/parameters/CallId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCallNoteRequest"},"example":{"content":"Client a cerut retur, comandă #10234."}}}},"responses":{"201":{"description":"Nota a fost salvată.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallNoteResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/calls/{id}/recording-link":{"get":{"tags":["Calls"],"summary":"Link temporar către înregistrare","description":"Necesită scope-ul `recordings:read` (acordat explicit — nu este inclus în citirea generală `calls:read`, pentru că expune audio, nu doar metadate). Generează un link semnat, expirabil în 1 oră, fără autentificare — regenerează la fiecare afișare (ex: într-un player HTML5 dintr-un grid), nu ține link-ul în cache mai mult de atât. Diferit de link-ul din comentariul automat pus pe comanda Magento (expiră în 30 de zile, compus o singură dată).","operationId":"getRecordingLink","security":[{"basicAuth":[]}],"x-required-scopes":["recordings:read"],"parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Link generat.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordingLinkResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/agents":{"get":{"tags":["Agents"],"summary":"Listă agenți","description":"Necesită scope-ul `agents:read`.","operationId":"listAgents","security":[{"basicAuth":[]}],"x-required-scopes":["agents:read"],"parameters":[{"$ref":"#/components/parameters/Page"},{"$ref":"#/components/parameters/PerPage"}],"responses":{"200":{"description":"Listă paginată de agenți.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/agents/{id}":{"get":{"tags":["Agents"],"summary":"Detaliu agent","description":"Necesită scope-ul `agents:read`.","operationId":"getAgent","security":[{"basicAuth":[]}],"x-required-scopes":["agents:read"],"parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Agentul cerut.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Agent"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/agents/{agent_id}/dial":{"post":{"tags":["Dialing"],"summary":"Click-to-dial (cere confirmarea agentului)","description":"Necesită scope-ul `calls:dial` (acordat explicit — separat de `notes:write`; niciunul dintre cele două scope-uri de scriere nu acordă automat pe celălalt). Acest endpoint NU inițiază un apel automat. Creează o CERERE de apel care apare ca popup în aplicația agentului (\"Apelează\" / \"Respinge\"); apelul real pornește doar dacă agentul apasă explicit \"Apelează\", folosind fluxul existent de apel ieșit din softphone-ul agentului. Cererea poate fi respinsă de agent sau poate expira fără răspuns (implicit 30 de secunde) — în ambele cazuri, niciun apel nu se inițiază. Limită de rată separată, mai strictă decât restul API-ului. Necesită header-ul `Idempotency-Key` (vezi secțiunea dedicată mai jos).","operationId":"dialAgent","security":[{"basicAuth":[]}],"x-required-scopes":["calls:dial"],"parameters":[{"$ref":"#/components/parameters/AgentId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DialAgentRequest"},"example":{"to":"+40712345678","from_number_id":"8f14e45f-4e0d-4c9a-9a4a-000000000000"}}}},"responses":{"202":{"description":"Cererea de apel a fost trimisă agentului și așteaptă confirmarea lui — NU înseamnă că apelul a început.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-RateLimit-Dial-Limit":{"$ref":"#/components/headers/DialRateLimitLimit"},"X-RateLimit-Dial-Remaining":{"$ref":"#/components/headers/DialRateLimitRemaining"},"X-RateLimit-Dial-Reset":{"$ref":"#/components/headers/DialRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DialRequestResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/DialWriteConflict"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/DialRateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/contacts/bulk-upsert":{"post":{"tags":["Contacts"],"summary":"Creează/actualizează contacte în masă","description":"Necesită scope-ul `contacts:write` (acordat explicit). Util la sincronizarea inițială a clienților dintr-un CRM/magazin extern. Maximum 100 de contacte per cerere. Spre deosebire de restul endpoint-urilor de scriere din acest API, un element invalid dintr-un batch NU respinge întreaga cerere cu `400` — apare individual în `results`, cu `status: \"skipped\"` și un motiv, ca o sincronizare de mii de clienți să nu se oprească la primul rând cu telefon lipsă/greșit. Un contact existent (același `phoneE164`) este actualizat, nu duplicat — `data` este îmbinat peste orice date existente, nu le înlocuiește (nu șterge câmpuri adăugate manual de un agent).","operationId":"bulkUpsertContacts","security":[{"basicAuth":[]}],"x-required-scopes":["contacts:write"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkUpsertContactsRequest"},"example":{"contacts":[{"phoneE164":"+40712345678","name":"Ion Popescu","email":"ion.popescu@exemplu.ro","externalId":"1042"}]}}}},"responses":{"200":{"description":"Batch procesat (chiar dacă unele elemente au fost sărite — vezi `results`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkUpsertContactsResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/queues":{"get":{"tags":["Queues"],"summary":"Listă cozi","description":"Necesită scope-ul `queues:read`.","operationId":"listQueues","security":[{"basicAuth":[]}],"x-required-scopes":["queues:read"],"parameters":[{"$ref":"#/components/parameters/Page"},{"$ref":"#/components/parameters/PerPage"}],"responses":{"200":{"description":"Listă paginată de cozi.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueueListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/queues/{id}":{"get":{"tags":["Queues"],"summary":"Detaliu coadă","description":"Necesită scope-ul `queues:read`.","operationId":"getQueue","security":[{"basicAuth":[]}],"x-required-scopes":["queues:read"],"parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Coada cerută.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Queue"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/numbers":{"get":{"tags":["Numbers"],"summary":"Listă numere de telefon","description":"Necesită scope-ul `numbers:read`.","operationId":"listNumbers","security":[{"basicAuth":[]}],"x-required-scopes":["numbers:read"],"parameters":[{"$ref":"#/components/parameters/Page"},{"$ref":"#/components/parameters/PerPage"}],"responses":{"200":{"description":"Listă paginată de numere.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}}},"webhooks":{"call.created":{"post":{"tags":["Webhooks"],"summary":"Un apel nou a fost creat","description":"Trimis imediat ce un apel nou (intrat sau ieșit) este înregistrat în cont — la începutul apelului, nu la final. Vezi ghidul Webhooks la /docs/webhooks pentru semnătură, reîncercări și exemple de verificare.","operationId":"callCreatedWebhook","security":[],"parameters":[{"$ref":"#/components/parameters/IsaharSignature"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallCreatedEvent"},"example":{"id":"evt_9f1c2a3b4d5e6f708192a3b4","type":"call.created","version":"1","created_at":"2026-01-15T10:00:00.000Z","data":{"callId":"c1a2b3c4-d5e6-4f70-8192-a3b4c5d6e7f8","direction":"INBOUND","from":"+40711111111","to":"+40788800000"}}}}},"responses":{"200":{"description":"Orice status 2xx înseamnă livrare confirmată. Alt status sau timeout (10s) contează ca eșec și declanșează o reîncercare."}}}},"call.ended":{"post":{"tags":["Webhooks"],"summary":"Un apel s-a terminat","description":"Trimis când apelul se termină, indiferent de rezultat (COMPLETED, MISSED, FAILED, VOICEMAIL). Pentru apelurile ratate se trimite și evenimentul separat `call.missed`.","operationId":"callEndedWebhook","security":[],"parameters":[{"$ref":"#/components/parameters/IsaharSignature"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallEndedEvent"},"example":{"id":"evt_1a2b3c4d5e6f708192a3b4c5","type":"call.ended","version":"1","created_at":"2026-01-15T10:03:12.000Z","data":{"callId":"c1a2b3c4-d5e6-4f70-8192-a3b4c5d6e7f8","direction":"INBOUND","from":"+40711111111","to":"+40788800000","status":"COMPLETED"}}}}},"responses":{"200":{"description":"Orice status 2xx înseamnă livrare confirmată. Alt status sau timeout (10s) contează ca eșec și declanșează o reîncercare."}}}},"call.missed":{"post":{"tags":["Webhooks"],"summary":"Un apel intrat a fost ratat","description":"Trimis, în plus față de `call.ended`, când un apel intrat se termină cu statusul MISSED (nimeni nu a răspuns).","operationId":"callMissedWebhook","security":[],"parameters":[{"$ref":"#/components/parameters/IsaharSignature"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallMissedEvent"},"example":{"id":"evt_5e6f708192a3b4c5d6e7f809","type":"call.missed","version":"1","created_at":"2026-01-15T10:03:12.000Z","data":{"callId":"c1a2b3c4-d5e6-4f70-8192-a3b4c5d6e7f8","from":"+40711111111","to":"+40788800000"}}}}},"responses":{"200":{"description":"Orice status 2xx înseamnă livrare confirmată. Alt status sau timeout (10s) contează ca eșec și declanșează o reîncercare."}}}},"recording.ready":{"post":{"tags":["Webhooks"],"summary":"Înregistrarea unui apel e disponibilă","description":"Trimis când înregistrarea apelului a fost salvată și e disponibilă. Payload-ul conține doar id-ul apelului — înregistrarea propriu-zisă se descarcă din aplicație (nu se expune un URL public de fișier prin webhook).","operationId":"recordingReadyWebhook","security":[],"parameters":[{"$ref":"#/components/parameters/IsaharSignature"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordingReadyEvent"},"example":{"id":"evt_708192a3b4c5d6e7f809f1c2","type":"recording.ready","version":"1","created_at":"2026-01-15T10:03:20.000Z","data":{"callId":"c1a2b3c4-d5e6-4f70-8192-a3b4c5d6e7f8"}}}}},"responses":{"200":{"description":"Orice status 2xx înseamnă livrare confirmată. Alt status sau timeout (10s) contează ca eșec și declanșează o reîncercare."}}}}},"components":{"securitySchemes":{"basicAuth":{"type":"http","scheme":"basic","description":"Username = keyId (ex: key_xxxxx), password = secretul cheii. Ambele sunt afișate la creare — secretul o singură dată, imposibil de recuperat după aceea (doar rotit). Schema HTTP Basic nu poate transporta scope-uri în `security` — scope-ul necesar fiecărei operații e documentat explicit în descrierea ei și în extensia `x-required-scopes`."}},"parameters":{"Id":{"name":"id","in":"path","required":true,"schema":{"type":"string"}},"CallId":{"name":"call_id","in":"path","required":true,"schema":{"type":"string"}},"AgentId":{"name":"agent_id","in":"path","required":true,"schema":{"type":"string"}},"Page":{"name":"page","in":"query","description":"Numărul paginii, de la 1.","schema":{"type":"integer","minimum":1,"default":1}},"PerPage":{"name":"per_page","in":"query","description":"Rezultate pe pagină.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":true,"description":"Obligatoriu pentru orice operațiune de scriere. O valoare aleasă de tine (recomandat: UUID v4), unică per operațiune logică. O reîncercare cu ACEEAȘI valoare, către ACELAȘI endpoint, cu ACEEAȘI cheie API, nu repetă acțiunea — returnează exact răspunsul (status + body) al primei execuții, indiferent ce conținut trimiți în corpul cererii a doua oară. O cerere concurentă cu aceeași valoare, cât timp prima e încă în curs de procesare, primește `409 idempotency_key_in_progress`. Lipsa header-ului -> `400 invalid_request`.","schema":{"type":"string","example":"8f14e45f-4e0d-4c9a-9a4a-6f9a2f0b7e21"}},"IsaharSignature":{"name":"X-Isahar-Signature","in":"header","required":true,"description":"Semnătura livrării — vezi ghidul Webhooks la /docs/webhooks pentru formula exactă și exemple de verificare (Node.js, PHP).","schema":{"type":"string","example":"t=1700000000,v1=5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d1"}}},"headers":{"RequestId":{"description":"Identificator unic al cererii, generat de server. Identic cu `request_id` din corpul răspunsurilor de eroare. Prezent pe ORICE răspuns (succes sau eroare) — util pentru corelare cu suportul.","schema":{"type":"string","example":"req_a1b2c3d4e5f6a1b2c3d4e5f6"}},"RateLimitLimit":{"description":"Limita de request-uri pe minut (valoarea mai restrictivă dintre limita pe cheie și limita pe cont).","schema":{"type":"integer","example":60}},"RateLimitRemaining":{"description":"Câte request-uri mai poți face în fereastra curentă de 60 de secunde.","schema":{"type":"integer","example":42}},"RateLimitReset":{"description":"Timestamp Unix (secunde) la care se resetează fereastra curentă.","schema":{"type":"integer","example":1700000060}},"DialRateLimitLimit":{"description":"Limita specifică pentru POST /v1/agents/{agent_id}/dial (separată și mai strictă decât limita generală).","schema":{"type":"integer","example":10}},"DialRateLimitRemaining":{"description":"Câte cereri de click-to-dial mai poți face în fereastra curentă de 60 de secunde.","schema":{"type":"integer","example":7}},"DialRateLimitReset":{"description":"Timestamp Unix (secunde) la care se resetează fereastra curentă a limitei de dial.","schema":{"type":"integer","example":1700000060}},"RetryAfter":{"description":"Câte secunde până la resetarea limitei care a respins această cerere.","schema":{"type":"integer","example":18}}},"responses":{"BadRequest":{"description":"Parametri sau corp de cerere invalide.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"invalid_request","message":"per_page trebuie să fie un număr întreg între 1 și 200.","request_id":"req_a1b2c3d4e5f6a1b2c3d4e5f6"}}}}},"Unauthorized":{"description":"Autentificare invalidă, absentă sau cheie revocată. Nu are headerele X-RateLimit-* (autentificarea a picat înainte ca limita de rată să fie verificată).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"invalid_credentials","message":"Cheia API sau secretul nu sunt valide.","request_id":"req_a1b2c3d4e5f6a1b2c3d4e5f6"}}}}},"Forbidden":{"description":"Cheia API nu are scope-ul necesar pentru acest endpoint.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"forbidden","message":"Această cheie API nu are scope-ul necesar (calls:read).","request_id":"req_a1b2c3d4e5f6a1b2c3d4e5f6"}}}}},"NotFound":{"description":"Resursa nu există în acest cont (identic pentru id inexistent sau id din alt cont — nu se poate distinge cele două cazuri).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"not_found","message":"Apelul nu există pentru acest cont.","request_id":"req_a1b2c3d4e5f6a1b2c3d4e5f6"}}}}},"RateLimited":{"description":"Limita generală de request-uri (60/minut implicit, per cheie și per cont) a fost depășită.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"rate_limit_exceeded","message":"Ai depășit limita de request-uri. Încearcă din nou mai târziu.","request_id":"req_a1b2c3d4e5f6a1b2c3d4e5f6"}}}}},"DialRateLimited":{"description":"Fie limita generală, fie limita specifică de click-to-dial (10/minut implicit) a fost depășită.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-RateLimit-Dial-Limit":{"$ref":"#/components/headers/DialRateLimitLimit"},"X-RateLimit-Dial-Remaining":{"$ref":"#/components/headers/DialRateLimitRemaining"},"X-RateLimit-Dial-Reset":{"$ref":"#/components/headers/DialRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"general_limit":{"summary":"Limita generală (60/minut)","value":{"error":{"code":"rate_limit_exceeded","message":"Ai depășit limita de request-uri. Încearcă din nou mai târziu.","request_id":"req_a1b2c3d4e5f6a1b2c3d4e5f6"}}},"dial_limit":{"summary":"Limita specifică de click-to-dial (10/minut)","value":{"error":{"code":"rate_limit_exceeded","message":"Ai depășit limita specifică pentru cereri de apel (click-to-dial).","request_id":"req_a1b2c3d4e5f6a1b2c3d4e5f6"}}}}}}},"IdempotencyConflict":{"description":"O cerere cu același Idempotency-Key este deja în curs de procesare (nu s-a terminat prima execuție).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"idempotency_key_in_progress","message":"O cerere cu acest Idempotency-Key este deja în curs de procesare.","request_id":"req_a1b2c3d4e5f6a1b2c3d4e5f6"}}}}},"DialWriteConflict":{"description":"Fie o coliziune de Idempotency-Key (cerere identică deja în curs), fie agentul nu este disponibil chiar acum, fie are deja o cerere de apel activă neexpirată.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"idempotency_in_progress":{"summary":"Idempotency-Key deja în curs de procesare","value":{"error":{"code":"idempotency_key_in_progress","message":"O cerere cu acest Idempotency-Key este deja în curs de procesare.","request_id":"req_a1b2c3d4e5f6a1b2c3d4e5f6"}}},"agent_unavailable":{"summary":"Agentul nu e disponibil (offline/dezactivat/deja într-un apel)","value":{"error":{"code":"agent_unavailable","message":"Agentul nu este disponibil în acest moment.","request_id":"req_a1b2c3d4e5f6a1b2c3d4e5f6"}}},"dial_request_pending":{"summary":"Agentul are deja o cerere de apel activă, nerezolvată","value":{"error":{"code":"dial_request_pending","message":"Există deja o cerere de apel activă pentru acest agent.","request_id":"req_a1b2c3d4e5f6a1b2c3d4e5f6"}}}}}}},"UnprocessableEntity":{"description":"Numărul sursă (`from_number_id`) nu poate fi folosit pentru apeluri efectuate (nu are capacitatea `voice`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"number_not_enabled_for_outbound","message":"Acest număr nu poate fi folosit pentru apeluri efectuate.","request_id":"req_a1b2c3d4e5f6a1b2c3d4e5f6"}}}}},"InternalError":{"description":"Eroare internă neprevăzută. Nu conține detalii tehnice interne — folosește `request_id` dacă ai nevoie de ajutorul suportului.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"internal_error","message":"A apărut o eroare internă. Contactează suportul cu request_id-ul din acest răspuns.","request_id":"req_a1b2c3d4e5f6a1b2c3d4e5f6"}}}}}},"schemas":{"PingResponse":{"type":"object","required":["ok","api_version"],"properties":{"ok":{"type":"boolean"},"api_version":{"type":"string"},"tenant_id":{"type":"string"}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","request_id"],"properties":{"code":{"type":"string","description":"Codul stabil de eroare, pentru logică programatică. Nu-ți construi integrarea pe baza textului din `message` (poate fi tradus/reformulat), doar pe `code`.","enum":["invalid_request","invalid_credentials","forbidden","not_found","rate_limit_exceeded","internal_error","idempotency_key_in_progress","agent_unavailable","dial_request_pending","number_not_enabled_for_outbound"]},"message":{"type":"string","description":"Mesaj în limba română, pentru citire umană (log-uri, UI de integrare) — nu pentru logică programatică."},"request_id":{"type":"string"}}}}},"PaginationMeta":{"type":"object","required":["page","per_page","total","next_page"],"properties":{"page":{"type":"integer"},"per_page":{"type":"integer"},"total":{"type":"integer"},"next_page":{"type":["integer","null"]}}},"CallStatus":{"type":"string","enum":["RINGING","IN_IVR","QUEUED","CONNECTED","COMPLETED","MISSED","FAILED","VOICEMAIL"]},"CallRef":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}},"Call":{"type":"object","properties":{"id":{"type":"string"},"direction":{"type":"string","enum":["INBOUND","OUTBOUND"]},"status":{"$ref":"#/components/schemas/CallStatus"},"external_number":{"type":"string","description":"Numărul celeilalte părți (clientul), în E.164."},"internal_number":{"type":"string","description":"Numărul propriu al contului implicat în acest apel, în E.164."},"agent":{"oneOf":[{"$ref":"#/components/schemas/CallRef"},{"type":"null"}]},"queue":{"oneOf":[{"$ref":"#/components/schemas/CallRef"},{"type":"null"}]},"started_at":{"type":"string","format":"date-time"},"agent_answered_at":{"type":["string","null"],"format":"date-time","description":"Momentul real al conectării agentului (bridge) — distinct de un simplu răspuns IVR."},"ended_at":{"type":["string","null"],"format":"date-time"},"duration_seconds":{"type":["integer","null"]},"wait_seconds":{"type":["integer","null"],"description":"Timpul petrecut în coadă până la conectarea agentului."},"missed_reason":{"type":["string","null"],"enum":[null,"blacklisted","billing_blocked"]},"has_recording":{"type":"boolean"}}},"CallListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Call"}},"meta":{"$ref":"#/components/schemas/PaginationMeta"}}},"Agent":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"},"extension":{"type":"string"},"role":{"type":"string","enum":["ADMIN","AGENT"]},"active":{"type":"boolean"},"presence":{"type":"string","enum":["DISPONIBIL","CONFORM_PROGRAMULUI","IN_AFARA_PROGRAMULUI","MOTIV","INDISPONIBIL","IN_APEL"],"description":"Starea reală curentă a agentului."},"active_call":{"oneOf":[{"type":"object","properties":{"call_id":{"type":"string"},"since":{"type":["string","null"],"format":"date-time"}}},{"type":"null"}],"description":"Populat doar cât timp presence este IN_APEL — nu este simulat."}}},"AgentListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Agent"}},"meta":{"$ref":"#/components/schemas/PaginationMeta"}}},"Queue":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"member_count":{"type":"integer"}}},"QueueListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Queue"}},"meta":{"$ref":"#/components/schemas/PaginationMeta"}}},"PhoneNumber":{"type":"object","properties":{"id":{"type":"string"},"e164":{"type":"string"},"name":{"type":["string","null"]},"number_type":{"type":["string","null"]},"capabilities":{"type":"array","items":{"type":"string"}},"has_ivr_flow":{"type":"boolean"}}},"PhoneNumberListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PhoneNumber"}},"meta":{"$ref":"#/components/schemas/PaginationMeta"}}},"CreateCallNoteRequest":{"type":"object","required":["content"],"properties":{"content":{"type":"string","maxLength":2000,"description":"Textul notei. Spațiile sunt eliminate automat la început și la sfârșit; gol după această normalizare -> 400."}}},"CallNoteCreatedBy":{"type":"object","properties":{"type":{"type":"string","enum":["api_key"]},"name":{"type":"string","description":"Numele cheii API (niciodată secretul)."}}},"CallNoteResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"call_id":{"type":"string"},"content":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"created_by":{"$ref":"#/components/schemas/CallNoteCreatedBy"}}}}},"DialAgentRequest":{"type":"object","required":["to","from_number_id"],"properties":{"to":{"type":"string","description":"Număr E.164 de apelat (ex: +40712345678)."},"from_number_id":{"type":"string","description":"Id-ul unui PhoneNumber al contului, folosit ca număr sursă."}}},"DialRequestResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"agent_id":{"type":"string"},"to":{"type":"string"},"from_number":{"type":"string"},"status":{"type":"string","enum":["pending"]},"expires_at":{"type":"string","format":"date-time"}}}}},"RecordingLinkResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"url":{"type":"string","description":"Link semnat, expirabil, către înregistrare — nu necesită autentificare."},"expires_at":{"type":"string","format":"date-time","description":"Expiră la o oră de la generare. Regenerează la fiecare afișare, nu ține link-ul în cache."}}}}},"BulkUpsertContactItem":{"type":"object","required":["phoneE164"],"properties":{"phoneE164":{"type":"string","description":"Format E.164 (ex: +40712345678)."},"name":{"type":"string","maxLength":200},"email":{"type":"string","format":"email"},"externalId":{"type":"string","maxLength":100,"description":"Id-ul clientului în sistemul tău (ex: id-ul customerului Magento)."},"data":{"type":"object","description":"Câmpuri suplimentare libere, îmbinate peste orice date existente ale contactului (nu le înlocuiește)."}}},"BulkUpsertContactsRequest":{"type":"object","required":["contacts"],"properties":{"contacts":{"type":"array","maxItems":100,"minItems":1,"items":{"$ref":"#/components/schemas/BulkUpsertContactItem"}}}},"BulkUpsertContactResult":{"type":"object","properties":{"phone_e164":{"type":"string"},"status":{"type":"string","enum":["upserted","skipped"]},"reason":{"type":"string","description":"Prezent doar când status este \"skipped\"."}}},"BulkUpsertContactsResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"upserted":{"type":"integer"},"total":{"type":"integer"},"results":{"type":"array","items":{"$ref":"#/components/schemas/BulkUpsertContactResult"}}}}}},"WebhookEventEnvelope":{"type":"object","description":"Structura comună a oricărei livrări de webhook — vezi câmpul `data`, specific per tip de eveniment, în schema fiecărui eveniment.","required":["id","type","version","created_at","data"],"properties":{"id":{"type":"string","description":"Identificator unic al evenimentului (nu al livrării — același `id` pe toate URL-urile care primesc acest eveniment, dacă ai mai multe webhook-uri active).","example":"evt_9f1c2a3b4d5e6f708192a3b4"},"type":{"type":"string","description":"Tipul evenimentului."},"version":{"type":"string","description":"Versiunea formatului payload-ului. Azi mereu \"1\".","example":"1"},"created_at":{"type":"string","format":"date-time"},"data":{"type":"object"}}},"CallCreatedEventData":{"type":"object","required":["callId","direction","from","to"],"properties":{"callId":{"type":"string"},"direction":{"type":"string","enum":["INBOUND","OUTBOUND"]},"from":{"type":"string"},"to":{"type":"string"}}},"CallCreatedEvent":{"allOf":[{"$ref":"#/components/schemas/WebhookEventEnvelope"},{"type":"object","properties":{"type":{"type":"string","const":"call.created"},"data":{"$ref":"#/components/schemas/CallCreatedEventData"}}}]},"CallEndedEventData":{"type":"object","required":["callId","direction","from","to","status"],"properties":{"callId":{"type":"string"},"direction":{"type":"string","enum":["INBOUND","OUTBOUND"]},"from":{"type":"string"},"to":{"type":"string"},"status":{"$ref":"#/components/schemas/CallStatus"}}},"CallEndedEvent":{"allOf":[{"$ref":"#/components/schemas/WebhookEventEnvelope"},{"type":"object","properties":{"type":{"type":"string","const":"call.ended"},"data":{"$ref":"#/components/schemas/CallEndedEventData"}}}]},"CallMissedEventData":{"type":"object","required":["callId","from","to"],"properties":{"callId":{"type":"string"},"from":{"type":"string"},"to":{"type":"string"}}},"CallMissedEvent":{"allOf":[{"$ref":"#/components/schemas/WebhookEventEnvelope"},{"type":"object","properties":{"type":{"type":"string","const":"call.missed"},"data":{"$ref":"#/components/schemas/CallMissedEventData"}}}]},"RecordingReadyEventData":{"type":"object","required":["callId"],"properties":{"callId":{"type":"string"}}},"RecordingReadyEvent":{"allOf":[{"$ref":"#/components/schemas/WebhookEventEnvelope"},{"type":"object","properties":{"type":{"type":"string","const":"recording.ready"},"data":{"$ref":"#/components/schemas/RecordingReadyEventData"}}}]}}},"x-rate-limiting":{"description":"Fiecare cheie API și fiecare tenant au o limită proprie (implicit 60 request-uri/minut, configurabilă). Click-to-dial (POST /v1/agents/{agent_id}/dial) are, în plus, o limită separată și mai strictă (implicit 10/minut). Limitele se raportează pe headerele X-RateLimit-* (vezi components.headers); peste limită, răspunsul 429 include și Retry-After (secunde)."},"x-write-actions":"Operațiunile de scriere disponibile azi sunt intenționat limitate și necesită confirmare umană unde are sens: notă pe apel (notes:write, imediată) și click-to-dial (calls:dial, cere confirmarea explicită a agentului înainte de a suna — API-ul nu pornește niciodată un apel direct).","x-examples":{"curl":"curl -u \"key_xxxxx:secretul_tau\" https://api.voice.isahar.io/v1/calls"}}