C cenaly.com
Empezar gratis

API y MCP de su propia centralita telefónica

Claves y permisos, REST API, servidor MCP para Claude y Cursor, conexión de ChatGPT y claude.ai mediante OAuth sin clave, ejemplos, límites y errores

On this page 10

API y MCP de su propia centralita

Su propia centralita Cenaly está abierta a sus programas y asistentes de IA:

  • CRM, sistema de contabilidad, su propio sitio web — REST API: registro de llamadas, grabaciones y transcripciones, llamada con un clic, extensiones, desvío de llamadas, reglas de llamadas;
  • Claude Code, Claude Desktop, Cursor — servidor MCP remoto mediante clave;
  • ChatGPT, claude.ai, Perplexity, Cursor y VS Code — el mismo servidor MCP sin clave: usted inicia sesión en su cuenta y autoriza el acceso (OAuth).

Todo funciona en nombre del propietario de la cuenta y solo con los permisos que este haya otorgado. Cada cambio queda visible en el registro de acciones de la centralita con el nombre de la clave o de la aplicación.

Direcciones#

Qué Dirección
REST API https://api.cenaly.com/pbx-api/v1
Servidor MCP (streamable HTTP) https://api.cenaly.com/pbx-api/mcp
Documentación de métodos api.cenaly.com/pbx-api/v1/docs
Descripción OpenAPI 3.1 api.cenaly.com/pbx-api/v1/openapi.json
Metadatos OAuth https://api.cenaly.com/.well-known/oauth-authorization-server/pbx-api

Clave y permisos#

La clave la crea solo el propietario de la cuenta: Telefonía → API y MCP → Claves → Crear clave. Un empleado con el rol «Administrador de la centralita» no ve las claves — la clave actúa en nombre del propietario.

  • La clave con formato cpx_… se muestra una sola vez — justo después de crearla, junto con fragmentos listos para conectarse. Solo guardamos su huella (SHA-256); no es posible recuperar la clave — solo emitir una nueva.
  • La clave se puede limitar por locales (si no, todos los locales, incluidos los futuros) y por plazo (30, 90, 365 días o indefinido).
  • «Permisos» cambia los permisos y los locales de la clave al vuelo: la integración sigue funcionando y los nuevos permisos se aplican desde la siguiente solicitud.
  • «Revocar» — de inmediato y para siempre: la siguiente solicitud con esta clave recibirá 401.
  • Claves y conexiones activas — hasta 20 por cuenta.

Los permisos se eligen con un conjunto predefinido o con casillas:

Permiso Qué otorga Conjuntos
calls:read registro de llamadas y ficha de llamada con su ruta CRM, asistente de IA, solo lectura, todo
recordings:read enlace a la grabación de la conversación (10 minutos) y transcripción CRM, asistente de IA, solo lectura, todo
calls:dial llamada con un clic: primero suena el teléfono del empleado y, tras la respuesta, la centralita marca el número CRM, asistente de IA, todo
extensions:read extensiones, grupos, desvío de llamadas, «no molestar» CRM, asistente de IA, solo lectura, todo
extensions:write cambiar el desvío personal y «no molestar» asistente de IA, todo
presence:read quién está libre, llamando o hablando asistente de IA, solo lectura, todo
reports:read métricas del centro de llamadas por periodo: nivel de servicio, espera, abandonadas, AHT, FCR, CSAT, dónde perdemos llamadas, desgloses por colas, líneas y empleados solo lectura, todo
routes:read números, reglas de entrada y salida, historial de reglas solo lectura, todo
routes:write crear, cambiar, eliminar y reordenar reglas de llamadas, revertir desde el historial solo con casilla aparte
config:export exportación de la configuración de la centralita en formato de archivo solo con casilla aparte
config:import carga de la configuración desde tablas: vista previa y luego aplicación solo con casilla aparte
webhooks:write crear, cambiar, comprobar y eliminar webhooks de eventos de llamadas solo con casilla aparte
daynight:write cerrar el local hasta el final del día o hasta una fecha; abrir — lo mismo que *28 desde el teléfono solo con casilla aparte

Los permisos que cambian algo o consumen minutos están marcados en ámbar en la ventana. Los tres últimos permisos no forman parte de ningún conjunto, ni siquiera de «Todo» — solo se pueden conceder de forma consciente, con una casilla aparte.

