API и MCP своей АТС
Своя АТС Cenaly открыта для ваших программ и ИИ-ассистентов:
- CRM, учётная система, свой сайт — REST API: журнал звонков, записи и расшифровки, звонок в клик, добавочные, переадресация, правила звонков;
- Claude Code, Claude Desktop, Cursor — удалённый MCP-сервер по ключу;
- ChatGPT, claude.ai, Perplexity, Cursor и VS Code — тот же MCP-сервер без ключа: вы входите в свой аккаунт и разрешаете доступ (OAuth).
Всё работает от имени владельца аккаунта и только с правами, которые он выдал. Каждое изменение видно в журнале действий АТС с названием ключа или приложения.
Адреса#
| Что | Адрес |
|---|---|
| REST API | https://api.cenaly.com/pbx-api/v1 |
| MCP-сервер (streamable HTTP) | https://api.cenaly.com/pbx-api/mcp |
| Документация по методам | api.cenaly.com/pbx-api/v1/docs |
| Описание OpenAPI 3.1 | api.cenaly.com/pbx-api/v1/openapi.json |
| Метаданные OAuth | https://api.cenaly.com/.well-known/oauth-authorization-server/pbx-api |
Ключ и права#
Ключ создаёт только владелец аккаунта: Телефония → API и MCP → Ключи → Создать ключ. Сотрудник с ролью «Администратор АТС» ключей не видит — ключ действует от имени владельца.
- Ключ вида
cpx_…показывается один раз — сразу после создания, вместе с готовыми вставками для подключения. У нас хранится только его отпечаток (SHA-256), восстановить ключ нельзя — только выпустить новый. - Ключ можно ограничить точками (иначе — все точки, включая будущие) и сроком (30, 90, 365 дней или бессрочно).
- «Права» меняют права и точки ключа на лету: интеграция продолжает работать, новые права действуют со следующего запроса.
- «Отозвать» — сразу и навсегда: следующий запрос с этим ключом получит 401.
- Живых ключей и подключений — до 20 на аккаунт.
Права выбираются готовым набором или галками:
| Право | Что даёт | Наборы |
|---|---|---|
calls:read |
журнал звонков и карточка звонка с маршрутом | CRM, ИИ-ассистент, только чтение, всё |
recordings:read |
ссылка на запись разговора (10 минут) и расшифровка | CRM, ИИ-ассистент, только чтение, всё |
calls:dial |
звонок в клик: сначала звонит телефон сотрудника, после ответа АТС набирает номер | CRM, ИИ-ассистент, всё |
extensions:read |
добавочные, группы, переадресация, «не беспокоить» | CRM, ИИ-ассистент, только чтение, всё |
extensions:write |
менять личную переадресацию и «не беспокоить» | ИИ-ассистент, всё |
presence:read |
кто свободен, звонит, разговаривает | ИИ-ассистент, только чтение, всё |
reports:read |
метрики колл-центра за период: уровень обслуживания, ожидание, брошенные, AHT, FCR, CSAT, где теряем звонки, разрезы по очередям, линиям, сотрудникам | только чтение, всё |
routes:read |
номера, правила входящих и исходящих, история правил | только чтение, всё |
routes:write |
создавать, менять, удалять и переставлять правила звонков, откатывать из истории | только отдельной галкой |
config:export |
выгрузка настроек АТС в формате файла | только отдельной галкой |
config:import |
загрузка настроек из таблиц: предпросмотр, затем применение | только отдельной галкой |
webhooks:write |
заводить, менять, проверять и удалять вебхуки событий звонков | только отдельной галкой |
daynight:write |
закрыть точку до конца суток или до даты, открыть — то же, что *28 с телефона |
только отдельной галкой |
Права, которые что-то меняют или тратят минуты, в окне помечены янтарным. Три последних права не входят ни в один набор, даже во «Всё» — их можно дать только осознанно, отдельной галкой.
ChatGPT, claude.ai, Perplexity, Cursor, VS Code: подключение без ключа#
Облачные ассистенты не умеют вставлять ключ в заголовок — они подключают MCP-сервер по OAuth. Ключ создавать не нужно.
claude.ai: Настройки → Коннекторы → Добавить свой коннектор → вставьте https://api.cenaly.com/pbx-api/mcp → Подключить.
ChatGPT: Настройки → Приложения и коннекторы → Дополнительно → включите режим разработчика → Создать → вставьте https://api.cenaly.com/pbx-api/mcp, аутентификация — OAuth.
Perplexity: Настройки аккаунта → Коннекторы → «+ Свой коннектор» → Удалённый → вставьте https://api.cenaly.com/pbx-api/mcp, аутентификация — OAuth.
Cursor и VS Code: добавьте удалённый (HTTP) MCP-сервер с адресом https://api.cenaly.com/pbx-api/mcp без заголовка с ключом — окно входа откроется в браузере. В Cursor: .cursor/mcp.json → {"mcpServers": {"cenaly-pbx": {"url": "https://api.cenaly.com/pbx-api/mcp"}}}; в VS Code: команда «MCP: Add Server» → HTTP.
Дальше ассистент откроет окно входа Cenaly. После входа вы увидите:
- кто просит доступ — приложение называется по адресу, куда вернётся ответ: «Проверенный коннектор · chatgpt.com» (так же — claude.ai, perplexity.ai, vscode.dev, Cursor), «Программа на этом компьютере» или предупреждение «Незнакомый адрес». Название, которое прислало приложение, показано мелко — его может написать кто угодно;
- права — отмечено то, что просит приложение (если оно просит «всё подряд», отмечен набор «ИИ-ассистент»); права «только отдельной галкой» заранее не отмечаются никогда;
- точки — все или выбранные.
«Разрешить» возвращает вас в ассистента, и он сразу видит инструменты АТС. Подключение появляется в Телефония → API и MCP → Ключи строкой с меткой OAuth и адресом приложения — там же меняются права и отзывается доступ. Повторное подключение того же приложения обновляет его права, а не заводит второе подключение.
Разрешать подключения может только владелец аккаунта.
Claude Code, Claude Desktop, Cursor: подключение по ключу#
Claude Code — одной командой:
claude mcp add --transport http telephony https://api.cenaly.com/pbx-api/mcp --header "Authorization: Bearer cpx_…"
Claude Desktop, Cursor (.cursor/mcp.json) и другие клиенты с файлом настроек:
{
"mcpServers": {
"telephony": {
"type": "http",
"url": "https://api.cenaly.com/pbx-api/mcp",
"headers": { "Authorization": "Bearer cpx_…" }
}
}
}
Готовые вставки с вашим ключом показываются сразу после создания ключа. Ассистент видит только инструменты, разрешённые ключом, и может ответить на «кто звонил сегодня и не дозвонился», «перезвони этому гостю с телефона Анны», «включи мне переадресацию на мобильный».
REST API#
Ключ — в заголовке Authorization: Bearer <ключ> (или X-Api-Key). Если у ключа одна точка, параметр location можно не передавать; если несколько — ответ 400 location_required перечислит их.
| Метод | Путь | Право | Что делает |
|---|---|---|---|
| GET | /v1/me |
— | права, точки и лимиты ключа |
| GET | /v1/locations |
— | точки ключа и их АТС |
| GET | /v1/calls |
calls:read |
журнал звонков: since, until, direction, status (answered, missed, ai), number, extension, limit до 100, cursor |
| GET | /v1/calls/{id} |
calls:read |
звонок с маршрутом: какое правило сработало, чей телефон звонил, кто ответил |
| GET | /v1/calls/{id}/recording |
recordings:read |
ссылка на запись на 10 минут |
| GET | /v1/calls/{id}/transcript |
recordings:read |
расшифровка и итог разговора |
| POST | /v1/dial |
calls:dial |
звонок в клик: { "extension": "101", "number": "+995555123456" } |
| GET | /v1/dial/{commandId} |
calls:dial |
как идёт звонок в клик |
| GET | /v1/extensions |
extensions:read |
добавочные, группы, переадресация, «не беспокоить» |
| PUT | /v1/extensions/{ext}/forwarding |
extensions:write |
личная переадресация добавочного |
| PUT | /v1/extensions/{ext}/dnd |
extensions:write |
«не беспокоить»: { "on": true } |
| GET | /v1/presence |
presence:read |
кто свободен прямо сейчас |
| GET | /v1/metrics |
reports:read |
метрики колл-центра за период: from, to (местные даты точки YYYY-MM-DD, умолчание — последние 7 дней), compare (prev, year, prev,year или пусто), sections (metrics,loss,queues,lines,operators,directions,heatmap,daily), callbacks=true — ещё и скорость перезвона |
| GET | /v1/routes |
routes:read |
номера, правила входящих и исходящих |
| GET | /v1/routes/history |
routes:read |
история изменений правил |
| POST, PUT, DELETE | /v1/routes/inbound…, /v1/routes/outbound… |
routes:write |
правила звонков по одному: создать, изменить переданные поля, удалить, переставить, откатить |
| GET, POST | /v1/config/export, /v1/config/import |
config:export, config:import |
настройки в формате файла; загрузка — предпросмотр, затем применение |
| GET | /v1/events |
calls:read |
события звонков точки за 7 дней (до 2000): since (id события или время), types, limit (до 500 за ответ) |
| GET, POST, PATCH, DELETE | /v1/webhooks… |
webhooks:write |
вебхуки: список, создать, изменить, удалить; …/rotate-secret, …/test, …/deliveries |
| GET, PUT | /v1/open-closed |
routes:read, daynight:write |
закрыто ли и до когда; закрыть до конца суток { "closed": true }, до даты { "closed": true, "until": "2026-10-05T09:00" } или открыть |
Полное описание полей — на странице документации и в OpenAPI.
Кто звонил сегодня и не дозвонился:
curl -H "Authorization: Bearer cpx_…" \
"https://api.cenaly.com/pbx-api/v1/calls?status=missed&since=$(date -u +%Y-%m-%dT00:00:00Z)"
Перезвонить клиенту с телефона добавочного 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"
Метрики за сентябрь с сравнением с августом и сентябрём прошлого года — для своей 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"
В ответе report.metrics — те же цифры, что на экране Телефония → Журнал звонков → Аналитика: входящие, принятые и пропущенные, уровень обслуживания (порог и цель — из настроек очереди), среднее и максимальное ожидание, доля брошенных без коротких, AHT, FCR, доля переводов и бота, CSAT. compare.prev и compare.year — те же поля за прошлый период и за тот же период год назад. Сравнение с прошлым годом считается для периодов до 400 дней.
Включить переадресацию на мобильный, если сотрудник не ответил за 15 секунд:
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#
Те же операции, что у REST. Клиент видит только инструменты, разрешённые ключом или подключением:
| Инструменты | Право |
|---|---|
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 |
Сервер работает без сессий (streamable HTTP, JSON-RPC 2.0): initialize, tools/list, tools/call.
OAuth для разработчиков своих коннекторов#
Сервер авторизации следует спецификации авторизации MCP — подойдёт любой клиент, который её поддерживает:
- Запрос к MCP без токена получает 401 с заголовком
WWW-Authenticate: Bearer resource_metadata="https://api.cenaly.com/.well-known/oauth-protected-resource/pbx-api/mcp". - Метаданные ресурса (RFC 9728) называют сервер авторизации —
https://api.cenaly.com/pbx-api; его метаданные (RFC 8414) — по адресуhttps://api.cenaly.com/.well-known/oauth-authorization-server/pbx-api(и…/pbx-api/.well-known/openid-configuration). - Динамическая регистрация клиента (RFC 7591):
POST /pbx-api/oauth/register. Адрес возврата — толькоlocalhost/127.0.0.1(любой порт) или адрес известного коннектора (claude.ai, claude.com, chatgpt.com, chat.openai.com, vscode.dev, www.perplexity.ai, enterprise.perplexity.ai; у Cursor — ещё ровноcursor://anysphere.cursor-mcp/oauth/callback). Способ входа клиента —none(публичный клиент),client_secret_postилиclient_secret_basic. - Код авторизации с PKCE (только
S256):GET /pbx-api/oauth/authorize→ окно входа и согласия владельца → возврат на ваш адрес сcode,stateиiss. Параметрresource— адрес MCP-сервера. - Токены:
POST /pbx-api/oauth/token. Код одноразовый и живёт 2 минуты (неверныйcode_verifierего гасит); токен доступа — 1 час; токен обновления — 90 дней, при каждом обновлении выдаётся новый, прежний перестаёт работать. - Отзыв:
POST /pbx-api/oauth/revoke(RFC 7009) гасит подключение целиком — как «Отозвать» в окне «API и MCP».
Токен доступа принимается везде, где ключ: и в MCP, и в REST, с правами и точками, которые разрешил владелец.
Лимиты и ошибки#
- На ключ или подключение: 120 запросов в минуту и 20 000 в сутки (REST и MCP вместе).
- Звонок в клик — отдельно: 10 в минуту и 300 в сутки.
- Журнал — до 100 звонков за запрос; в журнале АТС точки — последние 400 звонков за 90 дней.
| Код | Ошибка | Что делать |
|---|---|---|
| 401 | api_key_missing, api_key_invalid |
ключа нет, он отозван или истёк; токен OAuth истёк — обновите его |
| 403 | scope_required |
у ключа нет права — добавьте его в «Права» |
| 400 | location_required |
у ключа несколько точек — передайте location |
| 404 | location_not_found |
точки нет или ключ ограничен другими точками |
| 429 | rate_limited |
лимит исчерпан, повторите через Retry-After секунд |
События звонков#
В реальном времени — вебхуки АТС: Телефония → API и MCP → Вебхуки или POST /v1/webhooks ключом с правом webhooks:write. На ваш https-адрес за секунды приходит POST на каждое событие: call.started (начался: направление, кто, куда, линия), call.ringing (у кого звонит — по одному на каждый телефон), call.answered (кто ответил), call.transferred (кто и кому перевёл), call.ended (итог, длительность, разговор, кто ответил). Вебхук можно ограничить точками и типами событий.
{ "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": "Анна" } } }
Подпись — заголовок X-Cenaly-Signature: t=<время unix>,v1=<HMAC-SHA256 от "t.тело" секретом вебхука>. Пересчитайте, сравните за постоянное время и отвергните t старше 5 минут. Секрет whsec_… показывается один раз; «Новый секрет» гасит прежний сразу.
Доставка — ответьте 2xx за 10 секунд. Иначе повторяем с паузами 10 с, 30 с, 2, 5, 10, 15 и 15 минут (8 попыток, около часа); повтор несёт тот же id — по нему отбрасывайте дубли. Журнал доставок (код ответа, время, попытка) — в окне вебхука и GET /v1/webhooks/{id}/deliveries; «Проверить» шлёт служебное webhook.ping. Адрес 3 суток подряд не принял ни одной доставки — вебхук выключается сам: владельцу приходит письмо, в окне «Вебхуки» — пометка «Выключен автоматически» и кнопка «Включить» (или PATCH /v1/webhooks/{id} с { "enabled": true }).
Без своего сервера — та же лента опросом: GET /v1/events?since=<id последнего события> или инструмент MCP get_call_events (события точки за последние 7 дней, до 2000; за ответ — до 500, more: true — листайте дальше тем же since).
После обработки записи — звонок с расшифровкой и разбором приходит вебхуком источника «Вебхук» в разделе «Звонки».
«Открыто / закрыто» — PUT /v1/open-closed с { "closed": true } закрывает точку до конца суток (как *28 с телефона: звонки идут правилами «вне часов»), false — открывает; GET показывает, закрыто ли и кем. С "until" — закрыто до этого момента (не дальше 30 дней), потом откроется само: время без смещения (2026-10-05T09:00) читается в поясе точки; false снимает и его. Нужна включённая галка «Закрыть кодом *28» в маршрутизации (иначе 409).
Безопасность#
- Ключ показывается один раз и хранится у нас только отпечатком; утёкший ключ отзывается одной кнопкой.
- OAuth-подключение разрешает только владелец, и видно, какому адресу уходит доступ; разрешённое подключение можно ограничить правами и точками и отозвать в любой момент.
- Номера телефонов — персональные данные ваших клиентов: давайте права на журнал и записи только тем интеграциям, которым они действительно нужны.
- Все изменения по ключу или подключению — в журнале действий АТС с названием ключа или приложения.