API и MCP на собствената ви АТС
Собствената ви АТС Cenaly е отворена за вашите програми и AI асистенти:
- 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, AI асистент, само четене, всичко |
recordings:read |
връзка към запис на разговора (10 минути) и транскрипция | CRM, AI асистент, само четене, всичко |
calls:dial |
обаждане с едно кликване: първо звъни телефонът на служителя, след отговор АТС набира номера | CRM, AI асистент, всичко |
extensions:read |
вътрешни номера, групи, пренасочване, „не безпокойте“ | CRM, AI асистент, само четене, всичко |
extensions:write |
промяна на лично пренасочване и „не безпокойте“ | AI асистент, всичко |
presence:read |
кой е свободен, звъни, разговаря | AI асистент, само четене, всичко |
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), „Програма на този компютър“ или предупреждение „Непознат адрес“. Името, което приложението е изпратило, е показано с дребен шрифт — то може да бъде написано от всеки;
- права — отбелязано е това, което иска приложението (ако иска „всичко подред“, е отбелязан наборът „AI асистент“); правата „само с отделна отметка“ никога не се отбелязват предварително;
- обекти — всички или избрани.
„Разреши“ ви връща в асистента и той веднага вижда инструментите на АТС. Връзката се появява в Телефония → 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_…" }
}
}
}
Готовите фрагменти за вмъкване с вашия ключ се показват веднага след създаването на ключа. AI асистентът вижда само инструментите, разрешени от ключа, и може да отговори на „кой е звънял днес и не се е свързал“, „обади се обратно на този гост от телефона на Анна“, „включи ми пренасочване към мобилния“.
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"
В отговора 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 връзка разрешава само собственикът и се вижда на кой адрес отива достъпът; разрешената връзка може да бъде ограничена по права и обекти и отменена по всяко време.
- Телефонните номера са лични данни на вашите клиенти: давайте права за журнала и записите само на интеграциите, на които наистина са нужни.
- Всички промени по ключ или връзка — в журнала за действия на АТС с името на ключа или приложението.