API và MCP của tổng đài riêng
Tổng đài riêng Cenaly mở cho các chương trình và trợ lý AI của bạn:
- CRM, hệ thống kế toán, website riêng — REST API: nhật ký cuộc gọi, ghi âm và bản chép lời, gọi một cú nhấp, số máy lẻ, chuyển tiếp, quy tắc cuộc gọi;
- Claude Code, Claude Desktop, Cursor — máy chủ MCP từ xa bằng khóa;
- ChatGPT, claude.ai, Perplexity, Cursor và VS Code — cùng máy chủ MCP đó không cần khóa: bạn đăng nhập vào tài khoản của mình và cấp quyền truy cập (OAuth).
Mọi thứ hoạt động dưới danh nghĩa chủ tài khoản và chỉ với các quyền mà chủ tài khoản đã cấp. Mỗi thay đổi đều hiển thị trong nhật ký hành động của tổng đài kèm tên khóa hoặc ứng dụng.
Địa chỉ#
| Mục | Địa chỉ |
|---|---|
| REST API | https://api.cenaly.com/pbx-api/v1 |
| Máy chủ MCP (streamable HTTP) | https://api.cenaly.com/pbx-api/mcp |
| Tài liệu về các phương thức | api.cenaly.com/pbx-api/v1/docs |
| Mô tả OpenAPI 3.1 | api.cenaly.com/pbx-api/v1/openapi.json |
| Siêu dữ liệu OAuth | https://api.cenaly.com/.well-known/oauth-authorization-server/pbx-api |
Khóa và quyền#
Chỉ chủ tài khoản tạo được khóa: Điện thoại → API và MCP → Khóa → Tạo khóa. Nhân viên có vai trò «Quản trị viên tổng đài» không thấy khóa — khóa hoạt động dưới danh nghĩa chủ tài khoản.
- Khóa dạng
cpx_…chỉ hiển thị một lần — ngay sau khi tạo, cùng với các đoạn mã sẵn sàng để dán vào kết nối. Chúng tôi chỉ lưu dấu vân tay của khóa (SHA-256), không thể khôi phục khóa — chỉ có thể phát hành khóa mới. - Có thể giới hạn khóa theo chi nhánh (nếu không — tất cả chi nhánh, kể cả chi nhánh trong tương lai) và theo thời hạn (30, 90, 365 ngày hoặc vô thời hạn).
- «Quyền» thay đổi quyền và chi nhánh của khóa ngay lập tức: tích hợp vẫn tiếp tục hoạt động, các quyền mới có hiệu lực từ yêu cầu tiếp theo.
- «Thu hồi» — ngay lập tức và vĩnh viễn: yêu cầu tiếp theo với khóa này sẽ nhận 401.
- Số khóa và kết nối đang hoạt động — tối đa 20 trên mỗi tài khoản.
Quyền được chọn theo bộ có sẵn hoặc theo từng ô tích:
| Quyền | Cấp những gì | Bộ |
|---|---|---|
calls:read |
nhật ký cuộc gọi và thẻ cuộc gọi kèm lộ trình | CRM, trợ lý AI, chỉ đọc, tất cả |
recordings:read |
liên kết đến ghi âm cuộc trò chuyện (10 phút) và bản chép lời | CRM, trợ lý AI, chỉ đọc, tất cả |
calls:dial |
gọi một cú nhấp: trước tiên điện thoại của nhân viên đổ chuông, sau khi trả lời tổng đài quay số | CRM, trợ lý AI, tất cả |
extensions:read |
số máy lẻ, nhóm, chuyển tiếp, «không làm phiền» | CRM, trợ lý AI, chỉ đọc, tất cả |
extensions:write |
thay đổi chuyển tiếp cá nhân và «không làm phiền» | trợ lý AI, tất cả |
presence:read |
ai đang rảnh, đang gọi, đang trò chuyện | trợ lý AI, chỉ đọc, tất cả |
reports:read |
số liệu tổng đài trong kỳ: mức độ phục vụ, thời gian chờ, cuộc gọi bị bỏ, AHT, FCR, CSAT, nơi mất cuộc gọi, phân tích theo hàng đợi, đường dây, nhân viên | chỉ đọc, tất cả |
routes:read |
số điện thoại, quy tắc cuộc gọi đến và đi, lịch sử quy tắc | chỉ đọc, tất cả |
routes:write |
tạo, thay đổi, xóa và sắp xếp lại quy tắc cuộc gọi, khôi phục từ lịch sử | chỉ bằng ô tích riêng |
config:export |
xuất cài đặt tổng đài ra tệp | chỉ bằng ô tích riêng |
config:import |
tải cài đặt từ bảng: xem trước, sau đó áp dụng | chỉ bằng ô tích riêng |
webhooks:write |
tạo, thay đổi, kiểm tra và xóa webhook sự kiện cuộc gọi | chỉ bằng ô tích riêng |
daynight:write |
đóng chi nhánh đến hết ngày hoặc đến một ngày cụ thể, mở lại — giống như *28 từ điện thoại |
chỉ bằng ô tích riêng |
Các quyền có thể thay đổi điều gì đó hoặc tiêu tốn phút được đánh dấu màu hổ phách trong cửa sổ. Ba quyền cuối không thuộc bất kỳ bộ nào, kể cả «Tất cả» — chỉ có thể cấp một cách có ý thức, bằng ô tích riêng.
ChatGPT, claude.ai, Perplexity, Cursor, VS Code: kết nối không cần khóa#
Các trợ lý đám mây không biết chèn khóa vào tiêu đề — chúng kết nối máy chủ MCP qua OAuth. Không cần tạo khóa.
claude.ai: Cài đặt → Trình kết nối → Thêm trình kết nối của bạn → dán https://api.cenaly.com/pbx-api/mcp → Kết nối.
ChatGPT: Cài đặt → Ứng dụng và trình kết nối → Nâng cao → bật chế độ nhà phát triển → Tạo → dán https://api.cenaly.com/pbx-api/mcp, xác thực — OAuth.
Perplexity: Cài đặt tài khoản → Trình kết nối → «+ Trình kết nối của bạn» → Từ xa → dán https://api.cenaly.com/pbx-api/mcp, xác thực — OAuth.
Cursor và VS Code: thêm máy chủ MCP từ xa (HTTP) với địa chỉ https://api.cenaly.com/pbx-api/mcp không có tiêu đề chứa khóa — cửa sổ đăng nhập sẽ mở trong trình duyệt. Trong Cursor: .cursor/mcp.json → {"mcpServers": {"cenaly-pbx": {"url": "https://api.cenaly.com/pbx-api/mcp"}}}; trong VS Code: lệnh «MCP: Add Server» → HTTP.
Sau đó trợ lý sẽ mở cửa sổ đăng nhập Cenaly. Sau khi đăng nhập bạn sẽ thấy:
- ai đang yêu cầu quyền truy cập — ứng dụng được gọi tên theo địa chỉ mà phản hồi sẽ trả về: «Trình kết nối đã xác minh · chatgpt.com» (tương tự — claude.ai, perplexity.ai, vscode.dev, Cursor), «Chương trình trên máy tính này» hoặc cảnh báo «Địa chỉ không quen thuộc». Tên mà ứng dụng gửi đến được hiển thị nhỏ — bất kỳ ai cũng có thể viết tên đó;
- quyền — những gì ứng dụng yêu cầu được đánh dấu (nếu ứng dụng yêu cầu «mọi thứ», bộ «Trợ lý AI» được đánh dấu); các quyền «chỉ bằng ô tích riêng» không bao giờ được đánh dấu trước;
- chi nhánh — tất cả hoặc những chi nhánh được chọn.
«Cho phép» đưa bạn trở lại trợ lý, và trợ lý ngay lập tức thấy các công cụ của tổng đài. Kết nối xuất hiện trong Điện thoại → API và MCP → Khóa dưới dạng một dòng có nhãn OAuth và địa chỉ ứng dụng — tại đó cũng có thể thay đổi quyền và thu hồi quyền truy cập. Kết nối lại cùng một ứng dụng sẽ cập nhật quyền của ứng dụng đó, chứ không tạo kết nối thứ hai.
Chỉ chủ tài khoản mới có thể cho phép kết nối.
Claude Code, Claude Desktop, Cursor: kết nối bằng khóa#
Claude Code — bằng một lệnh:
claude mcp add --transport http telephony https://api.cenaly.com/pbx-api/mcp --header "Authorization: Bearer cpx_…"
Claude Desktop, Cursor (.cursor/mcp.json) và các ứng dụng khác có tệp cấu hình:
{
"mcpServers": {
"telephony": {
"type": "http",
"url": "https://api.cenaly.com/pbx-api/mcp",
"headers": { "Authorization": "Bearer cpx_…" }
}
}
}
Các đoạn mã mẫu sẵn sàng với khóa của bạn sẽ hiển thị ngay sau khi tạo khóa. Trợ lý AI chỉ thấy những công cụ được khóa cho phép và có thể trả lời cho các câu hỏi như “hôm nay ai đã gọi mà không liên lạc được”, “gọi lại cho vị khách này từ số điện thoại của Anna”, “bật cho tôi chuyển tiếp đến số di động”.
REST API#
Khóa — trong tiêu đề Authorization: Bearer <ключ> (hoặc X-Api-Key). Nếu khóa có một chi nhánh, có thể không truyền tham số location; nếu có nhiều chi nhánh — phản hồi 400 location_required sẽ liệt kê chúng.
| Phương thức | Đường dẫn | Quyền | Chức năng |
|---|---|---|---|
| GET | /v1/me |
— | quyền, chi nhánh và giới hạn của khóa |
| GET | /v1/locations |
— | các chi nhánh của khóa và tổng đài của chúng |
| GET | /v1/calls |
calls:read |
nhật ký cuộc gọi: since, until, direction, status (answered, missed, ai), number, extension, limit tối đa 100, cursor |
| GET | /v1/calls/{id} |
calls:read |
cuộc gọi kèm lộ trình: quy tắc nào đã kích hoạt, số điện thoại của ai đã đổ chuông, ai đã trả lời |
| GET | /v1/calls/{id}/recording |
recordings:read |
liên kết đến ghi âm trong 10 phút |
| GET | /v1/calls/{id}/transcript |
recordings:read |
bản gỡ băng và tóm tắt cuộc trò chuyện |
| POST | /v1/dial |
calls:dial |
gọi một chạm: { "extension": "101", "number": "+995555123456" } |
| GET | /v1/dial/{commandId} |
calls:dial |
tiến trình cuộc gọi một chạm |
| GET | /v1/extensions |
extensions:read |
số nội bộ, nhóm, chuyển tiếp, “không làm phiền” |
| PUT | /v1/extensions/{ext}/forwarding |
extensions:write |
chuyển tiếp cá nhân của số nội bộ |
| PUT | /v1/extensions/{ext}/dnd |
extensions:write |
“không làm phiền”: { "on": true } |
| GET | /v1/presence |
presence:read |
ai đang rảnh ngay bây giờ |
| GET | /v1/metrics |
reports:read |
chỉ số tổng đài trong kỳ: from, to (ngày địa phương của chi nhánh YYYY-MM-DD, mặc định — 7 ngày gần nhất), compare (prev, year, prev,year hoặc để trống), sections (metrics,loss,queues,lines,operators,directions,heatmap,daily), callbacks=true — thêm cả tốc độ gọi lại |
| GET | /v1/routes |
routes:read |
số điện thoại, quy tắc cuộc gọi đến và đi |
| GET | /v1/routes/history |
routes:read |
lịch sử thay đổi quy tắc |
| POST, PUT, DELETE | /v1/routes/inbound…, /v1/routes/outbound… |
routes:write |
quy tắc cuộc gọi từng cái một: tạo, sửa các trường được truyền, xóa, sắp xếp lại, khôi phục |
| GET, POST | /v1/config/export, /v1/config/import |
config:export, config:import |
cài đặt ở định dạng tệp; tải lên — xem trước, sau đó áp dụng |
| GET | /v1/events |
calls:read |
sự kiện cuộc gọi của chi nhánh trong 7 ngày (tối đa 2000): since (id sự kiện hoặc thời gian), types, limit (tối đa 500 mỗi phản hồi) |
| GET, POST, PATCH, DELETE | /v1/webhooks… |
webhooks:write |
webhook: danh sách, tạo, sửa, xóa; …/rotate-secret, …/test, …/deliveries |
| GET, PUT | /v1/open-closed |
routes:read, daynight:write |
đã đóng hay chưa và đến khi nào; đóng đến hết ngày { "closed": true }, đến ngày { "closed": true, "until": "2026-10-05T09:00" } hoặc mở lại |
Mô tả đầy đủ các trường — trên trang tài liệu và trong OpenAPI.
Ai đã gọi hôm nay mà không liên lạc được:
curl -H "Authorization: Bearer cpx_…" \
"https://api.cenaly.com/pbx-api/v1/calls?status=missed&since=$(date -u +%Y-%m-%dT00:00:00Z)"
Gọi lại cho khách hàng từ số điện thoại của số nội bộ 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"
Chỉ số tháng 9 với so sánh tháng 8 và tháng 9 năm ngoái — cho bảng BI của riêng bạn:
curl -H "Authorization: Bearer cpx_…" \
"https://api.cenaly.com/pbx-api/v1/metrics?from=2026-09-01&to=2026-09-30&compare=prev,year"
Trong phản hồi metrics — cùng những con số như trên màn hình Điện thoại → Nhật ký cuộc gọi → Phân tích: cuộc gọi đến, đã nhận và bị nhỡ, mức độ phục vụ (ngưỡng và mục tiêu — từ cài đặt hàng đợi), thời gian chờ trung bình và tối đa, tỷ lệ bỏ máy không tính cuộc gọi ngắn, AHT, FCR, tỷ lệ chuyển tiếp và bot, CSAT. compare.prev và compare.year — cùng các trường đó cho kỳ trước và cùng kỳ năm ngoái. So sánh với năm ngoái được tính cho các kỳ đến 400 ngày.
Bật chuyển tiếp đến số di động nếu nhân viên không trả lời trong 15 giây:
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"
Công cụ MCP#
Các thao tác tương tự như REST. Ứng dụng khách chỉ thấy những công cụ được cho phép bởi khóa hoặc kết nối:
| Công cụ | Quyền |
|---|---|
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 |
Máy chủ hoạt động không cần phiên (streamable HTTP, JSON-RPC 2.0): initialize, tools/list, tools/call.
OAuth cho nhà phát triển tự tạo trình kết nối#
Máy chủ ủy quyền tuân theo đặc tả ủy quyền MCP — bất kỳ ứng dụng khách nào hỗ trợ đặc tả này đều dùng được:
- Yêu cầu đến MCP không có token sẽ nhận 401 kèm tiêu đề
WWW-Authenticate: Bearer resource_metadata="https://api.cenaly.com/.well-known/oauth-protected-resource/pbx-api/mcp". - Siêu dữ liệu tài nguyên (RFC 9728) chỉ ra máy chủ ủy quyền —
https://api.cenaly.com/pbx-api; siêu dữ liệu của nó (RFC 8414) nằm tạihttps://api.cenaly.com/.well-known/oauth-authorization-server/pbx-api(và…/pbx-api/.well-known/openid-configuration). - Đăng ký động ứng dụng khách (RFC 7591):
POST /pbx-api/oauth/register. Địa chỉ chuyển hướng — chỉlocalhost/127.0.0.1(mọi cổng) hoặc địa chỉ của trình kết nối đã biết (claude.ai, claude.com, chatgpt.com, chat.openai.com, vscode.dev, www.perplexity.ai, enterprise.perplexity.ai; với Cursor — thêm đúngcursor://anysphere.cursor-mcp/oauth/callback). Phương thức đăng nhập của ứng dụng khách —none(ứng dụng khách công khai),client_secret_posthoặcclient_secret_basic. - Mã ủy quyền với PKCE (chỉ
S256):GET /pbx-api/oauth/authorize→ cửa sổ đăng nhập và đồng ý của chủ sở hữu → chuyển hướng về địa chỉ của bạn kèmcode,statevàiss. Tham sốresource— địa chỉ của máy chủ MCP. - Token:
POST /pbx-api/oauth/token. Mã chỉ dùng một lần và có hiệu lực 2 phút (code_verifiersai sẽ vô hiệu hóa mã); token truy cập — 1 giờ; token làm mới — 90 ngày, mỗi lần làm mới sẽ cấp token mới, token cũ không còn hiệu lực. - Thu hồi:
POST /pbx-api/oauth/revoke(RFC 7009) vô hiệu hóa toàn bộ kết nối — giống nút “Thu hồi” trong cửa sổ “API và MCP”.
Token truy cập được chấp nhận ở mọi nơi dùng khóa: cả trong MCP lẫn REST, với các quyền và chi nhánh mà chủ sở hữu đã cho phép.
Giới hạn và lỗi#
- Trên mỗi khóa hoặc kết nối: 120 yêu cầu mỗi phút và 20 000 mỗi ngày (REST và MCP tính chung).
- Cuộc gọi một chạm — tính riêng: 10 mỗi phút và 300 mỗi ngày.
- Nhật ký — tối đa 100 cuộc gọi mỗi yêu cầu; trong nhật ký tổng đài của chi nhánh — 400 cuộc gọi gần nhất trong 90 ngày.
| Mã | Lỗi | Cách xử lý |
|---|---|---|
| 401 | api_key_missing, api_key_invalid |
không có khóa, khóa đã bị thu hồi hoặc hết hạn; token OAuth hết hạn — hãy làm mới token |
| 403 | scope_required |
khóa chưa có quyền — hãy thêm quyền trong “Quyền” |
| 400 | location_required |
khóa có nhiều chi nhánh — hãy truyền location |
| 404 | location_not_found |
không có chi nhánh hoặc khóa bị giới hạn ở các chi nhánh khác |
| 429 | rate_limited |
đã hết giới hạn, hãy thử lại sau Retry-After giây |
Sự kiện cuộc gọi#
Theo thời gian thực — webhook của tổng đài: Telephony → API và MCP → Webhook hoặc POST /v1/webhooks bằng khóa có quyền webhooks:write. Đến địa chỉ https của bạn trong vài giây, một POST được gửi cho mỗi sự kiện: call.started (bắt đầu: hướng, ai, đến đâu, đường dây), call.ringing (đang đổ chuông ở đâu — một sự kiện cho mỗi điện thoại), call.answered (ai đã trả lời), call.transferred (ai chuyển và chuyển cho ai), call.ended (kết quả, thời lượng, cuộc trò chuyện, ai đã trả lời). Có thể giới hạn webhook theo chi nhánh và loại sự kiện.
{ "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": "Анна" } } }
Chữ ký — tiêu đề X-Cenaly-Signature: t=<thời gian unix>,v1=<HMAC-SHA256 của "t.nội dung" bằng secret của webhook>. Hãy tính lại, so sánh trong thời gian không đổi và từ chối t cũ hơn 5 phút. Secret whsec_… chỉ hiển thị một lần; «Secret mới» vô hiệu hóa secret trước đó ngay lập tức.
Gửi đến — hãy trả lời 2xx trong 10 giây. Nếu không, chúng tôi gửi lại với khoảng nghỉ 10 giây, 30 giây, 2, 5, 10, 15 và 15 phút (8 lần thử, khoảng một giờ); lần gửi lại mang cùng id — dựa vào đó để loại bỏ bản trùng. Nhật ký gửi đến (mã phản hồi, thời gian, lần thử) — trong cửa sổ webhook và GET /v1/webhooks/{id}/deliveries; «Kiểm tra» gửi webhook.ping dịch vụ. Địa chỉ 3 ngày liên tiếp không nhận được lần gửi nào — webhook tự tắt: chủ sở hữu nhận được email, trong cửa sổ «Webhook» — nhãn «Đã tắt tự động» và nút «Bật» (hoặc PATCH /v1/webhooks/{id} với { "enabled": true }).
Không có máy chủ riêng — cùng luồng đó bằng cách thăm dò: GET /v1/events?since=<id sự kiện cuối cùng> hoặc công cụ MCP get_call_events (sự kiện của chi nhánh trong 7 ngày qua, tối đa 2000; mỗi phản hồi — tối đa 500, more: true — tiếp tục phân trang bằng cùng since).
Sau khi xử lý ghi âm — cuộc gọi có bản chép lời và phân tích đến qua webhook của nguồn «Webhook» trong mục «Cuộc gọi».
«Mở / đóng» — PUT /v1/open-closed với { "closed": true } đóng chi nhánh đến hết ngày (như *28 từ điện thoại: cuộc gọi đi theo quy tắc «ngoài giờ»), false — mở; GET cho biết đã đóng hay chưa và do ai. Với "until" — đóng đến thời điểm đó (không quá 30 ngày), sau đó sẽ tự mở: thời gian không có độ lệch (2026-10-05T09:00) được đọc theo múi giờ của chi nhánh; false cũng gỡ bỏ nó. Cần bật tùy chọn «Đóng bằng mã *28» trong định tuyến (nếu không sẽ trả 409).
Bảo mật#
- Khóa chỉ hiển thị một lần và chúng tôi chỉ lưu dấu vân tay; khóa bị lộ có thể thu hồi bằng một nút.
- Chỉ chủ sở hữu mới cho phép kết nối OAuth, và có thể thấy quyền truy cập được gửi đến địa chỉ nào; kết nối đã cho phép có thể giới hạn quyền và chi nhánh và thu hồi bất cứ lúc nào.
- Số điện thoại là dữ liệu cá nhân của khách hàng: chỉ cấp quyền truy cập nhật ký và ghi âm cho những tích hợp thực sự cần.
- Mọi thay đổi qua khóa hoặc kết nối — trong nhật ký hành động của tổng đài kèm tên khóa hoặc ứng dụng.