HTTP 与运行时接口
除公开的 GET /health 外,本页列出的 callflow-esl 路由均使用共享 token 鉴权:配置 httpServer.authToken 中任一非空值后,请求头 token 或查询参数 token(主要用于 SSE 事件流)必须匹配,否则返回 HTTP 200 与 { "code": "401", "data": null, "msg": "Unauthorized" }。token 列表未配置或无有效值时不鉴权。
普通 JSON 控制接口遵循平台统一响应信封规范,返回 HTTP 200;健康检查未就绪时返回 HTTP 503:
- 成功:
{ "code": "0000", "data": T, "msg": "..." } - 业务/协议异常:
{ "code": string, "data": null, "msg": "..." }(例如"400"参数错误、"401"未授权、"404"资源未找到、"409"状态冲突、"500"/"502"服务端或下游 ESL 异常、"504"操作超时)
外呼 API
HTTP 接口:
text
POST /outbound-calls
Content-Type: application/json请求体:
json
{
"destinationNumber": "13800138000",
"businessCode": "default-call-business",
"callerIdNumber": "10086",
"callerIdName": "AI Bot",
"variables": {
"customer_id": "c001",
"campaign": "may-test"
},
"dialStringTemplate": "loopback/{{destinationNumber}}/default"
}返回体:
json
{
"code": "0000",
"data": {
"requestId": "uuid",
"replyText": "+OK ...",
"destinationNumber": "13800138000"
},
"msg": "外呼请求已提交"
}成功响应表示 FreeSWITCH 接受了 originate 命令,不表示被叫已经接通。请求体参数无效(code: "400")、FreeSWITCH 拒绝外呼(code: "502")或内部异常(code: "500")时,统一返回 HTTP 200 伴随对应错误信封 { "code": "...", "data": null, "msg": "..." }。
字段说明:
destinationNumber(必填):被叫号码businessCode(可选):会被写入 FreeSWITCH 通道变量business_code, 回拨进入 ESL 后按business_code优先路由业务callerIdNumber/callerIdName(可选):覆盖origination_caller_id_*variables(可选):字符串键值对,透传到 ESL 会话的额外通道变量dialStringTemplate(可选):覆盖config.freeswitch.originateDialStringTemplate, 必须包含占位符
健康检查与控制面接口
同一个 HTTP 端口(默认 9912)提供公开的请求:
text
GET /health就绪返回 HTTP 200:
json
{
"code": "0000",
"data": {
"activeSessions": 0,
"status": "running",
"eventConnection": "ready"
},
"msg": "请求成功"
}排空期间 status 为 draining;Inbound 事件连接认证、订阅或校准未完成时 eventConnection 为 not-ready。任一条件不满足就绪要求时返回 HTTP 503,保留相同信封和状态字段,code 为 "503"。activeSessions 包含正在执行最终清理的会话;socket close 不会提前减少计数。
退出信号首先停止 ESL 接入并拒绝新增外呼(控制接口返回 code "503"),存量通话控制继续可用。自然排空期限及强制收尾见配置说明。
业务路由缓存失效
text
POST /business-routes/reload
Content-Type: application/json
token: <共享令牌>请求体必须为对象 {},不接受附加字段。沿用本页共享 token 鉴权。成功返回 HTTP 200:
json
{ "code": "0000", "data": { "reloaded": true }, "msg": "请求成功" }本实例立即清空业务路由缓存,然后向 Redis 发布失效通知。广播失败返回 code "500",msg 明确说明本地已失效和广播失败;本地失效不回滚。失效前的在途查询不能把旧结果重新放入缓存。该接口不重新读取 TOML,不重新注册业务。
SIP 注册探活与清理
SIP 注册管理共有四个 HTTP 端点:callflow-esl 提供两个运行时端点,callflow-server 提供两个 管理台代理端点。详细语义以本节为准;管理台后端和前端 README 只保留入口摘要。
callflow-esl 运行时端点
以下两个端点遵循本页开头说明的共享 token 鉴权。
POST /freeswitch/registrations/probe
请求体:
json
{
"sipUser": "1010",
"sipHost": "192.168.2.184",
"profileName": "internal"
}sipUser与sipHost必填;sipHost必须取自目标注册行的sip_registrations.sip_host,不得用 ESL 连接地址或 outbound callback 地址代替。profileName可选,缺省为internal。- profile、SIP 用户、SIP 域和 Call-ID 均执行长度、控制字符及命令分隔符校验;非法输入返回 HTTP 400,不会进入 ESL socket。
- 服务通过 Inbound ESL 查询
sofia status profile <profile> user <user>@<host>,明确未发现注册时再用sofia_contact复核。该操作读取 FreeSWITCH 当前状态,不会主动发送一轮新的 SIP OPTIONS。 - 成功返回 HTTP 200:
json
{
"code": "0000",
"data": {
"reachable": true,
"registered": true,
"pingStatus": "Reachable",
"statusText": "Registered(TCP)",
"contact": "sip:1010@192.168.2.10:5060",
"userAgent": "Example UA",
"expSecs": 3500,
"pingTimeMs": 12,
"message": "分机在线且心跳正常 (12ms)"
},
"msg": "请求成功"
}其中 reachable 表示本次 Inbound ESL 查询成功,不等同于 SIP 终端可达;终端心跳状态看 pingStatus。明确未注册仍返回 HTTP 200、registered=false;非法 JSON、字段组合返回 code: "400";FreeSWITCH 连接、鉴权、命令拒绝或无法识别的 Sofia 回复返回 code: "502";注册操作共享约 3 秒整体 deadline,超时或上游取消会关闭 ESL socket 并返回 code: "504"。错误体统一为 { "code": "...", "data": null, "msg": "..." },不得把基础设施或协议错误伪装成未注册。
POST /freeswitch/registrations/flush
请求体可用 Call-ID 精确定位,或以分机号为后备目标:
json
{
"callId": "registration-call-id",
"sipUser": "1010",
"sipHost": "192.168.2.184",
"profileName": "internal"
}- 必须提供
callId,或同时提供sipUser与sipHost;存在callId时优先按 Call-ID 精确定位。 profileName缺省为internal;没有callId时,清理目标严格拼成<sipUser>@<sipHost>,不允许只按用户号清理。- 服务执行
sofia profile <profile> flush_inbound_reg <target>。仅明确+OK回复视为成功并返回 HTTP 200:{ "code": "0000", "data": { "success": true, "message": "..." }, "msg": "请求成功" }。 - 字段非法返回
code: "400";FreeSWITCH 命令拒绝或未知回复返回code: "502";整体操作超时返回code: "504"。
callflow-server 管理台代理端点
POST /api/freeswitch/registrations/probe 与 POST /api/freeswitch/registrations/flush 分别原样转发请求体到上述两个 callflow-esl 端点, 两个端点都经过 callflow-server 的路由守卫:要求 Authorization: Bearer <token>,缺失或无效返回 HTTP 401。callflow-esl 注册操作使用约 3 秒整体 deadline;callflow-server 代理保留额外 传输余量,并将浏览器请求取消继续向下游传播。
callflow-server 会把普通 JSON 统一规范为 { code, data, msg }:成功时 code="0000"; 上游 400/502/504 保留 HTTP status 并以对应状态码字符串作为 code;代理自身 deadline 返回 HTTP 504、code="504",连接失败、非 JSON 或响应协议异常返回 HTTP 502、code="502"。 浏览器页面只调用这两个 /api 端点,不直接持有 callflow-esl token。
实时盯屏控制由 callout-server 按通话所属 cell 调用以下内部端点(均走共享 token 鉴权,参数均置于请求体 JSON 中):
POST /live-call-sessions/intervene:写入待消费的人工介入请求(请求体接收{ sessionKey, agentId, agentName });POST /live-call-sessions/intervention/cancel:仅发起人原子取消尚未消费的请求(请求体接收{ sessionKey, agentId }), 若业务已消费并进入转接则返回 409;POST /live-call-sessions/hangup:写入幂等的NORMAL_CLEARING挂断信号(请求体接收{ sessionKey }), 由实际持有客户通道的业务实例执行,随后沿用正常结果回写与会议清理流程。
同一端口还承载浏览器软电话内部端点,由 src/runtime/browser-softphone-session-hub.ts 支撑,全部走共享 token 鉴权:
| 方法 | 路径 | 用途 |
|---|---|---|
POST | /softphone-sessions/create | 创建会话,请求体接收客户号码与线路配置 |
GET | /softphone-sessions/events | 订阅 SSE 事件,以 query 参数 ?sessionId=xxx 识别会话 |
POST | /softphone-sessions/messages | 发送文本消息,请求体接收 { sessionId, text } |
POST | /softphone-sessions/controls | 下发坐席控制动作,请求体接收 { sessionId, type, ... } |
POST | /softphone-sessions/end | 结束会话,请求体接收 { sessionId, reason? } |
这些端点由 callout-server 按其 callflowEsl.baseUrl 代理访问。浏览器请求和响应契约见 浏览器软电话与坐席 SIP 接入。
FreeSWITCH 运维控制接口
通过 Inbound ESL 直连 FreeSWITCH,提供底层运行状态检测与热重载能力(走共享 token 鉴权):
GET /freeswitch/status
获取 FreeSWITCH 运行时系统状态指标。
- 成功返回 HTTP 200:
json
{
"code": "0000",
"data": {
"reachable": true,
"uptimeText": "1 day, 2 hours, 15 minutes",
"version": "1.10.12",
"sessions": {
"total": 128,
"current": 4,
"max": 1000,
"peak": 36,
"peak5Min": 8
},
"sessionsPerSecond": {
"current": 0,
"max": 30,
"peak": 12,
"peak5Min": 2
},
"idleCpu": 98.5
},
"msg": "请求成功"
}- 若 FreeSWITCH 离线或 Inbound ESL 连接失败,接口仍返回 HTTP 200,但标记
data.reachable: false与错误信息,便于调用方区分网络异常与服务崩溃。
POST /freeswitch/reloadxml
通知 FreeSWITCH 立即重新加载磁盘 XML 配置文件(包括 dialplan、autoload_configs 等)。
- 成功返回 HTTP 200:
json
{
"code": "0000",
"data": {
"command": "reloadxml",
"reply": "+OK [Success]"
},
"msg": "请求成功"
}- 失败(ESL 连接拒绝或超时)返回
{ "code": "502", "data": null, "msg": "..." }。
会议管理与控制面接口
callflow-esl 自身具备基于 PostgreSQL(callflow.conferences、conference_members、conference_events)与 FreeSWITCH mod_conference 的完整会议管理与会控接口族(均走共享 token 鉴权,响应信封统一遵循 { code: "0000", data, msg },除无参请求外统一遵循 POST + JSON 规范):
1. 会议创建与查询
POST /conferences/create
创建/预分配会议室,支持只创建空会议,或创建后立即外呼邀请首批成员:
json
{
"conferenceId": "conf-room-101",
"metadata": { "department": "sales" },
"destinationNumbers": ["1001", "1002"],
"initialInviteNumbers": ["1003"],
"invite": {
"businessCode": "conference-guest-business",
"callerIdNumber": "8888",
"callerIdName": "Conference Bridge",
"concurrency": 2
}
}destinationNumbers与initialInviteNumbers:均为可选的初始邀请号码列表,二者会合并为同一批被邀请号码;invite为初始邀请的其余选项(businessCode/callerIdNumber/callerIdName/variables/dialStringTemplate/concurrency)。
POST /conferences/list
分页查询会议列表,请求体接收可选字段:
status:可选状态过滤(created/active/ended);limit:可选返回上限;offset:可选分页偏移量。
POST /conferences/detail
获取指定会议详情:包含会议 ID、状态(status)、加锁标记(locked)、录音状态(recordingStatus / recordingFileUrl)、开始/结束时间及当前在会成员计数。
- 请求体:
{ "conferenceId": "conf-room-101" }。
POST /conferences/members/list
获取指定会议在会成员快照列表,包含成员的 callUuid、conferenceMemberId、主叫信息、角色与入会时间。
- 请求体:
{ "conferenceId": "conf-room-101" }。
2. 会议流转与控制
POST /conferences/invite
向指定会议发起批量成员外呼邀请(基于 outbound socket 模式派发):
json
{
"conferenceId": "conf-room-101",
"destinationNumbers": ["1003", "1004"],
"businessCode": "conference-guest-business",
"concurrency": 2
}POST /conferences/lock 与 POST /conferences/unlock
加锁/解锁会议室。锁定后 FreeSWITCH 拒绝新成员加入。
- 请求体:
{ "conferenceId": "conf-room-101" }。
POST /conferences/end
结束会议室通话:
- 请求体:
{ "conferenceId": "conf-room-101", "mode": "hup" }(可选mode: "hup"默认挂断通道并释放,或mode: "kick"踢出成员)。
POST /conferences/members/control
精细化单成员控制:
jsonc
{
"conferenceId": "conf-room-101",
"callUuid": "xxxx-xxxx-xxxx",
"action": "mute" // 支持 "mute" | "unmute" | "deaf" | "undeaf" | "kick"
}3. 会议录音控制
POST /conferences/recording
会议级录音统一调度接口,通过 action 字段分发动作:
jsonc
// 启动录音
{
"conferenceId": "conf-room-101",
"action": "start",
"fileName": "rec-conf-101.wav" // 可选自定义文件名
}
// 停止录音
{
"conferenceId": "conf-room-101",
"action": "stop"
}
// 暂停 / 恢复 / 状态查询
{
"conferenceId": "conf-room-101",
"action": "pause" // 或 "resume" / "status"
}4. 会议实时对话流水与 AI 语音播报
POST /conferences/messages/list
增量拉取会议内部成员与 AI 的实时发言流水(转写与播报记录):
- 请求体:json
{ "conferenceId": "conf-room-101", "sinceId": 12 }sinceId(可选,数字):获取 ID 大于sinceId的增量发言记录,供前端实时高频轮询;不传时返回最新发言列表。
- 成功返回:json
{ "code": "0000", "data": [ { "id": 13, "conferenceId": "conf-room-101", "speaker": "agent", "text": "您好,有什么可以帮您?", "occurredAt": "2026-10-01T15:00:00.000Z" } ], "msg": "请求成功" }
POST /conferences/speak
向指定会议广播 AI 语音(即时调用 TTS 生成音频流并混音播报,同时自动持久化写入会议对话记录流):
- 请求体:json
{ "conferenceId": "conf-room-101", "text": "各位参会成员请注意,会议即将于两分钟后结束。" } - 成功返回:json
{ "code": "0000", "data": { "ok": true }, "msg": "请求成功" }