API en MCP van uw eigen telefooncentrale
Uw eigen telefooncentrale Cenaly staat open voor uw programma's en AI-assistenten:
- CRM, boekhoudsysteem, uw eigen website — REST API: oproeplog, opnamen en transcripties, klik-om-te-bellen, toestelnummers, doorschakeling, belregels;
- Claude Code, Claude Desktop, Cursor — externe MCP-server met een sleutel;
- ChatGPT, claude.ai, Perplexity, Cursor en VS Code — dezelfde MCP-server zonder sleutel: u logt in op uw account en geeft toegang (OAuth).
Alles werkt namens de eigenaar van het account en alleen met de rechten die hij heeft verleend. Elke wijziging is zichtbaar in het actielog van de telefooncentrale met de naam van de sleutel of de app.
Adressen#
| Wat | Adres |
|---|---|
| REST API | https://api.cenaly.com/pbx-api/v1 |
| MCP-server (streamable HTTP) | https://api.cenaly.com/pbx-api/mcp |
| Documentatie over de methoden | api.cenaly.com/pbx-api/v1/docs |
| OpenAPI 3.1-beschrijving | api.cenaly.com/pbx-api/v1/openapi.json |
| OAuth-metadata | https://api.cenaly.com/.well-known/oauth-authorization-server/pbx-api |
Sleutel en rechten#
Een sleutel wordt alleen door de eigenaar van het account aangemaakt: Telefonie → API en MCP → Sleutels → Sleutel aanmaken. Een medewerker met de rol „Beheerder telefooncentrale” ziet geen sleutels — de sleutel werkt namens de eigenaar.
- Een sleutel van de vorm
cpx_…wordt één keer getoond — direct na het aanmaken, samen met kant-en-klare fragmenten om te verbinden. Bij ons wordt alleen de vingerafdruk (SHA-256) bewaard; de sleutel kan niet worden hersteld — u kunt alleen een nieuwe uitgeven. - De sleutel kan worden beperkt tot vestigingen (anders alle vestigingen, inclusief toekomstige) en tot een looptijd (30, 90, 365 dagen of onbeperkt).
- „Rechten” wijzigen de rechten en vestigingen van de sleutel direct: de integratie blijft werken, de nieuwe rechten gelden vanaf het volgende verzoek.
- „Intrekken” — direct en definitief: het volgende verzoek met deze sleutel krijgt 401.
- Actieve sleutels en verbindingen — tot 20 per account.
Rechten worden gekozen met een kant-en-klare set of met afzonderlijke vinkjes:
| Recht | Wat het geeft | Sets |
|---|---|---|
calls:read |
oproeplog en oproepkaart met route | CRM, AI-assistent, alleen lezen, alles |
recordings:read |
link naar de opname van het gesprek (10 minuten) en transcriptie | CRM, AI-assistent, alleen lezen, alles |
calls:dial |
klik-om-te-bellen: eerst gaat de telefoon van de medewerker, na opname kiest de telefooncentrale het nummer | CRM, AI-assistent, alles |
extensions:read |
toestelnummers, groepen, doorschakeling, „niet storen” | CRM, AI-assistent, alleen lezen, alles |
extensions:write |
persoonlijke doorschakeling en „niet storen” wijzigen | AI-assistent, alles |
presence:read |
wie vrij is, belt, in gesprek is | AI-assistent, alleen lezen, alles |
reports:read |
metrics van het callcenter over een periode: servicelevel, wachttijd, gemiste oproepen, AHT, FCR, CSAT, waar we oproepen verliezen, uitsplitsingen naar wachtrijen, lijnen, medewerkers | alleen lezen, alles |
routes:read |
nummers, regels voor inkomende en uitgaande oproepen, geschiedenis van regels | alleen lezen, alles |
routes:write |
belregels aanmaken, wijzigen, verwijderen en herordenen, terugzetten uit de geschiedenis | alleen met een apart vinkje |
config:export |
export van de instellingen van de telefooncentrale als bestand | alleen met een apart vinkje |
config:import |
import van instellingen uit tabellen: voorbeeld, daarna toepassen | alleen met een apart vinkje |
webhooks:write |
webhooks voor oproepgebeurtenissen aanmaken, wijzigen, testen en verwijderen | alleen met een apart vinkje |
daynight:write |
een vestiging sluiten tot het einde van de dag of tot een datum, en weer openen — hetzelfde als *28 vanaf de telefoon |
alleen met een apart vinkje |
Rechten die iets wijzigen of minuten verbruiken, zijn in het venster met amber gemarkeerd. De laatste drie rechten zitten in geen enkele set, zelfs niet in „Alles” — ze kunnen alleen bewust, met een apart vinkje, worden gegeven.
ChatGPT, claude.ai, Perplexity, Cursor, VS Code: verbinden zonder sleutel#
Cloudassistenten kunnen geen sleutel in een header plaatsen — zij verbinden de MCP-server via OAuth. Een sleutel aanmaken is niet nodig.
claude.ai: Instellingen → Connectors → Eigen connector toevoegen → plak https://api.cenaly.com/pbx-api/mcp → Verbinden.
ChatGPT: Instellingen → Apps en connectors → Geavanceerd → schakel de ontwikkelaarsmodus in → Aanmaken → plak https://api.cenaly.com/pbx-api/mcp, authenticatie — OAuth.
Perplexity: Accountinstellingen → Connectors → „+ Eigen connector” → Extern → plak https://api.cenaly.com/pbx-api/mcp, authenticatie — OAuth.
Cursor en VS Code: voeg een externe (HTTP) MCP-server toe met het adres https://api.cenaly.com/pbx-api/mcp zonder header met een sleutel — het inlogvenster opent in de browser. In Cursor: .cursor/mcp.json → {"mcpServers": {"cenaly-pbx": {"url": "https://api.cenaly.com/pbx-api/mcp"}}}; in VS Code: de opdracht „MCP: Add Server” → HTTP.
Daarna opent de assistent het inlogvenster van Cenaly. Na het inloggen ziet u:
- wie toegang vraagt — de app wordt genoemd naar het adres waarnaar het antwoord teruggaat: „Geverifieerde connector · chatgpt.com” (net zo — claude.ai, perplexity.ai, vscode.dev, Cursor), „Programma op deze computer” of de waarschuwing „Onbekend adres”. De naam die de app heeft meegestuurd, staat klein weergegeven — die kan door iedereen zijn ingevuld;
- rechten — aangevinkt staat wat de app vraagt (vraagt die „alles maar dan ook alles”, dan is de set „AI-assistent” aangevinkt); rechten „alleen met een apart vinkje” worden nooit vooraf aangevinkt;
- vestigingen — alle of geselecteerde.
„Toestaan” brengt u terug naar de assistent, en die ziet direct de tools van de telefooncentrale. De verbinding verschijnt in Telefonie → API en MCP → Sleutels als een regel met het label OAuth en het adres van de app — daar wijzigt u ook de rechten en trekt u de toegang in. Opnieuw verbinden met dezelfde app werkt de rechten bij in plaats van een tweede verbinding aan te maken.
Alleen de eigenaar van het account mag verbindingen toestaan.
Claude Code, Claude Desktop, Cursor: verbinding met sleutel#
Claude Code — met één opdracht:
claude mcp add --transport http telephony https://api.cenaly.com/pbx-api/mcp --header "Authorization: Bearer cpx_…"
Claude Desktop, Cursor (.cursor/mcp.json) en andere clients met een instellingenbestand:
{
"mcpServers": {
"telephony": {
"type": "http",
"url": "https://api.cenaly.com/pbx-api/mcp",
"headers": { "Authorization": "Bearer cpx_…" }
}
}
}
Kant-en-klare fragmenten met uw sleutel worden direct na het aanmaken van de sleutel getoond. De AI-assistent ziet alleen de tools die de sleutel toestaat, en kan antwoorden op „wie belde er vandaag en kreeg geen gehoor”, „bel deze gast terug vanaf de telefoon van Anna”, „zet voor mij doorschakeling naar mobiel aan”.
REST API#
De sleutel staat in de header Authorization: Bearer <ключ> (of X-Api-Key). Als de sleutel één vestiging heeft, hoeft u de parameter location niet mee te sturen; bij meerdere somt de respons 400 location_required ze op.
| Methode | Pad | Recht | Wat het doet |
|---|---|---|---|
| GET | /v1/me |
— | rechten, vestigingen en limieten van de sleutel |
| GET | /v1/locations |
— | vestigingen van de sleutel en hun telefooncentrale |
| GET | /v1/calls |
calls:read |
oproeplog: since, until, direction, status (answered, missed, ai), number, extension, limit tot 100, cursor |
| GET | /v1/calls/{id} |
calls:read |
oproep met route: welke regel is uitgevoerd, wiens telefoon overging, wie opnam |
| GET | /v1/calls/{id}/recording |
recordings:read |
link naar de opname, 10 minuten geldig |
| GET | /v1/calls/{id}/transcript |
recordings:read |
transcriptie en samenvatting van het gesprek |
| POST | /v1/dial |
calls:dial |
klik-en-bel: { "extension": "101", "number": "+995555123456" } |
| GET | /v1/dial/{commandId} |
calls:dial |
voortgang van klik-en-bel |
| GET | /v1/extensions |
extensions:read |
toestelnummers, groepen, doorschakeling, „niet storen” |
| PUT | /v1/extensions/{ext}/forwarding |
extensions:write |
persoonlijke doorschakeling van een toestelnummer |
| PUT | /v1/extensions/{ext}/dnd |
extensions:write |
„niet storen”: { "on": true } |
| GET | /v1/presence |
presence:read |
wie is nu beschikbaar |
| GET | /v1/metrics |
reports:read |
callcentermetrics over een periode: from, to (lokale datums van de vestiging YYYY-MM-DD, standaard de laatste 7 dagen), compare (prev, year, prev,year of leeg), sections (metrics,loss,queues,lines,operators,directions,heatmap,daily), callbacks=true — ook de terugbelsnelheid |
| GET | /v1/routes |
routes:read |
nummers, regels voor inkomende en uitgaande oproepen |
| GET | /v1/routes/history |
routes:read |
wijzigingsgeschiedenis van regels |
| POST, PUT, DELETE | /v1/routes/inbound…, /v1/routes/outbound… |
routes:write |
oproepregels één voor één: aanmaken, doorgegeven velden wijzigen, verwijderen, herordenen, terugdraaien |
| GET, POST | /v1/config/export, /v1/config/import |
config:export, config:import |
instellingen als bestand; bij uploaden eerst een voorbeeld, daarna toepassen |
| GET | /v1/events |
calls:read |
oproepgebeurtenissen van de vestiging over 7 dagen (tot 2000): since (gebeurtenis-id of tijd), types, limit (tot 500 per respons) |
| GET, POST, PATCH, DELETE | /v1/webhooks… |
webhooks:write |
webhooks: opvragen, aanmaken, wijzigen, verwijderen; …/rotate-secret, …/test, …/deliveries |
| GET, PUT | /v1/open-closed |
routes:read, daynight:write |
of het gesloten is en tot wanneer; sluiten tot het einde van de dag { "closed": true }, tot een datum { "closed": true, "until": "2026-10-05T09:00" } of openen |
De volledige beschrijving van de velden staat op de pagina documentatie en in OpenAPI.
Wie belde er vandaag en kreeg geen gehoor:
curl -H "Authorization: Bearer cpx_…" \
"https://api.cenaly.com/pbx-api/v1/calls?status=missed&since=$(date -u +%Y-%m-%dT00:00:00Z)"
Een klant terugbellen vanaf de telefoon van toestelnummer 101:
curl -X POST -H "Authorization: Bearer cpx_…" -H "Content-Type: application/json" \
-d '{"extension":"101","number":"+995555123456"}' "https://api.cenaly.com/pbx-api/v1/dial"
Metrics over september met vergelijking met augustus en september van vorig jaar — voor uw eigen BI-paneel:
curl -H "Authorization: Bearer cpx_…" \
"https://api.cenaly.com/pbx-api/v1/metrics?from=2026-09-01&to=2026-09-30&compare=prev,year"
In de respons bevat metrics dezelfde cijfers als op het scherm Telefonie → Oproeplog → Analyse: inkomend, aangenomen en gemist, servicelevel (drempel en doel — uit de wachtrijinstellingen), gemiddelde en maximale wachttijd, aandeel opgehangen zonder kort gesprek, AHT, FCR, aandeel doorverbindingen en bot, CSAT. compare.prev en compare.year — dezelfde velden over de vorige periode en over dezelfde periode een jaar geleden. De vergelijking met vorig jaar wordt berekend voor periodes tot 400 dagen.
Doorschakeling naar mobiel aanzetten als een medewerker niet binnen 15 seconden opneemt:
curl -X PUT -H "Authorization: Bearer cpx_…" -H "Content-Type: application/json" \
-d '{"forward":{"noAnswer":{"kind":"number","number":"+995599000111"},"noAnswerSec":15}}' \
"https://api.cenaly.com/pbx-api/v1/extensions/101/forwarding"
MCP-gereedschappen#
Dezelfde bewerkingen als bij REST. De client ziet alleen gereedschappen die door de sleutel of verbinding zijn toegestaan:
| Gereedschappen | Recht |
|---|---|
list_locations |
— |
list_calls, get_call |
calls:read |
get_call_recording, get_call_transcript |
recordings:read |
call_number, get_dial_status |
calls:dial |
list_extensions |
extensions:read |
set_forwarding, set_do_not_disturb |
extensions:write |
get_presence |
presence:read |
get_call_metrics |
reports:read |
list_routes, list_route_history |
routes:read |
save_inbound_rule, delete_inbound_rule, move_inbound_rule, save_outbound_rule, delete_outbound_rule, move_outbound_rule, set_outbound_calls, restore_route_version |
routes:write |
export_config |
config:export |
import_config |
config:import |
get_call_events |
calls:read |
list_webhooks, save_webhook, delete_webhook, rotate_webhook_secret, test_webhook, list_webhook_deliveries |
webhooks:write |
get_open_closed |
routes:read |
set_open_closed |
daynight:write |
De server werkt zonder sessies (streamable HTTP, JSON-RPC 2.0): initialize, tools/list, tools/call.
OAuth voor ontwikkelaars van eigen connectors#
De autorisatieserver volgt de MCP-autorisatiespecificatie — elke client die deze ondersteunt, is geschikt:
- Een verzoek aan MCP zonder token krijgt 401 met de header
WWW-Authenticate: Bearer resource_metadata="https://api.cenaly.com/.well-known/oauth-protected-resource/pbx-api/mcp". - De metadata van de resource (RFC 9728) noemen de autorisatieserver —
https://api.cenaly.com/pbx-api; de metadata daarvan (RFC 8414) staan ophttps://api.cenaly.com/.well-known/oauth-authorization-server/pbx-api(en…/pbx-api/.well-known/openid-configuration). - Dynamische registratie van de client (RFC 7591):
POST /pbx-api/oauth/register. Het retouradres is alleenlocalhost/127.0.0.1(elke poort) of het adres van een bekende connector (claude.ai, claude.com, chatgpt.com, chat.openai.com, vscode.dev, www.perplexity.ai, enterprise.perplexity.ai; bij Cursor ook preciescursor://anysphere.cursor-mcp/oauth/callback). De inlogmethode van de client isnone(publieke client),client_secret_postofclient_secret_basic. - Autorisatiecode met PKCE (alleen
S256):GET /pbx-api/oauth/authorize→ inlog- en toestemmingsvenster van de eigenaar → terugkeer naar uw adres metcode,stateeniss. De parameterresourceis het adres van de MCP-server. - Tokens:
POST /pbx-api/oauth/token. De code is eenmalig en leeft 2 minuten (een onjuistecode_verifierdooft hem); het toegangstoken — 1 uur; het vernieuwingstoken — 90 dagen, bij elke vernieuwing wordt een nieuw token uitgegeven en werkt het vorige niet meer. - Intrekken:
POST /pbx-api/oauth/revoke(RFC 7009) dooft de verbinding in zijn geheel — net als „Intrekken” in het venster „API en MCP”.
Het toegangstoken wordt overal geaccepteerd waar de sleutel geldt: zowel in MCP als in REST, met de rechten en vestigingen die de eigenaar heeft toegestaan.
Limieten en fouten#
- Per sleutel of verbinding: 120 verzoeken per minuut en 20 000 per dag (REST en MCP samen).
- Bellen in één klik — apart: 10 per minuut en 300 per dag.
- Logboek — tot 100 gesprekken per verzoek; in het logboek van de ATS-vestiging de laatste 400 gesprekken van 90 dagen.
| Code | Fout | Wat te doen |
|---|---|---|
| 401 | api_key_missing, api_key_invalid |
de sleutel ontbreekt, is ingetrokken of verlopen; het OAuth-token is verlopen — vernieuw het |
| 403 | scope_required |
de sleutel mist een recht — voeg het toe bij „Rechten” |
| 400 | location_required |
de sleutel heeft meerdere vestigingen — geef location mee |
| 404 | location_not_found |
de vestiging bestaat niet of de sleutel is beperkt tot andere vestigingen |
| 429 | rate_limited |
de limiet is bereikt, probeer het opnieuw na Retry-After seconden |
Gespreksgebeurtenissen#
Realtime — webhooks van de telefooncentrale: Telefonie → API en MCP → Webhooks of POST /v1/webhooks met een sleutel met het recht webhooks:write. Op uw https-adres komt binnen enkele seconden een POST binnen voor elke gebeurtenis: call.started (begonnen: richting, wie, waarnaartoe, lijn), call.ringing (bij wie het overgaat — één per telefoon), call.answered (wie heeft opgenomen), call.transferred (wie heeft naar wie doorverbonden), call.ended (resultaat, duur, gesprek, wie heeft opgenomen). De webhook kan worden beperkt tot vestigingen en gebeurtenistypen.
{ "id": "1727771234.56:2", "type": "call.answered", "createdAt": "2026-10-01T10:00:05Z",
"locationId": "…", "webhookId": "wh_…", "attempt": 1,
"data": { "callId": "1727771234.56", "seq": 2, "direction": "inbound",
"from": { "number": "+995555123456" }, "party": { "extension": "101", "name": "Анна" } } }
Handtekening — de header X-Cenaly-Signature: t=<unixtijd>,v1=<HMAC-SHA256 van "t.body" met het webhookgeheim>. Herbereken, vergelijk in constante tijd en weiger een t ouder dan 5 minuten. Het geheim whsec_… wordt één keer getoond; „Nieuw geheim” dooft het vorige onmiddellijk.
Bezorging — antwoord met 2xx binnen 10 seconden. Anders herhalen we met pauzes van 10 s, 30 s, 2, 5, 10, 15 en 15 minuten (8 pogingen, ongeveer een uur); de herhaling draagt dezelfde id — gooi daarmee duplicaten weg. Het bezorglogboek (antwoordcode, tijd, poging) staat in het webhookvenster en via GET /v1/webhooks/{id}/deliveries; „Controleren” stuurt een servicebericht webhook.ping. Als het adres 3 etmalen op rij geen enkele bezorging heeft aangenomen, wordt de webhook automatisch uitgeschakeld: de eigenaar ontvangt een e-mail, in het venster „Webhooks” staat de markering „Automatisch uitgeschakeld” en de knop „Inschakelen” (of PATCH /v1/webhooks/{id} met { "enabled": true }).
Zonder eigen server — dezelfde feed via polling: GET /v1/events?since=<id van de laatste gebeurtenis> of de MCP-tool get_call_events (gebeurtenissen van de vestiging van de laatste 7 dagen, tot 2000; per antwoord tot 500, more: true — blader verder met dezelfde since).
Na verwerking van de opname — een gesprek met transcriptie en analyse komt binnen via een webhook van de bron „Webhook” in het gedeelte „Gesprekken”.
„Open / gesloten” — PUT /v1/open-closed met { "closed": true } sluit de vestiging tot het einde van de dag (zoals *28 vanaf de telefoon: gesprekken verlopen volgens de regels „buiten openingstijden”), false opent deze; GET toont of deze gesloten is en door wie. Met "until" — gesloten tot dat moment (niet verder dan 30 dagen), daarna gaat deze vanzelf weer open: een tijd zonder offset (2026-10-05T09:00) wordt gelezen in de tijdzone van de vestiging; false heft dat ook op. Hiervoor is het aangevinkte vakje „Sluiten met code *28” in de routering nodig (anders 409).
Beveiliging#
- De sleutel wordt één keer getoond en bij ons alleen als vingerafdruk bewaard; een gelekt sleutel wordt met één knop ingetrokken.
- Een OAuth-verbinding mag alleen de eigenaar toestaan, en er is zichtbaar naar welk adres de toegang gaat; een toegestane verbinding kan worden beperkt in rechten en vestigingen en op elk moment worden ingetrokken.
- Telefoonnummers zijn persoonsgegevens van uw klanten: geef alleen die integraties rechten op het logboek en de opnamen die ze echt nodig hebben.
- Alle wijzigingen via een sleutel of verbinding staan in het actielogboek van de telefooncentrale met de naam van de sleutel of app.