API et MCP de votre propre PBX
Votre PBX Cenaly est ouverte à vos programmes et à vos assistants IA :
- CRM, système de gestion, votre site — REST API : journal des appels, enregistrements et transcriptions, appel en un clic, postes, renvoi, règles d'appel ;
- Claude Code, Claude Desktop, Cursor — serveur MCP distant par clé ;
- ChatGPT, claude.ai, Perplexity, Cursor et VS Code — le même serveur MCP sans clé : vous vous connectez à votre compte et autorisez l'accès (OAuth).
Tout fonctionne au nom du propriétaire du compte et uniquement avec les droits qu'il a accordés. Chaque modification est visible dans le journal des actions de la PBX avec le nom de la clé ou de l'application.
Adresses#
| Quoi | Adresse |
|---|---|
| REST API | https://api.cenaly.com/pbx-api/v1 |
| Serveur MCP (streamable HTTP) | https://api.cenaly.com/pbx-api/mcp |
| Documentation des méthodes | api.cenaly.com/pbx-api/v1/docs |
| Description OpenAPI 3.1 | api.cenaly.com/pbx-api/v1/openapi.json |
| Métadonnées OAuth | https://api.cenaly.com/.well-known/oauth-authorization-server/pbx-api |
Clé et droits#
La clé est créée uniquement par le propriétaire du compte : Téléphonie → API et MCP → Clés → Créer une clé. Un employé avec le rôle « Administrateur PBX » ne voit pas les clés — la clé agit au nom du propriétaire.
- La clé de type
cpx_…n'est affichée qu'une seule fois — immédiatement après sa création, avec les extraits prêts à l'emploi pour la connexion. Nous ne conservons que son empreinte (SHA-256), il est impossible de restaurer la clé — seulement d'en émettre une nouvelle. - La clé peut être limitée par établissements (sinon — tous les établissements, y compris les futurs) et par durée (30, 90, 365 jours ou illimitée).
- « Droits » modifie les droits et les établissements de la clé à la volée : l'intégration continue de fonctionner, les nouveaux droits s'appliquent dès la requête suivante.
- « Révoquer » — immédiat et définitif : la requête suivante avec cette clé recevra 401.
- Clés et connexions actives — jusqu'à 20 par compte.
Les droits sont choisis par ensemble prêt à l'emploi ou par cases à cocher :
| Droit | Ce qu'il donne | Ensembles |
|---|---|---|
calls:read |
journal des appels et fiche d'appel avec le parcours | CRM, assistant IA, lecture seule, tout |
recordings:read |
lien vers l'enregistrement de la conversation (10 minutes) et transcription | CRM, assistant IA, lecture seule, tout |
calls:dial |
appel en un clic : le téléphone de l'employé sonne d'abord, après la réponse la PBX compose le numéro | CRM, assistant IA, tout |
extensions:read |
postes, groupes, renvoi, « ne pas déranger » | CRM, assistant IA, lecture seule, tout |
extensions:write |
modifier le renvoi personnel et « ne pas déranger » | assistant IA, tout |
presence:read |
qui est libre, en appel, en conversation | assistant IA, lecture seule, tout |
reports:read |
métriques du centre d'appels sur une période : niveau de service, attente, appels abandonnés, AHT, FCR, CSAT, où nous perdons des appels, répartitions par files, lignes, employés | lecture seule, tout |
routes:read |
numéros, règles d'appels entrants et sortants, historique des règles | lecture seule, tout |
routes:write |
créer, modifier, supprimer et réordonner les règles d'appel, restaurer depuis l'historique | uniquement par case à cocher dédiée |
config:export |
export des paramètres de la PBX au format fichier | uniquement par case à cocher dédiée |
config:import |
import des paramètres depuis des tableaux : aperçu, puis application | uniquement par case à cocher dédiée |
webhooks:write |
créer, modifier, tester et supprimer les webhooks d'événements d'appels | uniquement par case à cocher dédiée |
daynight:write |
fermer un établissement jusqu'à la fin de la journée ou jusqu'à une date, ouvrir — équivalent à *28 depuis le téléphone |
uniquement par case à cocher dédiée |
Les droits qui modifient quelque chose ou consomment des minutes sont marqués en ambre dans la fenêtre. Les trois derniers droits ne font partie d'aucun ensemble, même de « Tout » — ils ne peuvent être accordés que consciemment, par une case à cocher dédiée.
ChatGPT, claude.ai, Perplexity, Cursor, VS Code : connexion sans clé#
Les assistants cloud ne savent pas insérer une clé dans l'en-tête — ils connectent le serveur MCP via OAuth. Aucune clé à créer.
claude.ai : Paramètres → Connecteurs → Ajouter votre connecteur → collez https://api.cenaly.com/pbx-api/mcp → Connecter.
ChatGPT : Paramètres → Applications et connecteurs → Avancé → activez le mode développeur → Créer → collez https://api.cenaly.com/pbx-api/mcp, authentification — OAuth.
Perplexity : Paramètres du compte → Connecteurs → « + Votre connecteur » → Distant → collez https://api.cenaly.com/pbx-api/mcp, authentification — OAuth.
Cursor et VS Code : ajoutez un serveur MCP distant (HTTP) avec l'adresse https://api.cenaly.com/pbx-api/mcp sans en-tête avec clé — la fenêtre de connexion s'ouvrira dans le navigateur. Dans Cursor : .cursor/mcp.json → {"mcpServers": {"cenaly-pbx": {"url": "https://api.cenaly.com/pbx-api/mcp"}}} ; dans VS Code : commande « MCP: Add Server » → HTTP.
Ensuite, l'assistant ouvrira la fenêtre de connexion Cenaly. Après la connexion, vous verrez :
- qui demande l'accès — l'application est nommée d'après l'adresse vers laquelle la réponse reviendra : « Connecteur vérifié · chatgpt.com » (idem — claude.ai, perplexity.ai, vscode.dev, Cursor), « Programme sur cet ordinateur » ou l'avertissement « Adresse inconnue ». Le nom envoyé par l'application est affiché en petit — n'importe qui peut l'écrire ;
- les droits — ce que l'application demande est coché (si elle demande « tout », l'ensemble « assistant IA » est coché) ; les droits « uniquement par case à cocher dédiée » ne sont jamais cochés d'avance ;
- les établissements — tous ou sélectionnés.
« Autoriser » vous ramène dans l'assistant, qui voit immédiatement les outils de la PBX. La connexion apparaît dans Téléphonie → API et MCP → Clés sous forme de ligne avec le libellé OAuth et l'adresse de l'application — c'est là que les droits se modifient et que l'accès se révoque. Reconnecter la même application met à jour ses droits, sans créer une seconde connexion.
Seul le propriétaire du compte peut autoriser les connexions.
Claude Code, Claude Desktop, Cursor : connexion par clé#
Claude Code — en une seule commande :
claude mcp add --transport http telephony https://api.cenaly.com/pbx-api/mcp --header "Authorization: Bearer cpx_…"
Claude Desktop, Cursor (.cursor/mcp.json) et autres clients avec un fichier de configuration :
{
"mcpServers": {
"telephony": {
"type": "http",
"url": "https://api.cenaly.com/pbx-api/mcp",
"headers": { "Authorization": "Bearer cpx_…" }
}
}
}
Les extraits prêts à coller avec votre clé s'affichent immédiatement après la création de la clé. L'assistant ne voit que les outils autorisés par la clé et peut répondre à « qui a appelé aujourd'hui sans réussir à joindre », « rappelle ce client depuis le téléphone d'Anna », « active le renvoi vers mon mobile ».
REST API#
La clé se place dans l'en-tête Authorization: Bearer <clé> (ou X-Api-Key). Si la clé ne couvre qu'un seul établissement, le paramètre location peut être omis ; si elle en couvre plusieurs, la réponse 400 location_required les énumère.
| Méthode | Chemin | Droit | Ce qu'il fait |
|---|---|---|---|
| GET | /v1/me |
— | droits, établissements et limites de la clé |
| GET | /v1/locations |
— | établissements de la clé et leurs PBX |
| GET | /v1/calls |
calls:read |
journal des appels : since, until, direction, status (answered, missed, ai), number, extension, limit jusqu'à 100, cursor |
| GET | /v1/calls/{id} |
calls:read |
appel avec son parcours : quelle règle s'est déclenchée, quel téléphone a sonné, qui a répondu |
| GET | /v1/calls/{id}/recording |
recordings:read |
lien vers l'enregistrement pendant 10 minutes |
| GET | /v1/calls/{id}/transcript |
recordings:read |
transcription et résumé de la conversation |
| POST | /v1/dial |
calls:dial |
appel en un clic : { "extension": "101", "number": "+995555123456" } |
| GET | /v1/dial/{commandId} |
calls:dial |
état d'avancement de l'appel en un clic |
| GET | /v1/extensions |
extensions:read |
postes supplémentaires, groupes, renvoi, « ne pas déranger » |
| PUT | /v1/extensions/{ext}/forwarding |
extensions:write |
renvoi personnel d'un poste supplémentaire |
| PUT | /v1/extensions/{ext}/dnd |
extensions:write |
« ne pas déranger » : { "on": true } |
| GET | /v1/presence |
presence:read |
qui est disponible en ce moment |
| GET | /v1/metrics |
reports:read |
métriques du centre d'appels sur une période : from, to (dates locales de l'établissement YYYY-MM-DD, par défaut les 7 derniers jours), compare (prev, year, prev,year ou vide), sections (metrics,loss,queues,lines,operators,directions,heatmap,daily), callbacks=true — en plus la rapidité de rappel |
| GET | /v1/routes |
routes:read |
numéros, règles d'appels entrants et sortants |
| GET | /v1/routes/history |
routes:read |
historique des modifications des règles |
| POST, PUT, DELETE | /v1/routes/inbound…, /v1/routes/outbound… |
routes:write |
règles d'appels une par une : créer, modifier les champs transmis, supprimer, réordonner, rétablir |
| GET, POST | /v1/config/export, /v1/config/import |
config:export, config:import |
paramètres au format fichier ; l'import — aperçu, puis application |
| GET | /v1/events |
calls:read |
événements d'appels de l'établissement sur 7 jours (jusqu'à 2000) : since (id d'événement ou horodatage), types, limit (jusqu'à 500 par réponse) |
| GET, POST, PATCH, DELETE | /v1/webhooks… |
webhooks:write |
webhooks : liste, créer, modifier, supprimer ; …/rotate-secret, …/test, …/deliveries |
| GET, PUT | /v1/open-closed |
routes:read, daynight:write |
si c'est fermé et jusqu'à quand ; fermer jusqu'à la fin de la journée { "closed": true }, jusqu'à une date { "closed": true, "until": "2026-10-05T09:00" } ou ouvrir |
La description complète des champs se trouve sur la page de documentation et dans l'OpenAPI.
Qui a appelé aujourd'hui sans réussir à joindre :
curl -H "Authorization: Bearer cpx_…" \
"https://api.cenaly.com/pbx-api/v1/calls?status=missed&since=$(date -u +%Y-%m-%dT00:00:00Z)"
Rappeler un client depuis le téléphone du poste supplémentaire 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"
Métriques de septembre avec comparaison à août et à septembre de l'année dernière — pour votre propre tableau de bord BI :
curl -H "Authorization: Bearer cpx_…" \
"https://api.cenaly.com/pbx-api/v1/metrics?from=2026-09-01&to=2026-09-30&compare=prev,year"
Dans la réponse, metrics contient les mêmes chiffres que sur l'écran Téléphonie → Journal des appels → Analytique : appels entrants, pris et manqués, niveau de service (seuil et objectif — issus des paramètres de la file d'attente), attente moyenne et maximale, part des abandons sans attente courte, AHT, FCR, part des transferts et du bot, CSAT. compare.prev et compare.year — les mêmes champs sur la période précédente et sur la même période un an plus tôt. La comparaison avec l'année dernière est calculée pour des périodes allant jusqu'à 400 jours.
Activer le renvoi vers un mobile si l'employé n'a pas répondu dans les 15 secondes :
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"
Outils MCP#
Les mêmes opérations que REST. Le client ne voit que les outils autorisés par la clé ou la connexion :
| Outils | Droit |
|---|---|
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 |
Le serveur fonctionne sans sessions (streamable HTTP, JSON-RPC 2.0) : initialize, tools/list, tools/call.
OAuth pour les développeurs de leurs propres connecteurs#
Le serveur d'autorisation suit la spécification d'autorisation MCP — tout client qui la prend en charge convient :
- Une requête vers MCP sans jeton reçoit 401 avec l'en-tête
WWW-Authenticate: Bearer resource_metadata="https://api.cenaly.com/.well-known/oauth-protected-resource/pbx-api/mcp". - Les métadonnées de la ressource (RFC 9728) désignent le serveur d'autorisation —
https://api.cenaly.com/pbx-api; ses métadonnées (RFC 8414) se trouvent à l'adressehttps://api.cenaly.com/.well-known/oauth-authorization-server/pbx-api(et…/pbx-api/.well-known/openid-configuration). - Enregistrement dynamique du client (RFC 7591) :
POST /pbx-api/oauth/register. L'adresse de retour — uniquementlocalhost/127.0.0.1(n'importe quel port) ou l'adresse d'un connecteur connu (claude.ai, claude.com, chatgpt.com, chat.openai.com, vscode.dev, www.perplexity.ai, enterprise.perplexity.ai ; pour Cursor — en plus exactementcursor://anysphere.cursor-mcp/oauth/callback). La méthode d'authentification du client —none(client public),client_secret_postouclient_secret_basic. - Code d'autorisation avec PKCE (uniquement
S256) :GET /pbx-api/oauth/authorize→ fenêtre de connexion et de consentement du propriétaire → retour vers votre adresse aveccode,stateetiss. Le paramètreresource— l'adresse du serveur MCP. - Jetons :
POST /pbx-api/oauth/token. Le code est à usage unique et vit 2 minutes (uncode_verifierincorrect l'annule) ; le jeton d'accès — 1 heure ; le jeton de rafraîchissement — 90 jours, à chaque rafraîchissement un nouveau est délivré, le précédent cesse de fonctionner. - Révocation :
POST /pbx-api/oauth/revoke(RFC 7009) annule la connexion entièrement — comme « Révoquer » dans la fenêtre « API et MCP ».
Le jeton d'accès est accepté partout où la clé l'est : dans MCP comme dans REST, avec les droits et les établissements que le propriétaire a autorisés.
Limites et erreurs#
- Par clé ou connexion : 120 requêtes par minute et 20 000 par jour (REST et MCP ensemble).
- Appel en un clic — séparément : 10 par minute et 300 par jour.
- Journal — jusqu'à 100 appels par requête ; dans le journal du PBX, les établissements — les 400 derniers appels sur 90 jours.
| Code | Erreur | Que faire |
|---|---|---|
| 401 | api_key_missing, api_key_invalid |
la clé est absente, révoquée ou expirée ; le jeton OAuth a expiré — rafraîchissez-le |
| 403 | scope_required |
la clé n'a pas le droit — ajoutez-le dans « Droits » |
| 400 | location_required |
la clé couvre plusieurs établissements — transmettez location |
| 404 | location_not_found |
l'établissement n'existe pas ou la clé est limitée à d'autres établissements |
| 429 | rate_limited |
limite atteinte, réessayez après Retry-After secondes |
Événements d'appels#
En temps réel — webhooks du PBX : Téléphonie → API et MCP → Webhooks ou POST /v1/webhooks avec une clé disposant du droit webhooks:write. Un POST arrive en quelques secondes à votre adresse https pour chaque événement : call.started (a commencé : sens, qui, vers où, ligne), call.ringing (chez qui ça sonne — un par téléphone), call.answered (qui a répondu), call.transferred (qui a transféré et à qui), call.ended (résultat, durée, conversation, qui a répondu). Le webhook peut être limité par établissements et par types d'événements.
{ "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": "Анна" } } }
Signature — en-tête X-Cenaly-Signature: t=<temps unix>,v1=<HMAC-SHA256 de "t.corps" avec le secret du webhook>. Recalculez, comparez en temps constant et rejetez un t de plus de 5 minutes. Le secret whsec_… n'est affiché qu'une fois ; « Nouveau secret » invalide immédiatement le précédent.
Livraison — répondez 2xx en 10 secondes. Sinon, nous réessayons avec des pauses de 10 s, 30 s, 2, 5, 10, 15 et 15 minutes (8 tentatives, environ une heure) ; la nouvelle tentative porte le même id — utilisez-le pour écarter les doublons. Le journal des livraisons (code de réponse, heure, tentative) se trouve dans la fenêtre du webhook et via GET /v1/webhooks/{id}/deliveries ; « Vérifier » envoie un webhook.ping de service. Si l'adresse n'a accepté aucune livraison pendant 3 jours consécutifs, le webhook se désactive tout seul : le propriétaire reçoit un e-mail, la fenêtre « Webhooks » affiche la mention « Désactivé automatiquement » et le bouton « Activer » (ou PATCH /v1/webhooks/{id} avec { "enabled": true }).
Sans votre propre serveur — le même flux par interrogation : GET /v1/events?since=<id du dernier événement> ou l'outil MCP get_call_events (événements de l'établissement sur les 7 derniers jours, jusqu'à 2000 ; par réponse, jusqu'à 500, more: true — continuez avec le même since).
Après le traitement de l'enregistrement — un appel avec transcription et analyse arrive par webhook de la source « Webhook » dans la section « Appels ».
« Ouvert / fermé » — PUT /v1/open-closed avec { "closed": true } ferme l'établissement jusqu'à la fin de la journée (comme *28 depuis le téléphone : les appels suivent les règles « hors horaires »), false — l'ouvre ; GET indique s'il est fermé et par qui. Avec "until" — fermé jusqu'à ce moment (pas au-delà de 30 jours), puis réouverture automatique : une heure sans décalage (2026-10-05T09:00) est lue dans le fuseau de l'établissement ; false l'annule aussi. La case « Fermer par le code *28 » doit être activée dans le routage (sinon 409).
Sécurité#
- La clé n'est affichée qu'une fois et n'est conservée chez nous que sous forme d'empreinte ; une clé compromise se révoque d'un simple bouton.
- Seul le propriétaire autorise une connexion OAuth, et l'on voit à quelle adresse l'accès est transmis ; une connexion autorisée peut être limitée par droits et par établissements et révoquée à tout moment.
- Les numéros de téléphone sont des données personnelles de vos clients : n'accordez les droits sur le journal et les enregistrements qu'aux intégrations qui en ont réellement besoin.
- Toutes les modifications via une clé ou une connexion figurent dans le journal d'actions du PBX avec le nom de la clé ou de l'application.