自有 PBX 的 API 与 MCP
自有 PBX Cenaly 面向您的程序和 AI 助手开放:
- CRM、记账系统、自有网站 — REST API:通话记录、录音与转写、点击呼叫、分机、转接、呼叫规则;
- Claude Code、Claude Desktop、Cursor — 通过密钥连接远程 MCP 服务器;
- ChatGPT、claude.ai、Perplexity、Cursor 和 VS Code — 同一个 MCP 服务器,无需密钥:登录自己的账户并授权访问(OAuth)。
一切操作均以账户所有者名义执行,且仅限其授予的权限。每项变更都会显示在 PBX 操作日志中,并标注密钥或应用名称。
地址#
| 项目 | 地址 |
|---|---|
| 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 → 密钥 → 创建密钥。拥有“PBX 管理员”角色的员工看不到密钥——密钥以所有者名义生效。
cpx_…形式的密钥只显示一次——创建后立即显示,并附带可直接粘贴的连接片段。我们只保存其指纹(SHA-256),密钥无法恢复——只能重新签发。- 密钥可限制门店(否则为全部门店,包括未来新增的)和有效期(30、90、365 天或永久)。
- **“权限”**可即时更改密钥的权限和门店:集成继续运行,新权限从下一个请求起生效。
- “撤销”——立即且永久:使用该密钥的下一个请求将收到 401。
- 每个账户的有效密钥和连接最多 20 个。
权限可通过预设组合或勾选选择:
| 权限 | 作用 | 组合 |
|---|---|---|
calls:read |
通话记录及含路由的通话详情 | CRM、AI 助手、只读、全部 |
recordings:read |
通话录音链接(10 分钟)和转写 | CRM、AI 助手、只读、全部 |
calls:dial |
点击呼叫:先呼叫员工电话,接听后 PBX 再拨号 | CRM、AI 助手、全部 |
extensions:read |
分机、群组、转接、“免打扰” | CRM、AI 助手、只读、全部 |
extensions:write |
更改个人转接和“免打扰” | AI 助手、全部 |
presence:read |
谁空闲、通话中、正在交谈 | AI 助手、只读、全部 |
reports:read |
某时段的呼叫中心指标:服务水平、等待、未接、AHT、FCR、CSAT、在哪里丢失来电,按队列、线路、员工的细分 | 只读、全部 |
routes:read |
号码、呼入和呼出规则、规则历史 | 只读、全部 |
routes:write |
创建、更改、删除和调整呼叫规则顺序,从历史回滚 | 仅单独勾选 |
config:export |
以文件格式导出 PBX 设置 | 仅单独勾选 |
config:import |
从表格导入设置:先预览,再应用 | 仅单独勾选 |
webhooks:write |
创建、更改、测试和删除通话事件 webhook | 仅单独勾选 |
daynight:write |
将门店关闭至当日结束或指定日期,开启同理,等同于电话上的 *28 |
仅单独勾选 |
会更改内容或消耗分钟的权限在窗口中用琥珀色标记。最后三项权限不属于任何组合,甚至不在“全部”中——只能通过单独勾选有意识地授予。
ChatGPT、claude.ai、Perplexity、Cursor、VS Code:无需密钥连接#
云端助手无法将密钥插入请求头——它们通过 OAuth 连接 MCP 服务器。无需创建密钥。
**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:**添加地址为 https://api.cenaly.com/pbx-api/mcp 的远程(HTTP)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 助手”组合);“仅单独勾选”的权限从不预先勾选;
- 门店——全部或所选门店。
“允许”会将您带回助手,它立即就能看到 PBX 工具。连接会出现在电话 → 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 |
— | 密钥的门店及其 PBX |
| 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 |
Webhook:列表、创建、修改、删除;…/rotate-secret、…/test、…/deliveries |
| GET、PUT | /v1/open-closed |
routes:read、daynight:write |
是否已关闭及关闭到何时;关闭到当天结束 { "closed": true },关闭到某日期 { "closed": true, "until": "2026-10-05T09:00" },或开启 |
今天谁打来但没接通:
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 条通话;门店的 PBX 日志——最近 90 天内的最近 400 条通话。
| 代码 | 错误 | 处理方法 |
|---|---|---|
| 401 | api_key_missing、api_key_invalid |
密钥不存在、已被撤销或已过期;OAuth 令牌已过期——请刷新 |
| 403 | scope_required |
密钥缺少权限——请在“权限”中添加 |
| 400 | location_required |
密钥关联多个门店——请传入 location |
| 404 | location_not_found |
门店不存在,或密钥受限于其他门店 |
| 429 | rate_limited |
已达限额,请在 Retry-After 秒后重试 |
通话事件#
实时 — 电话系统 Webhook:电话 → API 和 MCP → Webhook,或使用具有 webhooks:write 权限的密钥调用 POST /v1/webhooks。每个事件都会在几秒内以 POST 请求发送到您的 https 地址:call.started(已开始:方向、主叫方、被叫方、线路)、call.ringing(哪部电话在响——每部电话各一条)、call.answered(谁接听了)、call.transferred(谁转给了谁)、call.ended(结果、时长、通话内容、谁接听的)。Webhook 可按门店和事件类型进行限制。
{ "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=<用 Webhook 密钥对 "t.请求体" 计算的 HMAC-SHA256>。请重新计算,以恒定时间比较,并拒绝超过 5 分钟的 t。密钥 whsec_… 仅显示一次;“新密钥”会立即废止旧密钥。
投递 — 请在 10 秒内返回 2xx。否则我们会以 10 秒、30 秒、2、5、10、15 和 15 分钟的间隔重试(共 8 次尝试,约一小时);重试携带相同的 id——据此丢弃重复项。投递日志(响应码、时间、尝试次数)位于 Webhook 窗口和 GET /v1/webhooks/{id}/deliveries;“检查”会发送一条服务性 webhook.ping。地址连续 3 天未接受任何投递——Webhook 会自动关闭:所有者会收到邮件,“Webhook”窗口中会显示“已自动关闭”标记和“启用”按钮(或使用 { "enabled": true } 调用 PATCH /v1/webhooks/{id})。
没有自己的服务器 — 通过轮询获取同一事件流:GET /v1/events?since=<最后事件的 id> 或 MCP 工具 get_call_events(门店最近 7 天的事件,最多 2000 条;每次响应最多 500 条,more: true 表示用相同的 since 继续翻页)。
录音处理完成后 — 带有转写和分析的通话会通过“Webhook”来源的 Webhook 送达,位于“通话”部分。
“已关闭 / 已开放” — 使用 { "closed": true } 调用 PUT /v1/open-closed 会将门店关闭至当日结束(如同电话上的 *28:来电按“非工作时间”规则处理),false 则开放;GET 显示是否已关闭以及由谁关闭。使用 "until" 时——关闭至该时刻(不超过 30 天),之后会自动开放:不带时区偏移的时间(2026-10-05T09:00)按门店时区解读;false 也会取消该设置。需要在路由中勾选“用代码 *28 关闭”(否则返回 409)。
安全#
- 密钥仅显示一次,我们只以指纹形式存储;泄露的密钥可通过一个按钮撤销。
- OAuth 连接仅限所有者授权,并且可以看到访问权限授予了哪个地址;已授权的连接可按权限和门店进行限制,并可随时撤销。
- 电话号码是您客户的个人数据:仅向确实需要这些数据的集成授予日志和录音权限。
- 通过密钥或连接进行的所有更改都会记录在电话系统操作日志中,并注明密钥或应用名称。