API e MCP del proprio centralino
Il proprio centralino Cenaly è aperto ai vostri programmi e assistenti IA:
- CRM, sistema di contabilità, sito proprio — REST API: registro delle chiamate, registrazioni e trascrizioni, chiamata con un clic, interni, inoltro, regole di chiamata;
- Claude Code, Claude Desktop, Cursor — server MCP remoto tramite chiave;
- ChatGPT, claude.ai, Perplexity, Cursor e VS Code — lo stesso server MCP senza chiave: si accede al proprio account e si autorizza l'accesso (OAuth).
Tutto funziona a nome del proprietario dell'account e solo con i diritti che egli ha concesso. Ogni modifica è visibile nel registro delle azioni del centralino con il nome della chiave o dell'app.
Indirizzi#
| Cosa | Indirizzo |
|---|---|
| REST API | https://api.cenaly.com/pbx-api/v1 |
| Server MCP (streamable HTTP) | https://api.cenaly.com/pbx-api/mcp |
| Documentazione sui metodi | api.cenaly.com/pbx-api/v1/docs |
| Descrizione OpenAPI 3.1 | api.cenaly.com/pbx-api/v1/openapi.json |
| Metadati OAuth | https://api.cenaly.com/.well-known/oauth-authorization-server/pbx-api |
Chiave e diritti#
La chiave è creata solo dal proprietario dell'account: Telefonia → API e MCP → Chiavi → Crea chiave. Un collaboratore con il ruolo «Amministratore del centralino» non vede le chiavi — la chiave agisce a nome del proprietario.
- La chiave nel formato
cpx_…è mostrata una sola volta — subito dopo la creazione, insieme agli snippet pronti per la connessione. Da noi è conservata solo la sua impronta (SHA-256), non è possibile recuperare la chiave — solo emetterne una nuova. - La chiave può essere limitata per sedi (altrimenti — tutte le sedi, comprese quelle future) e per scadenza (30, 90, 365 giorni o senza scadenza).
- «Diritti» modificano i diritti e le sedi della chiave al volo: l'integrazione continua a funzionare, i nuovi diritti hanno effetto dalla richiesta successiva.
- «Revoca» — immediata e definitiva: la richiesta successiva con questa chiave riceverà 401.
- Chiavi e connessioni attive — fino a 20 per account.
I diritti si scelgono con un set pronto o con le caselle:
| Diritto | Cosa consente | Set |
|---|---|---|
calls:read |
registro delle chiamate e scheda della chiamata con il percorso | CRM, Assistente IA, sola lettura, tutto |
recordings:read |
link alla registrazione della conversazione (10 minuti) e trascrizione | CRM, Assistente IA, sola lettura, tutto |
calls:dial |
chiamata con un clic: prima squilla il telefono del collaboratore, dopo la risposta il centralino compone il numero | CRM, Assistente IA, tutto |
extensions:read |
interni, gruppi, inoltro, «non disturbare» | CRM, Assistente IA, sola lettura, tutto |
extensions:write |
modificare l'inoltro personale e «non disturbare» | Assistente IA, tutto |
presence:read |
chi è libero, sta chiamando, sta parlando | Assistente IA, sola lettura, tutto |
reports:read |
metriche del call center per periodo: livello di servizio, attesa, abbandonate, AHT, FCR, CSAT, dove perdiamo le chiamate, analisi per code, linee, collaboratori | sola lettura, tutto |
routes:read |
numeri, regole in entrata e in uscita, storico delle regole | sola lettura, tutto |
routes:write |
creare, modificare, eliminare e riordinare le regole di chiamata, ripristinare dallo storico | solo con casella separata |
config:export |
esportazione delle impostazioni del centralino in formato file | solo con casella separata |
config:import |
caricamento delle impostazioni da tabelle: anteprima, poi applicazione | solo con casella separata |
webhooks:write |
creare, modificare, verificare ed eliminare webhook degli eventi di chiamata | solo con casella separata |
daynight:write |
chiudere una sede fino a fine giornata o fino a una data, riaprire — come *28 dal telefono |
solo con casella separata |
I diritti che modificano qualcosa o consumano minuti sono contrassegnati in ambra nella finestra. Gli ultimi tre diritti non fanno parte di nessun set, nemmeno di «tutto» — possono essere concessi solo consapevolmente, con una casella separata.
ChatGPT, claude.ai, Perplexity, Cursor, VS Code: connessione senza chiave#
Gli assistenti cloud non sanno inserire una chiave nell'intestazione — collegano il server MCP tramite OAuth. Non è necessario creare una chiave.
claude.ai: Impostazioni → Connettori → Aggiungi il proprio connettore → incollate https://api.cenaly.com/pbx-api/mcp → Connetti.
ChatGPT: Impostazioni → App e connettori → Avanzate → attivate la modalità sviluppatore → Crea → incollate https://api.cenaly.com/pbx-api/mcp, autenticazione — OAuth.
Perplexity: Impostazioni account → Connettori → «+ Proprio connettore» → Remoto → incollate https://api.cenaly.com/pbx-api/mcp, autenticazione — OAuth.
Cursor e VS Code: aggiungete un server MCP remoto (HTTP) con l'indirizzo https://api.cenaly.com/pbx-api/mcp senza intestazione con la chiave — la finestra di accesso si aprirà nel browser. In Cursor: .cursor/mcp.json → {"mcpServers": {"cenaly-pbx": {"url": "https://api.cenaly.com/pbx-api/mcp"}}}; in VS Code: comando «MCP: Add Server» → HTTP.
Poi l'assistente aprirà la finestra di accesso a Cenaly. Dopo l'accesso vedrete:
- chi richiede l'accesso — l'app è denominata in base all'indirizzo a cui tornerà la risposta: «Connettore verificato · chatgpt.com» (così anche — claude.ai, perplexity.ai, vscode.dev, Cursor), «Programma su questo computer» o l'avviso «Indirizzo sconosciuto». Il nome inviato dall'app è mostrato in piccolo — può essere scritto da chiunque;
- diritti — è contrassegnato ciò che richiede l'app (se chiede «tutto di seguito», è contrassegnato il set «Assistente IA»); i diritti «solo con casella separata» non vengono mai preselezionati;
- sedi — tutte o quelle scelte.
«Consenti» vi riporta all'assistente, che vede subito gli strumenti del centralino. La connessione compare in Telefonia → API e MCP → Chiavi come riga con l'etichetta OAuth e l'indirizzo dell'app — lì si modificano i diritti e si revoca l'accesso. Una nuova connessione della stessa app aggiorna i suoi diritti, non crea una seconda connessione.
Solo il proprietario dell'account può autorizzare le connessioni.
Claude Code, Claude Desktop, Cursor: connessione tramite chiave#
Claude Code — con un solo comando:
claude mcp add --transport http telephony https://api.cenaly.com/pbx-api/mcp --header "Authorization: Bearer cpx_…"
Claude Desktop, Cursor (.cursor/mcp.json) e altri client con file di configurazione:
{
"mcpServers": {
"telephony": {
"type": "http",
"url": "https://api.cenaly.com/pbx-api/mcp",
"headers": { "Authorization": "Bearer cpx_…" }
}
}
}
Gli snippet pronti con la Sua chiave vengono mostrati subito dopo la creazione della chiave. L'Assistente IA vede solo gli strumenti consentiti dalla chiave e può rispondere a «chi ha chiamato oggi e non ha ricevuto risposta», «richiama questo ospite dal telefono di Anna», «attivami la deviazione sul cellulare».
REST API#
La chiave va nell'intestazione Authorization: Bearer <ключ> (oppure X-Api-Key). Se la chiave ha una sola Sede, il parametro location può essere omesso; se ne ha più di una, la risposta 400 location_required le elencherà.
| Metodo | Percorso | Permesso | Cosa fa |
|---|---|---|---|
| GET | /v1/me |
— | permessi, Sedi e limiti della chiave |
| GET | /v1/locations |
— | Sedi della chiave e i loro centralini |
| GET | /v1/calls |
calls:read |
registro delle chiamate: since, until, direction, status (answered, missed, ai), number, extension, limit fino a 100, cursor |
| GET | /v1/calls/{id} |
calls:read |
chiamata con il percorso: quale regola è scattata, quale telefono ha squillato, chi ha risposto |
| GET | /v1/calls/{id}/recording |
recordings:read |
link alla Registrazione valido 10 minuti |
| GET | /v1/calls/{id}/transcript |
recordings:read |
trascrizione e riepilogo della conversazione |
| POST | /v1/dial |
calls:dial |
chiamata con un clic: { "extension": "101", "number": "+995555123456" } |
| GET | /v1/dial/{commandId} |
calls:dial |
come procede la chiamata con un clic |
| GET | /v1/extensions |
extensions:read |
interni, gruppi, deviazione, «non disturbare» |
| PUT | /v1/extensions/{ext}/forwarding |
extensions:write |
deviazione personale dell'interno |
| PUT | /v1/extensions/{ext}/dnd |
extensions:write |
«non disturbare»: { "on": true } |
| GET | /v1/presence |
presence:read |
chi è libero in questo momento |
| GET | /v1/metrics |
reports:read |
metriche del call center per periodo: from, to (date locali della Sede YYYY-MM-DD, predefinito — ultimi 7 giorni), compare (prev, year, prev,year o vuoto), sections (metrics,loss,queues,lines,operators,directions,heatmap,daily), callbacks=true — anche la velocità di richiamata |
| GET | /v1/routes |
routes:read |
numeri, regole in entrata e in uscita |
| GET | /v1/routes/history |
routes:read |
cronologia delle modifiche alle regole |
| POST, PUT, DELETE | /v1/routes/inbound…, /v1/routes/outbound… |
routes:write |
regole delle chiamate una alla volta: creare, modificare i campi trasmessi, eliminare, riordinare, ripristinare |
| GET, POST | /v1/config/export, /v1/config/import |
config:export, config:import |
impostazioni in formato file; il caricamento — anteprima, poi applicazione |
| GET | /v1/events |
calls:read |
eventi delle chiamate della Sede degli ultimi 7 giorni (fino a 2000): since (id dell'evento o orario), types, limit (fino a 500 per risposta) |
| GET, POST, PATCH, DELETE | /v1/webhooks… |
webhooks:write |
webhook: elenco, creare, modificare, eliminare; …/rotate-secret, …/test, …/deliveries |
| GET, PUT | /v1/open-closed |
routes:read, daynight:write |
se è chiuso e fino a quando; chiudere fino a fine giornata { "closed": true }, fino a una data { "closed": true, "until": "2026-10-05T09:00" } oppure aprire |
La descrizione completa dei campi è nella pagina della documentazione e nell'OpenAPI.
Chi ha chiamato oggi e non ha ricevuto risposta:
curl -H "Authorization: Bearer cpx_…" \
"https://api.cenaly.com/pbx-api/v1/calls?status=missed&since=$(date -u +%Y-%m-%dT00:00:00Z)"
Richiamare un cliente dal telefono dell'interno 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"
Metriche di settembre con confronto con agosto e con settembre dell'anno scorso — per la propria dashboard 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"
Nella risposta metrics — gli stessi numeri che si vedono nella schermata Telefonia → Registro chiamate → Analisi: in entrata, accettate e perse, livello di servizio (soglia e obiettivo — dalle impostazioni della coda), attesa media e massima, quota di abbandonate senza attesa breve, AHT, FCR, quota di trasferimenti e del bot, CSAT. compare.prev e compare.year — gli stessi campi per il periodo precedente e per lo stesso periodo di un anno fa. Il confronto con l'anno scorso viene calcolato per periodi fino a 400 giorni.
Attivare la deviazione sul cellulare se il dipendente non risponde entro 15 secondi:
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"
Strumenti MCP#
Le stesse operazioni del REST. Il client vede solo gli strumenti consentiti dalla chiave o dalla connessione:
| Strumenti | Diritto |
|---|---|
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 |
Il server funziona senza sessioni (streamable HTTP, JSON-RPC 2.0): initialize, tools/list, tools/call.
OAuth per sviluppatori di connettori propri#
Il server di autorizzazione segue la specifica di autorizzazione MCP: va bene qualsiasi client che la supporti:
- Una richiesta a MCP senza token riceve 401 con l'intestazione
WWW-Authenticate: Bearer resource_metadata="https://api.cenaly.com/.well-known/oauth-protected-resource/pbx-api/mcp". - I metadati della risorsa (RFC 9728) indicano il server di autorizzazione —
https://api.cenaly.com/pbx-api; i suoi metadati (RFC 8414) sono all'indirizzohttps://api.cenaly.com/.well-known/oauth-authorization-server/pbx-api(e…/pbx-api/.well-known/openid-configuration). - Registrazione dinamica del client (RFC 7591):
POST /pbx-api/oauth/register. L'indirizzo di ritorno è sololocalhost/127.0.0.1(qualsiasi porta) oppure l'indirizzo di un connettore noto (claude.ai, claude.com, chatgpt.com, chat.openai.com, vscode.dev, www.perplexity.ai, enterprise.perplexity.ai; per Cursor anche esattamentecursor://anysphere.cursor-mcp/oauth/callback). Il metodo di accesso del client ènone(client pubblico),client_secret_postoclient_secret_basic. - Codice di autorizzazione con PKCE (solo
S256):GET /pbx-api/oauth/authorize→ finestra di accesso e consenso del proprietario → ritorno al vostro indirizzo concode,stateeiss. Il parametroresourceè l'indirizzo del server MCP. - Token:
POST /pbx-api/oauth/token. Il codice è monouso e dura 2 minuti (uncode_verifiererrato lo annulla); il token di accesso dura 1 ora; il token di aggiornamento dura 90 giorni, a ogni aggiornamento ne viene emesso uno nuovo e il precedente smette di funzionare. - Revoca:
POST /pbx-api/oauth/revoke(RFC 7009) annulla la connessione del tutto — come «Revoca» nella finestra «API e MCP».
Il token di accesso è accettato ovunque sia accettata la chiave: sia in MCP sia in REST, con i diritti e le sedi che il proprietario ha consentito.
Limiti ed errori#
- Per chiave o connessione: 120 richieste al minuto e 20 000 al giorno (REST e MCP insieme).
- Chiamata in click — a parte: 10 al minuto e 300 al giorno.
- Il registro — fino a 100 chiamate per richiesta; nel registro del centralino le sedi — le ultime 400 chiamate in 90 giorni.
| Codice | Errore | Cosa fare |
|---|---|---|
| 401 | api_key_missing, api_key_invalid |
la chiave non c'è, è stata revocata o è scaduta; il token OAuth è scaduto — aggiornatelo |
| 403 | scope_required |
la chiave non ha il diritto — aggiungetelo in «Diritti» |
| 400 | location_required |
la chiave ha più sedi — passate location |
| 404 | location_not_found |
la sede non c'è o la chiave è limitata ad altre sedi |
| 429 | rate_limited |
limite esaurito, riprovate dopo Retry-After secondi |
Eventi delle chiamate#
In tempo reale — webhook del centralino: Telefonia → API e MCP → Webhook oppure POST /v1/webhooks con una chiave con il diritto webhooks:write. Al vostro indirizzo https arriva in pochi secondi un POST per ogni evento: call.started (iniziata: direzione, chi, verso chi, linea), call.ringing (a chi sta squillando — uno per ogni telefono), call.answered (chi ha risposto), call.transferred (chi ha trasferito e a chi), call.ended (esito, durata, conversazione, chi ha risposto). Il webhook può essere limitato per sedi e tipi di evento.
{ "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": "Анна" } } }
Firma — intestazione X-Cenaly-Signature: t=<tempo unix>,v1=<HMAC-SHA256 di "t.corpo" con il segreto del webhook>. Ricalcolate, confrontate a tempo costante e rifiutate t più vecchi di 5 minuti. Il segreto whsec_… viene mostrato una sola volta; «Nuovo segreto» disattiva immediatamente il precedente.
Consegna — rispondete 2xx entro 10 secondi. Altrimenti ripetiamo con pause di 10 s, 30 s, 2, 5, 10, 15 e 15 minuti (8 tentativi, circa un'ora); la ripetizione porta lo stesso id — usatelo per scartare i duplicati. Il registro delle consegne (codice di risposta, ora, tentativo) è nella finestra del webhook e in GET /v1/webhooks/{id}/deliveries; «Verifica» invia il webhook.ping di servizio. Se l'indirizzo per 3 giorni consecutivi non ha accettato nessuna consegna, il webhook si disattiva da solo: al proprietario arriva un'email, nella finestra «Webhook» compaiono la nota «Disattivato automaticamente» e il pulsante «Attiva» (oppure PATCH /v1/webhooks/{id} con { "enabled": true }).
Senza un proprio server — la stessa sequenza tramite polling: GET /v1/events?since=<id dell'ultimo evento> oppure lo strumento MCP get_call_events (eventi della sede degli ultimi 7 giorni, fino a 2000; per risposta fino a 500, more: true — continuate a scorrere con lo stesso since).
Dopo l'elaborazione della registrazione — la chiamata con trascrizione e analisi arriva tramite webhook della fonte «Webhook» nella sezione «Chiamate».
«Aperto / chiuso» — PUT /v1/open-closed con { "closed": true } chiude la sede fino alla fine della giornata (come *28 dal telefono: le chiamate seguono le regole «fuori orario»), false la apre; GET mostra se è chiusa e da chi. Con "until" — chiusa fino a quel momento (non oltre 30 giorni), poi si riapre da sola: un orario senza offset (2026-10-05T09:00) viene letto nel fuso della sede; false annulla anche questo. Serve la casella «Chiudi con il codice *28» attiva nell'instradamento (altrimenti 409).
Sicurezza#
- La chiave viene mostrata una sola volta e da noi è conservata solo come impronta; una chiave compromessa si revoca con un pulsante.
- Il collegamento OAuth è autorizzato solo dal proprietario ed è visibile a quale indirizzo viene concesso l'accesso; un collegamento autorizzato può essere limitato per diritti e sedi e revocato in qualsiasi momento.
- I numeri di telefono sono dati personali dei vostri clienti: concedete i diritti sul registro e sulle registrazioni solo alle integrazioni che ne hanno davvero bisogno.
- Tutte le modifiche tramite chiave o collegamento sono nel registro delle azioni del centralino con il nome della chiave o dell'app.