ChatGPT, claude.ai, Perplexity, Cursor, VS Code: conexión sin clave#

Los asistentes en la nube no saben insertar una clave en la cabecera — conectan el servidor MCP por OAuth. No hace falta crear una clave.

claude.ai: Ajustes → Conectores → Añadir su propio conector → pegue https://api.cenaly.com/pbx-api/mcp → Conectar.

ChatGPT: Ajustes → Aplicaciones y conectores → Avanzado → active el modo desarrollador → Crear → pegue https://api.cenaly.com/pbx-api/mcp, autenticación — OAuth.

Perplexity: Ajustes de la cuenta → Conectores → «+ Su propio conector» → Remoto → pegue https://api.cenaly.com/pbx-api/mcp, autenticación — OAuth.

Cursor y VS Code: añada un servidor MCP remoto (HTTP) con la dirección https://api.cenaly.com/pbx-api/mcp sin cabecera con clave — la ventana de inicio de sesión se abrirá en el navegador. En Cursor: .cursor/mcp.json → {"mcpServers": {"cenaly-pbx": {"url": "https://api.cenaly.com/pbx-api/mcp"}}}; en VS Code: el comando «MCP: Add Server» → HTTP.

Después, el asistente abrirá la ventana de inicio de sesión de Cenaly. Tras iniciar sesión verá:

  • quién solicita el acceso — la aplicación se nombra por la dirección a la que volverá la respuesta: «Conector verificado · chatgpt.com» (lo mismo — claude.ai, perplexity.ai, vscode.dev, Cursor), «Programa en este equipo» o la advertencia «Dirección desconocida». El nombre que envió la aplicación se muestra en letra pequeña — lo puede escribir cualquiera;
  • permisos — está marcado lo que solicita la aplicación (si pide «todo seguido», se marca el conjunto «Asistente de IA»); los permisos «solo con casilla aparte» nunca se marcan de antemano;
  • locales — todos o los seleccionados.

«Permitir» le devuelve al asistente y este ve de inmediato las herramientas de la centralita. La conexión aparece en Telefonía → API y MCP → Claves como una línea con la etiqueta OAuth y la dirección de la aplicación — ahí mismo se cambian los permisos y se revoca el acceso. Volver a conectar la misma aplicación actualiza sus permisos en lugar de crear una segunda conexión.

Solo el propietario de la cuenta puede autorizar conexiones.

Claude Code, Claude Desktop, Cursor: conexión por clave#

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) y otros clientes con archivo de configuración:

{
  "mcpServers": {
    "telephony": {
      "type": "http",
      "url": "https://api.cenaly.com/pbx-api/mcp",
      "headers": { "Authorization": "Bearer cpx_…" }
    }
  }
}

Los fragmentos listos para pegar con su clave se muestran justo después de crear la clave. El asistente solo ve las herramientas permitidas por la clave y puede responder a «quién llamó hoy y no logró comunicarse», «vuelve a llamar a este cliente desde el teléfono de Anna», «actívame el desvío al móvil».

REST API#

La clave va en el encabezado Authorization: Bearer <clave> (o X-Api-Key). Si la clave tiene un solo local, el parámetro location puede omitirse; si tiene varios, la respuesta 400 location_required los enumerará.

