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-поврзување дозволува само сопственикот, и се гледа на која адреса оди пристапот; дозволеното поврзување може да се ограничи по права и локации и да се отповика во секој момент.
- Телефонските броеви се лични податоци на вашите клиенти: давајте права за дневникот и снимките само на интеграциите на кои навистина им се потребни.
- Сите промени преку клуч или поврзување — во дневникот на дејства на АТС со името на клучот или апликацијата.