API i MCP własnej centrali
Twoja centrala Cenaly jest otwarta dla Twoich programów i asystentów AI:
- CRM, system księgowy, własna strona — REST API: dziennik połączeń, nagrania i transkrypcje, połączenie jednym kliknięciem, numery wewnętrzne, przekierowanie, reguły połączeń;
- Claude Code, Claude Desktop, Cursor — zdalny serwer MCP na podstawie klucza;
- ChatGPT, claude.ai, Perplexity, Cursor i VS Code — ten sam serwer MCP bez klucza: logujesz się na swoje konto i zezwalasz na dostęp (OAuth).
Wszystko działa w imieniu właściciela konta i tylko z uprawnieniami, które nadał. Każda zmiana jest widoczna w dzienniku działań centrali z nazwą klucza lub aplikacji.
Adresy#
| Co | Adres |
|---|---|
| REST API | https://api.cenaly.com/pbx-api/v1 |
| Serwer MCP (streamable HTTP) | https://api.cenaly.com/pbx-api/mcp |
| Dokumentacja metod | api.cenaly.com/pbx-api/v1/docs |
| Opis OpenAPI 3.1 | api.cenaly.com/pbx-api/v1/openapi.json |
| Metadane OAuth | https://api.cenaly.com/.well-known/oauth-authorization-server/pbx-api |
Klucz i uprawnienia#
Klucz tworzy tylko właściciel konta: Telefonia → API i MCP → Klucze → Utwórz klucz. Pracownik z rolą „Administrator centrali” nie widzi kluczy — klucz działa w imieniu właściciela.
- Klucz w postaci
cpx_…jest pokazywany tylko raz — zaraz po utworzeniu, wraz z gotowymi wstawkami do połączenia. Przechowujemy tylko jego odcisk (SHA-256), odzyskanie klucza nie jest możliwe — można jedynie wydać nowy. - Klucz można ograniczyć placówkami (w przeciwnym razie — wszystkie placówki, w tym przyszłe) i terminem (30, 90, 365 dni lub bezterminowo).
- „Uprawnienia” zmieniają uprawnienia i placówki klucza w locie: integracja działa dalej, nowe uprawnienia obowiązują od następnego żądania.
- „Odwołaj” — natychmiast i na zawsze: następne żądanie z tym kluczem otrzyma 401.
- Aktywnych kluczy i połączeń — do 20 na konto.
Uprawnienia wybiera się gotowym zestawem lub checkboxami:
| Uprawnienie | Co daje | Zestawy |
|---|---|---|
calls:read |
dziennik połączeń i karta połączenia z trasą | CRM, Asystent AI, tylko odczyt, wszystko |
recordings:read |
link do nagrania rozmowy (10 minut) i transkrypcja | CRM, Asystent AI, tylko odczyt, wszystko |
calls:dial |
połączenie jednym kliknięciem: najpierw dzwoni telefon pracownika, po odebraniu centrala wybiera numer | CRM, Asystent AI, wszystko |
extensions:read |
numery wewnętrzne, grupy, przekierowanie, „nie przeszkadzać” | CRM, Asystent AI, tylko odczyt, wszystko |
extensions:write |
zmiana osobistego przekierowania i „nie przeszkadzać” | Asystent AI, wszystko |
presence:read |
kto jest wolny, dzwoni, rozmawia | Asystent AI, tylko odczyt, wszystko |
reports:read |
metryki call center za okres: poziom obsługi, oczekiwanie, porzucone, AHT, FCR, CSAT, gdzie tracimy połączenia, przekroje według kolejek, linii, pracowników | tylko odczyt, wszystko |
routes:read |
numery, reguły połączeń przychodzących i wychodzących, historia reguł | tylko odczyt, wszystko |
routes:write |
tworzenie, zmiana, usuwanie i przestawianie reguł połączeń, przywracanie z historii | tylko osobnym checkboxem |
config:export |
eksport ustawień centrali w formacie pliku | tylko osobnym checkboxem |
config:import |
import ustawień z tabel: podgląd, następnie zastosowanie | tylko osobnym checkboxem |
webhooks:write |
zakładanie, zmiana, sprawdzanie i usuwanie webhooków zdarzeń połączeń | tylko osobnym checkboxem |
daynight:write |
zamknięcie placówki do końca doby lub do daty, otwarcie — to samo co *28 z telefonu |
tylko osobnym checkboxem |
Uprawnienia, które coś zmieniają lub zużywają minuty, są w oknie oznaczone bursztynowym kolorem. Trzy ostatnie uprawnienia nie wchodzą w żaden zestaw, nawet w „Wszystko” — można je nadać tylko świadomie, osobnym checkboxem.
ChatGPT, claude.ai, Perplexity, Cursor, VS Code: połączenie bez klucza#
Asystenci chmurowi nie potrafią wstawić klucza do nagłówka — podłączają serwer MCP przez OAuth. Klucza nie trzeba tworzyć.
claude.ai: Ustawienia → Konektory → Dodaj własny konektor → wklej https://api.cenaly.com/pbx-api/mcp → Połącz.
ChatGPT: Ustawienia → Aplikacje i konektory → Zaawansowane → włącz tryb deweloperski → Utwórz → wklej https://api.cenaly.com/pbx-api/mcp, uwierzytelnianie — OAuth.
Perplexity: Ustawienia konta → Konektory → „+ Własny konektor” → Zdalny → wklej https://api.cenaly.com/pbx-api/mcp, uwierzytelnianie — OAuth.
Cursor i VS Code: dodaj zdalny (HTTP) serwer MCP z adresem https://api.cenaly.com/pbx-api/mcp bez nagłówka z kluczem — okno logowania otworzy się w przeglądarce. W Cursor: .cursor/mcp.json → {"mcpServers": {"cenaly-pbx": {"url": "https://api.cenaly.com/pbx-api/mcp"}}}; w VS Code: polecenie „MCP: Add Server” → HTTP.
Następnie asystent otworzy okno logowania Cenaly. Po zalogowaniu zobaczysz:
- kto prosi o dostęp — aplikacja jest nazwana według adresu, na który wróci odpowiedź: „Sprawdzony konektor · chatgpt.com” (tak samo — claude.ai, perplexity.ai, vscode.dev, Cursor), „Program na tym komputerze” lub ostrzeżenie „Nieznany adres”. Nazwa przesłana przez aplikację jest pokazana drobnym drukiem — może ją wpisać ktokolwiek;
- uprawnienia — zaznaczone jest to, o co prosi aplikacja (jeśli prosi o „wszystko po kolei”, zaznaczony jest zestaw „Asystent AI”); uprawnienia „tylko osobnym checkboxem” nigdy nie są zaznaczane z góry;
- placówki — wszystkie lub wybrane.
„Zezwól” przenosi Cię z powrotem do asystenta, a on od razu widzi narzędzia centrali. Połączenie pojawia się w Telefonia → API i MCP → Klucze wierszem z etykietą OAuth i adresem aplikacji — tam też zmienia się uprawnienia i odwołuje dostęp. Ponowne połączenie tej samej aplikacji aktualizuje jej uprawnienia, a nie zakłada drugiego połączenia.
Zezwalać na połączenia może tylko właściciel konta.
Claude Code, Claude Desktop, Cursor: połączenie za pomocą klucza#
Claude Code — jedną komendą:
claude mcp add --transport http telephony https://api.cenaly.com/pbx-api/mcp --header "Authorization: Bearer cpx_…"
Claude Desktop, Cursor (.cursor/mcp.json) i inni klienci z plikiem ustawień:
{
"mcpServers": {
"telephony": {
"type": "http",
"url": "https://api.cenaly.com/pbx-api/mcp",
"headers": { "Authorization": "Bearer cpx_…" }
}
}
}
Gotowe wstawki z Twoim kluczem są pokazywane od razu po utworzeniu klucza. Asystent AI widzi tylko narzędzia dozwolone przez klucz i może odpowiedzieć na „kto dzwonił dziś i nie dodzwonił się”, „oddzwoń do tego gościa z telefonu Anny”, „włącz mi przekierowanie na komórkę”.
REST API#
Klucz — w nagłówku Authorization: Bearer <klucz> (lub X-Api-Key). Jeśli klucz ma jedną placówkę, parametru location można nie przekazywać; jeśli kilka — odpowiedź 400 location_required je wymieni.
| Metoda | Ścieżka | Uprawnienie | Co robi |
|---|---|---|---|
| GET | /v1/me |
— | uprawnienia, placówki i limity klucza |
| GET | /v1/locations |
— | placówki klucza i ich centrale |
| GET | /v1/calls |
calls:read |
dziennik połączeń: since, until, direction, status (answered, missed, ai), number, extension, limit do 100, cursor |
| GET | /v1/calls/{id} |
calls:read |
połączenie z trasą: która reguła zadziałała, czyj telefon dzwonił, kto odebrał |
| GET | /v1/calls/{id}/recording |
recordings:read |
link do nagrania na 10 minut |
| GET | /v1/calls/{id}/transcript |
recordings:read |
transkrypcja i podsumowanie rozmowy |
| POST | /v1/dial |
calls:dial |
połączenie jednym kliknięciem: { "extension": "101", "number": "+995555123456" } |
| GET | /v1/dial/{commandId} |
calls:dial |
jak przebiega połączenie jednym kliknięciem |
| GET | /v1/extensions |
extensions:read |
numery wewnętrzne, grupy, przekierowanie, „nie przeszkadzać” |
| PUT | /v1/extensions/{ext}/forwarding |
extensions:write |
osobiste przekierowanie numeru wewnętrznego |
| PUT | /v1/extensions/{ext}/dnd |
extensions:write |
„nie przeszkadzać”: { "on": true } |
| GET | /v1/presence |
presence:read |
kto jest teraz wolny |
| GET | /v1/metrics |
reports:read |
metryki call center za okres: from, to (lokalne daty placówki YYYY-MM-DD, domyślnie — ostatnie 7 dni), compare (prev, year, prev,year lub puste), sections (metrics,loss,queues,lines,operators,directions,heatmap,daily), callbacks=true — także szybkość oddzwaniania |
| GET | /v1/routes |
routes:read |
numery, reguły połączeń przychodzących i wychodzących |
| GET | /v1/routes/history |
routes:read |
historia zmian reguł |
| POST, PUT, DELETE | /v1/routes/inbound…, /v1/routes/outbound… |
routes:write |
reguły połączeń pojedynczo: utworzyć, zmienić przekazane pola, usunąć, przestawić, przywrócić |
| GET, POST | /v1/config/export, /v1/config/import |
config:export, config:import |
ustawienia w formacie pliku; wgranie — podgląd, następnie zastosowanie |
| GET | /v1/events |
calls:read |
zdarzenia połączeń placówki za 7 dni (do 2000): since (id zdarzenia lub czas), types, limit (do 500 na odpowiedź) |
| GET, POST, PATCH, DELETE | /v1/webhooks… |
webhooks:write |
webhooki: lista, utworzyć, zmienić, usunąć; …/rotate-secret, …/test, …/deliveries |
| GET, PUT | /v1/open-closed |
routes:read, daynight:write |
czy zamknięte i do kiedy; zamknąć do końca doby { "closed": true }, do daty { "closed": true, "until": "2026-10-05T09:00" } lub otworzyć |
Pełny opis pól — na stronie dokumentacji i w OpenAPI.
Kto dzwonił dziś i nie dodzwonił się:
curl -H "Authorization: Bearer cpx_…" \
"https://api.cenaly.com/pbx-api/v1/calls?status=missed&since=$(date -u +%Y-%m-%dT00:00:00Z)"
Oddzwonić do klienta z telefonu numeru wewnętrznego 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"
Metryki za wrzesień z porównaniem z sierpniem i wrześniem zeszłego roku — do własnego panelu 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"
W odpowiedzi metrics — te same liczby co na ekranie Telefonia → Dziennik połączeń → Analityka: przychodzące, odebrane i pominięte, poziom obsługi (próg i cel — z ustawień kolejki), średnie i maksymalne oczekiwanie, udział porzuconych bez krótkich, AHT, FCR, udział przekierowań i bota, CSAT. compare.prev i compare.year — te same pola za poprzedni okres i za ten sam okres rok temu. Porównanie z zeszłym rokiem jest liczone dla okresów do 400 dni.
Włączyć przekierowanie na komórkę, jeśli pracownik nie odpowiedział w ciągu 15 sekund:
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"
Narzędzia MCP#
Te same operacje co w REST. Klient widzi tylko narzędzia dozwolone przez klucz lub połączenie:
| Narzędzia | Uprawnienie |
|---|---|
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 |
Serwer działa bez sesji (streamable HTTP, JSON-RPC 2.0): initialize, tools/list, tools/call.
OAuth dla deweloperów własnych konektorów#
Serwer autoryzacji jest zgodny ze specyfikacją autoryzacji MCP — pasuje każdy klient, który ją obsługuje:
- Żądanie do MCP bez tokenu otrzymuje 401 z nagłówkiem
WWW-Authenticate: Bearer resource_metadata="https://api.cenaly.com/.well-known/oauth-protected-resource/pbx-api/mcp". - Metadane zasobu (RFC 9728) wskazują serwer autoryzacji —
https://api.cenaly.com/pbx-api; jego metadane (RFC 8414) są pod adresemhttps://api.cenaly.com/.well-known/oauth-authorization-server/pbx-api(oraz…/pbx-api/.well-known/openid-configuration). - Dynamiczna rejestracja klienta (RFC 7591):
POST /pbx-api/oauth/register. Adres powrotu — tylkolocalhost/127.0.0.1(dowolny port) lub adres znanego konektora (claude.ai, claude.com, chatgpt.com, chat.openai.com, vscode.dev, www.perplexity.ai, enterprise.perplexity.ai; w przypadku Cursor — dodatkowo dokładniecursor://anysphere.cursor-mcp/oauth/callback). Metoda logowania klienta —none(klient publiczny),client_secret_postlubclient_secret_basic. - Kod autoryzacji z PKCE (tylko
S256):GET /pbx-api/oauth/authorize→ okno logowania i zgody właściciela → powrót na twój adres zcode,stateiiss. Parametrresource— adres serwera MCP. - Tokeny:
POST /pbx-api/oauth/token. Kod jest jednorazowy i żyje 2 minuty (nieprawidłowycode_verifiergo unieważnia); token dostępu — 1 godzina; token odświeżania — 90 dni, przy każdym odświeżeniu wydawany jest nowy, poprzedni przestaje działać. - Odwołanie:
POST /pbx-api/oauth/revoke(RFC 7009) unieważnia połączenie w całości — jak „Odwołaj” w oknie „API i MCP”.
Token dostępu jest przyjmowany wszędzie tam, gdzie klucz: i w MCP, i w REST, z uprawnieniami i placówkami, które zatwierdził właściciel.
Limity i błędy#
- Na klucz lub połączenie: 120 żądań na minutę i 20 000 na dobę (REST i MCP razem).
- Połączenie w klik — osobno: 10 na minutę i 300 na dobę.
- Dziennik — do 100 połączeń na żądanie; w dzienniku centrali placówki — ostatnie 400 połączeń z 90 dni.
| Kod | Błąd | Co zrobić |
|---|---|---|
| 401 | api_key_missing, api_key_invalid |
klucza nie ma, został odwołany lub wygasł; token OAuth wygasł — odśwież go |
| 403 | scope_required |
klucz nie ma uprawnienia — dodaj je w „Uprawnienia” |
| 400 | location_required |
klucz ma kilka placówek — przekaż location |
| 404 | location_not_found |
placówki nie ma lub klucz jest ograniczony do innych placówek |
| 429 | rate_limited |
limit wyczerpany, powtórz po Retry-After sekundach |
Zdarzenia połączeń#
W czasie rzeczywistym — webhooki centrali: Telefonia → API i MCP → Webhooki lub POST /v1/webhooks kluczem z uprawnieniem webhooks:write. Na wasz adres https w ciągu sekund przychodzi POST dla każdego zdarzenia: call.started (rozpoczęte: kierunek, kto, do kogo, linia), call.ringing (u kogo dzwoni — po jednym na każdy telefon), call.answered (kto odebrał), call.transferred (kto i do kogo przekierował), call.ended (wynik, czas trwania, rozmowa, kto odebrał). Webhook można ograniczyć placówkami i typami zdarzeń.
{ "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": "Анна" } } }
Podpis — nagłówek X-Cenaly-Signature: t=<czas unix>,v1=<HMAC-SHA256 z "t.treść" sekretem webhooka>. Przeliczcie ponownie, porównajcie w stałym czasie i odrzućcie t starsze niż 5 minut. Sekret whsec_… jest pokazywany raz; „Nowy sekret” unieważnia poprzedni natychmiast.
Dostarczanie — odpowiedzcie 2xx w ciągu 10 sekund. W przeciwnym razie ponawiamy z przerwami 10 s, 30 s, 2, 5, 10, 15 i 15 minut (8 prób, około godziny); ponowienie niesie ten sam id — po nim odrzucajcie duplikaty. Dziennik dostarczeń (kod odpowiedzi, czas, próba) — w oknie webhooka i GET /v1/webhooks/{id}/deliveries; „Sprawdź” wysyła służbowe webhook.ping. Adres przez 3 doby z rzędu nie przyjął ani jednego dostarczenia — webhook wyłącza się sam: właściciel otrzymuje e-mail, w oknie „Webhooki” — oznaczenie „Wyłączony automatycznie” i przycisk „Włącz” (lub PATCH /v1/webhooks/{id} z { "enabled": true }).
Bez własnego serwera — ten sam strumień przez odpytywanie: GET /v1/events?since=<id ostatniego zdarzenia> lub narzędzie MCP get_call_events (zdarzenia placówki z ostatnich 7 dni, do 2000; na odpowiedź — do 500, more: true — przewijajcie dalej tym samym since).
Po przetworzeniu nagrania — połączenie z transkrypcją i analizą przychodzi webhookiem źródła „Webhook” w sekcji „Połączenia”.
„Otwarte / zamknięte” — PUT /v1/open-closed z { "closed": true } zamyka placówkę do końca doby (jak *28 z telefonu: połączenia idą regułami „poza godzinami”), false — otwiera; GET pokazuje, czy jest zamknięta i przez kogo. Z "until" — zamknięta do tego momentu (nie dalej niż 30 dni), potem otworzy się sama: czas bez przesunięcia (2026-10-05T09:00) czytany jest w strefie placówki; false zdejmuje również to. Potrzebny włączony znacznik „Zamknij kodem *28” w routingu (inaczej 409).
Bezpieczeństwo#
- Klucz jest pokazywany raz i przechowujemy go tylko jako odcisk; wyciekły klucz odwołuje się jednym przyciskiem.
- Połączenie OAuth zatwierdza tylko właściciel i widać, na jaki adres trafia dostęp; zatwierdzone połączenie można ograniczyć uprawnieniami i placówkami oraz odwołać w każdej chwili.
- Numery telefonów to dane osobowe waszych klientów: dawajcie uprawnienia do dziennika i nagrań tylko tym integracjom, którym są naprawdę potrzebne.
- Wszystkie zmiany przez klucz lub połączenie — w dzienniku działań centrali z nazwą klucza lub aplikacji.