Método Ruta Permiso Qué hace
GET /v1/me — permisos, locales y límites de la clave
GET /v1/locations — locales de la clave y su centralita
GET /v1/calls calls:read registro de llamadas: since, until, direction, status (answered, missed, ai), number, extension, limit hasta 100, cursor
GET /v1/calls/{id} calls:read llamada con su ruta: qué regla se activó, qué teléfono sonó, quién respondió
GET /v1/calls/{id}/recording recordings:read enlace a la grabación durante 10 minutos
GET /v1/calls/{id}/transcript recordings:read transcripción y resumen de la conversación
POST /v1/dial calls:dial llamada en un clic: { "extension": "101", "number": "+995555123456" }
GET /v1/dial/{commandId} calls:dial cómo va la llamada en un clic
GET /v1/extensions extensions:read extensiones, grupos, desvío, «no molestar»
PUT /v1/extensions/{ext}/forwarding extensions:write desvío personal de la extensión
PUT /v1/extensions/{ext}/dnd extensions:write «no molestar»: { "on": true }
GET /v1/presence presence:read quién está libre ahora mismo
GET /v1/metrics reports:read métricas del centro de llamadas por periodo: from, to (fechas locales del local YYYY-MM-DD, por defecto los últimos 7 días), compare (prev, year, prev,year o vacío), sections (metrics,loss,queues,lines,operators,directions,heatmap,daily), callbacks=true — además la velocidad de devolución de llamada
GET /v1/routes routes:read números, reglas de entrada y de salida
GET /v1/routes/history routes:read historial de cambios de las reglas
POST, PUT, DELETE /v1/routes/inbound…, /v1/routes/outbound… routes:write reglas de llamadas de una en una: crear, modificar los campos enviados, eliminar, reordenar, revertir
GET, POST /v1/config/export, /v1/config/import config:export, config:import configuración en formato de archivo; la carga — vista previa y luego aplicación
GET /v1/events calls:read eventos de llamadas del local de los últimos 7 días (hasta 2000): since (id del evento o momento), types, limit (hasta 500 por respuesta)
GET, POST, PATCH, DELETE /v1/webhooks… webhooks:write webhooks: listar, crear, modificar, eliminar; …/rotate-secret, …/test, …/deliveries
GET, PUT /v1/open-closed routes:read, daynight:write si está cerrado y hasta cuándo; cerrar hasta el final del día { "closed": true }, hasta una fecha { "closed": true, "until": "2026-10-05T09:00" } o abrir

La descripción completa de los campos está en la página de documentación y en OpenAPI.

Quién llamó hoy y no logró comunicarse:

curl -H "Authorization: Bearer cpx_…" \
  "https://api.cenaly.com/pbx-api/v1/calls?status=missed&since=$(date -u +%Y-%m-%dT00:00:00Z)"

Devolver la llamada a un cliente desde el teléfono de la extensión 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étricas de septiembre con comparación con agosto y con septiembre del año pasado — para su propio panel de 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"

En la respuesta, metrics — las mismas cifras que en la pantalla Telefonía → Registro de llamadas → Analítica: entrantes, atendidas y perdidas, nivel de servicio (umbral y objetivo — de la configuración de la cola), espera media y máxima, proporción de abandonadas sin cortas, AHT, FCR, proporción de transferencias y del bot, CSAT. compare.prev y compare.year — los mismos campos del periodo anterior y del mismo periodo de hace un año. La comparación con el año pasado se calcula para periodos de hasta 400 días.

Activar el desvío al móvil si el empleado no responde en 15 segundos:

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"

Herramientas MCP#

Las mismas operaciones que en REST. El cliente solo ve las herramientas permitidas por la clave o la conexión:

Herramientas Permiso
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

El servidor funciona sin sesiones (streamable HTTP, JSON-RPC 2.0): initialize, tools/list, tools/call.

OAuth para desarrolladores de sus propios conectores#

El servidor de autorización sigue la especificación de autorización MCP: sirve cualquier cliente que la admita:

  1. Una solicitud a MCP sin token recibe 401 con la cabecera WWW-Authenticate: Bearer resource_metadata="https://api.cenaly.com/.well-known/oauth-protected-resource/pbx-api/mcp".
  2. Los metadatos del recurso (RFC 9728) indican el servidor de autorización — https://api.cenaly.com/pbx-api; sus metadatos (RFC 8414) están en https://api.cenaly.com/.well-known/oauth-authorization-server/pbx-api (y …/pbx-api/.well-known/openid-configuration).
  3. Registro dinámico del cliente (RFC 7591): POST /pbx-api/oauth/register. La dirección de retorno es solo localhost / 127.0.0.1 (cualquier puerto) o la dirección de un conector conocido (claude.ai, claude.com, chatgpt.com, chat.openai.com, vscode.dev, www.perplexity.ai, enterprise.perplexity.ai; en Cursor, además exactamente cursor://anysphere.cursor-mcp/oauth/callback). El método de acceso del cliente es none (cliente público), client_secret_post o client_secret_basic.
  4. Código de autorización con PKCE (solo S256): GET /pbx-api/oauth/authorize → ventana de acceso y consentimiento del propietario → retorno a su dirección con code, state e iss. El parámetro resource es la dirección del servidor MCP.
  5. Tokens: POST /pbx-api/oauth/token. El código es de un solo uso y dura 2 minutos (un code_verifier incorrecto lo anula); el token de acceso dura 1 hora; el token de actualización dura 90 días, en cada actualización se emite uno nuevo y el anterior deja de funcionar.
  6. Revocación: POST /pbx-api/oauth/revoke (RFC 7009) anula la conexión por completo, igual que «Revocar» en la ventana «API y MCP».

El token de acceso se acepta en cualquier lugar donde se acepte la clave: tanto en MCP como en REST, con los permisos y Local que haya autorizado el propietario.

Límites y errores#

  • Por clave o conexión: 120 solicitudes por minuto y 20 000 al día (REST y MCP juntos).
  • Llamada en un clic: aparte, 10 por minuto y 300 al día.
  • El registro: hasta 100 llamadas por solicitud; en el registro de la central del Local, las últimas 400 llamadas de 90 días.
Código Error Qué hacer
401 api_key_missing, api_key_invalid no hay clave, está revocada o caducada; el token OAuth ha caducado: actualícelo
403 scope_required la clave no tiene el permiso: añádalo en «Permisos»
400 location_required la clave tiene varios Local: indique location
404 location_not_found el Local no existe o la clave está limitada a otros Local
429 rate_limited límite agotado, vuelva a intentarlo dentro de Retry-After segundos

Eventos de llamadas#

En tiempo real — webhooks de la centralita: Telefonía → API y MCP → Webhooks o POST /v1/webhooks con una clave con el permiso webhooks:write. A su dirección https llega en segundos un POST por cada evento: call.started (ha comenzado: dirección, quién, a dónde, línea), call.ringing (a quién le suena — uno por cada teléfono), call.answered (quién ha respondido), call.transferred (quién y a quién ha transferido), call.ended (resultado, duración, conversación, quién respondió). El webhook se puede limitar por locales y tipos de eventos.

{ "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 — cabecera X-Cenaly-Signature: t=<tiempo unix>,v1=<HMAC-SHA256 de "t.cuerpo" con el secreto del webhook>. Recalcule, compare en tiempo constante y rechace t con más de 5 minutos. El secreto whsec_… se muestra una sola vez; «Nuevo secreto» anula el anterior de inmediato.

Entrega — responda 2xx en 10 segundos. De lo contrario, reintentamos con pausas de 10 s, 30 s, 2, 5, 10, 15 y 15 minutos (8 intentos, alrededor de una hora); el reintento lleva el mismo id — descarte duplicados por él. El registro de entregas (código de respuesta, hora, intento) está en la ventana del webhook y en GET /v1/webhooks/{id}/deliveries; «Comprobar» envía un webhook.ping de servicio. Si la dirección no ha aceptado ninguna entrega durante 3 días seguidos, el webhook se desactiva solo: al propietario le llega un correo, en la ventana «Webhooks» aparece la marca «Desactivado automáticamente» y el botón «Activar» (o PATCH /v1/webhooks/{id} con { "enabled": true }).

Sin su propio servidor — el mismo flujo por sondeo: GET /v1/events?since=<id del último evento> o la herramienta MCP get_call_events (eventos del local de los últimos 7 días, hasta 2000; por respuesta, hasta 500, more: true — siga pasando páginas con el mismo since).

Tras procesar la grabación — la llamada con transcripción y análisis llega mediante un webhook del origen «Webhook» en la sección «Llamadas».

«Abierto / cerrado» — PUT /v1/open-closed con { "closed": true } cierra el local hasta el final del día (como *28 desde el teléfono: las llamadas se rigen por las reglas de «fuera de horario»), false lo abre; GET muestra si está cerrado y por quién. Con "until" — cerrado hasta ese momento (no más de 30 días), después se abrirá solo: la hora sin desplazamiento (2026-10-05T09:00) se lee en la zona horaria del local; false también lo anula. Se requiere la casilla «Cerrar con el código *28» activada en el enrutamiento (de lo contrario, 409).

Seguridad#

  • La clave se muestra una sola vez y en nuestro sistema solo se guarda su huella; una clave filtrada se revoca con un solo botón.
  • La conexión OAuth solo la autoriza el propietario, y se ve a qué dirección se concede el acceso; la conexión autorizada se puede limitar por permisos y locales, y revocar en cualquier momento.
  • Los números de teléfono son datos personales de sus clientes: conceda permisos sobre el registro y las grabaciones solo a aquellas integraciones que realmente los necesiten.
  • Todos los cambios realizados con una clave o conexión quedan en el registro de acciones de la centralita con el nombre de la clave o de la aplicación.
Was this article helpful?