外观
HTTP 接口文档
外呼派发接口背后的领取、容量、originate 与终态闭环见 外呼任务生命周期。
callout-server 默认监听 0.0.0.0:9920。所有接口(除特别说明)都走会话鉴权,所有响应都用统一信封。
通用约定
响应信封
jsonc
// 成功
{ "code": "0000", "data": { /* ... */ }, "msg": "获取成功" }
// 失败
{ "code": "404", "data": null, "msg": "中文错误信息" }三个顶层字段始终存在,成功码固定为字符串 "0000",失败码通常与 HTTP status 一致。SSE 事件、二进制下载、音频流和 OPTIONS 预检不套用 JSON 信封;业务数据内部 原本名为 ok 的字段保持不变。 HTTP 状态码:200 成功;400 参数错误;401 未鉴权/凭证无效;403 权限不足; 404 资源不存在;409 冲突(重复编码/忙碌中/锁竞争/非空删除);410 资源已过期; 500 服务端错误;502/503 下游(FreeSWITCH/Redis)不可用。
鉴权
启用鉴权(auth.enabled=true,默认)时:
POST /api/auth/login用用户名/密码换取token。- 后续请求带
Authorization: Bearer <token>。 GET /api/auth/me返回当前用户与其permissions列表(前端据此做菜单/按钮门控)。
本地联调可设 CALLOUT_AUTH_ENABLED=false 关闭鉴权。启用时自动创建引导管理员 (默认 admin / 见部署配置,可用 CALLOUT_AUTH_BOOTSTRAP_ADMIN_USERNAME/PASSWORD 覆盖)。
内部回写接口 /api/call-results、/api/transfers/route、/api/transfers/end 与 /api/call-progress 不走会话鉴权,改用内部 token (请求头 X-Callout-Internal-Token,比对 internalApi.callResultToken;配置为空时放行)。
角色与权限矩阵
| 权限 | admin | supervisor | agent |
|---|---|---|---|
admin(用户/系统) | ✓ | ||
audit:read(审计) | ✓ | ||
line:write(线路) | ✓ | ||
campaign:write(任务) | ✓ | ✓ | |
campaign:dispatch(派发/会议监控/主管介入) | ✓ | ✓ | |
policy:write(策略) | ✓ | ✓ | |
compliance:write(DNC 合规) | ✓ | ✓ | |
import:write(导入) | ✓ | ✓ | |
contact:write(联系人/结果) | ✓ | ✓ | ✓ |
agent:write(坐席花名册管理) | ✓ | ✓ | |
agent:operate(坐席自助:状态/呼出/回拨/转人工/踢会议成员) | ✓ | ✓ | ✓ |
agent:write(管理坐席)与agent:operate(坐席本人操作)刻意分离:坐席能操作软电话工作流,但不能管理花名册。
分页与搜索
列表接口普遍支持 ?page(默认 1)、?pageSize(默认 50,部分上限 200)、?q(全文搜索)。 分页响应统一为 { rows, page, pageSize, total }。
鉴权与权限
POST /api/auth/login
请求:{ "username": "admin", "password": "******" } 响应 data:
jsonc
{
"token": "<bearer-token>",
"expiresAt": "2026-06-16T09:00:00.000Z",
"user": { "id": 1, "username": "admin", "displayName": "管理员", "role": "admin" },
"permissions": ["admin", "campaign:write", ...]
}POST /api/auth/logout
注销当前会话(请求头带 Bearer Token)。data 返回已注销用户摘要。
GET /api/auth/me
返回当前用户与权限;未登录或鉴权关闭时也会返回相应状态,供前端判断是否需要登录。
GET /api/roles/permissions
返回权限定义 + 角色→权限矩阵:{ permissions: PermissionDefinition[], roles: RolePermissionRow[] }。
仪表盘与运行态
GET /api/nlu/capabilities
由 callout-server 使用服务端配置的 endpoint、内部 token 和超时代理统一 NLU 的能力发现接口。响应 data.actions 包含 decision / extract,每项提供 defaultProfile 与 profiles[{ key, labelZh, descriptionZh }]。下游超时返回 504,其它不可用或无效响应返回 502;浏览器不会接触 NLU token。
注意路径差异:本接口是
/api/nlu/capabilities(无v1,callout-server 代理),下游是 llm-nlu-server 的GET /api/v1/nlu/capabilities(有v1,默认:9930)。NLU 执行接口POST /api/v1/nlu由 callflow-esl 直连 llm-nlu-server,不经 callout-server。
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| GET | /health | 公开 | 服务与数据库连通性 |
| GET | /api/overview | 会话 | 数量、比率、近期活动/呼叫尝试 |
| GET | /api/overview/trend?days=14 | 会话 | 按天聚合的呼叫/结果趋势(days 默认 14,范围 1–90) |
| GET | /api/media/config | 会话 | 前端拼接录音和情绪复核音频播放地址所需的 baseUrl |
| GET | /api/runtime/overview | 会话 | 运行中并发、卡住联系人、各任务进度、各线路并发、近期运行事件 |
| GET | /api/runtime/events | 会话 | SSE 运行事件流(text/event-stream) |
| PATCH | /api/runtime/events/:id/acknowledge | 会话 | 确认某条运行事件/告警 |
| GET | /api/audit-logs?page&pageSize&q | audit:read | 敏感操作审计日志 |
GET /api/runtime/overview 的 data(RuntimeOverviewResponse)含:activeContacts、stuckContacts、 runningBatches、scheduler.{submittedLastMinute,failedLastMinute,completedBatchesLastMinute}、 campaignProgress[]、lineConcurrency[]、cellConcurrency[](各 FS cell 的 { cellId, maxConcurrency, active },maxConcurrency 0 = 不限制)、stuckBatches[]、recentEvents[]。
业务编码(callflow 业务)
GET /api/businesses
列出 callflow 库中已启用的 call_businesses,供创建任务时选择 businessCode。 data.rows: { id, businessCode, businessName, isEnabled, remark }[]。
流程脚本 scripts
流程脚本是系统二的话术资产:前端可视化编排节点图(start / send / collect / router / chat / end / transfer),运行时由 callflow-esl 的 callout-flow-business 按 runtime 接口输出的 JSON 逐节点执行。NLU 与对话节点契约分别见 flow-script-nlu-development.md 和 flow-script-chat-development.md。
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| GET | /api/v1/scripts | 会话 | 列出流程脚本,供外呼活动选择与管理页展示 |
| POST | /api/v1/scripts | 会话 | 创建或更新脚本(节点、变量、话术、NLU 分支、结果归类) |
| DELETE | /api/v1/scripts/:id | 会话 | 软删除脚本,保留历史引用 |
| GET | /api/v1/scripts/:id/runtime | 内部 token | 输出 callout-flow-business 执行用的运行时 JSON(最小字段集) |
GET /api/v1/scripts/:id/runtime 不走会话鉴权:与 /api/call-results 一样比对 X-Callout-Internal-Token 与 internalApi.callResultToken(配置为空时放行),供 callflow-esl 在通话内拉取。
外呼线路 lines
线路决定主叫号码与并发;任务必须绑定至少一条启用线路。
| 方法 | 路径 | 权限 |
|---|---|---|
| GET | /api/lines | 会话 |
| POST | /api/lines | line:write(仅 admin) |
| PATCH | /api/lines/:id | line:write |
| DELETE | /api/lines/:id | line:write(仅软删未被引用的线路) |
POST /api/lines 请求体(CreateLineInput):
jsonc
{
"name": "默认外呼线路",
"code": "default-trunk",
"dialStringTemplate": "sofia/gateway/my-trunk/{{destinationNumber}}",
"callerIdNumber": "01088886666", // 可选,single_shared 模式默认主叫
"callerIdName": "示例企业", // 可选
"maxConcurrency": 10, // 该线路下所有任务共享
"allocationMode": "single_shared", // single_shared | number_pool | bound_numbers
"callerIdHeaderMode": "caller_id_only", // caller_id_only | p_preferred_identity | from_and_p_preferred_identity
"numbers": [ // number_pool / bound_numbers 模式的号码池
{ "number": "01088886601", "weight": 2, "maxConcurrency": 1, "isEnabled": 1 }
],
"isEnabled": 1,
"description": "..."
}号码分配模式:single_shared(一号多并发)/ number_pool(多号轮换,按 weight)/ bound_numbers(一号一并发)。响应 data.line: CalloutLineRow(含 numbers[])。
外呼任务 campaigns
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| GET | /api/campaigns?page&pageSize&q | 会话 | 列表(搜索+分页) |
| POST | /api/campaigns | campaign:write | 创建任务 |
| GET | /api/campaigns/:id | 会话 | 详情:任务 + 联系人样本 + 近期呼叫尝试 + 批次 |
| PATCH | /api/campaigns/:id | campaign:write | 更新(状态/并发/重试/工作日/绑定线路/业务配置等) |
| DELETE | /api/campaigns/:id | campaign:write | 软删空任务(非运行中且无联系人/批次/未软删尝试) |
| GET | /api/campaigns/:id/preflight | 会话 | 启动前预检:状态、计划窗口、待呼名单、线路、策略 |
POST /api/campaigns 请求体(CreateCampaignInput):
jsonc
{
"name": "外呼平台演示任务",
"businessCode": "callout-flow-business", // 必须能在 call_businesses 查到启用记录
"cellId": null, // 可选;绑定 config.cells 声明的 FS cell,空 = 派发时自动分配
"lineIds": [1], // 至少一条启用线路
"maxConcurrency": 5,
"maxRetries": 2,
"retryIntervalSeconds": 600,
"callIntervalMs": 1000,
"workdayStart": "09:00", // 可选 HH:mm
"workdayEnd": "20:00",
"scheduledStartAt": null,
"scheduledEndAt": null,
"businessConfig": {
"variables": { "script_id": "4" } // 仅字符串值会注入通道变量
},
"description": "..."
}PATCH /api/campaigns/:id 把 status 改为 running 即启动(被调度器扫描);改 paused 暂停自动派发。 启动时若既无待呼/可重试联系人,也无活跃通话,返回 HTTP 409 且保留原状态;已归档任务同样 不可启动。等待未来重试或仍有活跃通话时允许保持/切换为 running。 cellId 传字符串改绑 cell(须在 config.cells 中声明),传 null 或空串解绑。 businessConfig 使用整组替换语义,不接受旧的顶层 variables / intentKeywords。空的 variables 或 intentKeywords 会被省略。知识库实时盯屏任务可在业务专属配置中提交 { "businessCode": "kb-audio-chat-business", "businessConfig": { "intentKeywords": ["可以", "有兴趣"], "monitoringMode": "elastic", "monitoringPriority": 50 } }。 monitoringMode 支持 strict(无人时停止新派发)、elastic(允许 AI 无人值守)与 unattended;这两个监控属性只对 kb-audio-chat-business 生效,切换到其它业务时服务端会清理。
GET /api/campaigns/:id 的 data(CampaignDetail):{ campaign, contacts[], contactTotal, attempts[], batches[] }。
联系人 contacts
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| GET | /api/campaigns/:id/contacts?page&pageSize&q&status | 会话 | 列表 |
| POST | /api/campaigns/:id/contacts | contact:write | 粘贴号码同步导入,单次最多 10,000 行 |
| PATCH | /api/campaigns/:id/contacts/:contactId | contact:write | 更新单个 |
| DELETE | /api/campaigns/:id/contacts/:contactId | contact:write | 软删单个 |
| POST | /api/campaigns/:id/contacts/pause | contact:write | 批量暂停(避免运行中继续派发) |
| POST | /api/campaigns/:id/contacts/delete | contact:write | 批量软删 |
POST /api/campaigns/:id/contacts 请求体(CreateContactsInput):
jsonc
{
"contacts": [
{ "phoneNumber": "13800138000", "customerName": "王先生", "remark": "...",
"metadata": { "orderId": "A123", "level": "vip" } } // 仅字符串值会作为通道变量注入
]
}响应与文件导入一致,包含 totalRows / createdRows / skippedRows / duplicateRows / failedRows / errors。 errors 每项只有 { rowNumber, phoneNumber, reason };计数恒满足 totalRows = createdRows + duplicateRows + failedRows、skippedRows = duplicateRows + failedRows。 手工新增与粘贴入口不限制号码格式,只要求号码非空,并由服务端唯一索引最终去重。 CSV/TSV 文件导入仍按提交的地区、运营商和固话选项执行号码校验。
派发、批次与重试
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| POST | /api/campaigns/:id/dispatch | campaign:dispatch | 手动派发,最多 100 个待呼联系人 |
| POST | /api/campaigns/:id/retry | campaign:dispatch | 把匹配联系人重置为 pending |
| GET | /api/campaigns/:id/batches?page&pageSize | 会话 | 派发批次历史 |
| POST | /api/campaigns/:id/batches/:batchId/cancel | campaign:dispatch | 取消仍在 running 的批次 |
| GET | /api/call-attempts?page&pageSize&campaignId&status&callStatus&emotionStatus | 会话 | 呼叫明细列表 |
| GET | /api/call-attempts/:attemptId | 会话 | 按 ID 返回完整、未软删除的呼叫明细;不存在时返回 404 |
POST /api/campaigns/:id/dispatch 请求体 { "limit": 20 }(服务端 clamp 到 1..100)。 响应(DispatchCampaignResponse)data.result(DispatchResult):
jsonc
{
"campaignId": 1, "batchId": 42,
"requestedLimit": 20, "effectiveLimit": 20, "requested": 8,
"submitted": 8, "failed": 0, "remainingPending": 12,
"campaignStatus": "running",
"attempts": [
{ "contactId": 100, "phoneNumber": "138...", "attemptId": 555,
"status": "accepted", "requestId": "<uuid>", "replyText": "+OK ...",
"callStatus": "answered", // 启用回铃检测时带
"ringbackDetection": { "status": "answered", "detectionSource": "answer", ... } }
]
}- 派发可能被外呼策略拦截 →
409,error以「当前外呼策略禁止派发」开头。 - 锁竞争 →
409「任务正在调度中」;Redis 锁不可用 →503。 - 无可派发联系人 / 无可用槽位 / 呼叫间隔未到时不落批次,
batchId返回null,requested/submitted/failed均为 0。
POST /api/campaigns/:id/retry 请求体(RetryContactsInput):
jsonc
{ "status": "failed" } // 或 { "contactIds": [1,2,3] }把匹配联系人改回 pending。status 可取 failed/submitted/completed/paused。
小批量同步文件导入
文件解析和号码校验先完成,再进入事务锁定 campaign 并按 1,000 行分批写入。除 running 外的任务状态均可导入;运行中返回 409,且不写入联系人。数据库异常会整体回滚。
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| POST | /api/campaigns/:id/import-files | import:write | 上传最大 5 MiB、最多 10,000 行的 CSV/TSV 并同步返回结果 |
空文件、表头或列映射错误、未闭合 CSV 引号、超过 10,000 行返回 400;超过 5 MiB 返回 413。所有拒绝都发生在联系人事务写入前。JSON POST /import-jobs、导入历史、错误明细和 错误 CSV 接口已删除,不再保存逐行导入历史。
外呼策略 policies
全局/任务级禁呼、时段、号码规则、频控、重试与并发限制;执行语义更严格优先(全局与任务级都必须通过)。
| 方法 | 路径 | 权限 |
|---|---|---|
| GET | /api/policies?campaignId= | 会话 |
| POST | /api/policies | policy:write |
| PATCH | /api/policies/:id | policy:write |
| DELETE | /api/policies/:id | policy:write |
| POST | /api/policies/evaluate | 会话 |
POST /api/policies 请求体(CreateCalloutPolicyInput):
jsonc
{
"name": "工作时段",
"scope": "global", // global | campaign(campaign 时需 campaignId)
"type": "daily_window",
"action": "allow_window", // block | allow_window | limit
"priority": 10,
"config": { "start": "09:00", "end": "20:00" },
"isEnabled": 1
}策略类型 type 与 config:
| type | config 示例 | 作用 |
|---|---|---|
blocked_dates | { "dates": ["2026-05-01"] } | 命中日期阻断 |
holiday_blocklist | { "dates":["2026-02-17"], "recurringDates":["01-01"], "workdayOverrides":["2026-02-14"], "region":"CN" } | 节假日阻断 + 补班放行 |
daily_window | { "start":"09:00", "end":"20:00" } | 仅允许每日时段 |
weekly_window | { "days":[1,2,3,4,5], "start":"09:00", "end":"18:00" } | 仅允许星期时段 |
phone_blacklist | { "numbers":["138...","139..."] } | 按号码阻断 |
phone_prefix_blocklist | { "prefixes":["170","171"] } | 按前缀阻断 |
contact_frequency_limit | { "days":7, "maxAttempts":2 } | N 天最大触达次数 |
rate_limit | { "callIntervalMs":2000 } | 收紧呼叫间隔 |
retry_rule | { "maxRetries":1, "retryIntervalSeconds":1800 } | 收紧重试 |
concurrency_limit | { "maxConcurrency":3 } | 收紧并发 |
POST /api/policies/evaluate 试算(PolicyEvaluationRequest → PolicyEvaluationResult):
jsonc
// 请求
{ "campaignId": 1, "phoneNumber": "13800138000", "at": "2026-06-15T03:00:00.000Z" }
// 响应 data
{ "allowed": false, "reason": "命中策略:工作时段(当前不在允许时段)",
"matchedPolicies": [ /* CalloutPolicyRow */ ],
"effectiveLimits": { "maxConcurrency": 3, "maxRetries": 1, "retryIntervalSeconds": 1800, "callIntervalMs": 2000 } }DNC 免打扰名单
派发前独立于策略表进行阻断;业务回写退订/拒呼时自动沉淀。
| 方法 | 路径 | 权限 |
|---|---|---|
| GET | /api/dnc?page&pageSize&q | 会话 |
| POST | /api/dnc | compliance:write |
| DELETE | /api/dnc/:id | compliance:write |
POST /api/dnc:{ "phoneNumber": "138...", "reason": "客户要求", "expiresAt": null }(expiresAt 为空=永久)。
结果回写(内部)
POST /api/call-results
内部接口(token 鉴权,非会话)。由 callflow-esl 业务在通话结束时回写。 完整字段、定位规则、时长推导、DNC 自动沉淀见 outbound-business-example.md。最小示例:
http
POST /api/call-results
Content-Type: application/json
X-Callout-Internal-Token: <internalApi.callResultToken>
{ "requestId": "<callout_request_id>", "callStatus": "completed",
"payload": { "result": { "recognizedText": "客户有意向" } } }实时盯屏与人工介入 live-calls
坐席「一人盯多路、有意向即插入」的实时监控链路。数据源是 callflow-esl 的 kb-audio-chat-business 在通话过程中逐轮上报的进度事件写入 Redis TTL 共享快照, 并通过 Pub/Sub 向每个实例的本机 SSE 客户端广播。Redis 是必选依赖,服务启动时连接失败会 直接退出,不提供内存降级。快照不写业务库(挂机后的整段回写 /api/call-results 仍是权威数据)。链路与配置 详见 transfer-to-human.md §盯屏介入 与业务文档 callflow-esl/docs/existing-businesses.md。
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| POST | /api/call-progress | 内部 token | callflow-esl 逐轮上报通话进度(started/segment/intervention/ended),更新共享快照并广播 |
| GET | /api/live-calls?campaignId | agent:operate | 当前用户实际获配及介入前保留只读通话的快照与全局容量摘要 |
| GET | /api/live-calls/events?campaignId | agent:operate | 多路复用 SSE:推送实际获配及保留只读通话的字幕/关键词/介入/归属变化事件 |
| POST | /api/live-monitor/heartbeat | agent:operate | 登记并续租盯屏 Presence,按坐席实时状态计算接管容量并触发重分配 |
| DELETE | /api/live-monitor/presence | agent:operate | 主动离开盯屏并立即释放分配;异常断线由 TTL 回收 |
| POST | /api/live-calls/:callKey/intervene | agent:operate | 坐席认领人工介入,信号转发给 callflow-esl(原子抢占,重复认领返回 409) |
| POST | /api/live-calls/:callKey/intervention/cancel | agent:operate | 仅介入发起人取消仍为 requested 的请求;已消费进入转接返回 409 |
| POST | /api/live-calls/:callKey/release | agent:operate | 当前监控责任人主动让出;本次页面 Presence 生命周期内不再分回本人 |
| POST | /api/live-calls/:callKey/hangup | campaign:dispatch | 主管/管理员以 NORMAL_CLEARING 结束整通客户业务,成功操作写审计与运行事件 |
callKey形如attempt:<attemptId>(或request:<requestId>)。segment事件的speaker取值:bot(AI 播报)/customer(客户 ASR)/agent(人工介入后坐席的会议转写);介入成功后坐席与客户的发言仍持续上报。- 意向关键词在 campaign 的
businessConfig.intentKeywords中配置;收到客户 ASR 片段命中 任一关键词时,SSE 推intent-alert事件,盯屏页高亮 + 提示音。 - 每路通话只分配一个主监控人;分配按
businessConfig.monitoringPriority、意向命中、等待时间、 坐席 cell 与页面任务过滤执行。同一登录用户的多个标签页合计仍受服务端容量限制。 - 介入期间只保留介入通话的实际责任,其余原获配通话以
monitorViewMode=retained继续只读展示;monitorAssignment可为 null(等待接管)或指向另一坐席。介入结束后保留视图自动清除。 strict任务会把活跃盯屏容量加入任务/线路/cell 的派发上限计算;无人时联系人保持待呼。- 介入生效有节点边界延迟(业务播完当前录音/收音后才转接),典型 3~10 秒。
- Redis 已启用但不可用时,快照、SSE 建连和介入返回
503,不会退化为各实例分裂的本地状态。
坐席与软电话 agents
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| GET | /api/agents | 会话 | 坐席 + 坐席组列表 |
| GET | /api/agents/extensions | 会话 | FreeSWITCH 可用分机(含 freeswitchReachable) |
| GET | /api/agents/phone-statuses | agent:operate | 全部坐席的非敏感注册摘要:agentId / phoneExtension / registrationStatus;四态为 registered / unregistered / unconfigured / unknown |
| POST | /api/agent-groups | agent:write | 创建坐席组 |
| PATCH/DELETE | /api/agent-groups/:groupId | agent:write | 更新/删除坐席组 |
| POST | /api/agents | agent:write | 创建坐席 |
| PATCH/DELETE | /api/agents/:id | agent:write | 更新/删除坐席(忙碌中不可删) |
| GET | /api/agents/me/status | agent:operate | 当前用户绑定且启用的坐席状态及电话注册态摘要;未绑定时 agent: null |
| PATCH | /api/agents/me/status | agent:operate | 切换本人绑定坐席状态,仅接受 idle / busy / offline;电话仅在 registered(可用)时才能切到非离线状态 |
| PATCH | /api/agents/:id/status | agent:operate | 切换 idle / busy / offline;普通坐席仅能修改本人,主管/管理员可修改全部;旧 online 请求兼容映射为 idle;电话仅在 registered(可用)时才能切到非离线状态 |
| POST | /api/agents/:id/callbacks | agent:operate | 人工回拨:originate 坐席分机后 bridge 客户 |
| POST | /api/agents/:id/manual-calls | agent:operate | 人工呼出任意客户号码 |
| POST | /api/agents/:id/transfers | agent:operate | 工作台对客户号码发起坐席↔客户桥接呼叫(≠AI会议转人工,见 transfer-to-human.md §7) |
坐席公开结构只包含唯一终端标识 phoneExtension,不再返回 sipUsername。升级兼容期内, 创建/更新接口仍会把旧请求中单独出现的 sipUsername 映射到 phoneExtension;两个非空值 不一致时返回 400,禁止静默选择。 | GET | /api/softphone-calls?page&pageSize&agentId&campaignId | agent:operate | 软电话呼出/回拨/转人工记录 |
POST /api/agents/:id/manual-calls 请求体:
jsonc
{ "phoneNumber": "13800138000", "campaignId": 1, "contactId": 100, "note": "..." }
// 也可只传 contactId,号码从联系人取回拨/呼出会经 submitAgentBridgeCall 先 originate 坐席分机、接通后 &bridge 客户号码, 并落 agent_call_records 记录(direction=callback/manual_call)。
浏览器软电话会话 softphone-sessions
坐席浏览器 SIP 软电话链路:整体流程、坐席 SIP 字段、配置与 FreeSWITCH 侧要求见 softphone-sip.md。callout-server 在这组接口上做权限校验与 agent_call_records 落库,实时会话本体代理到 callflow-esl 的 browser-softphone-session-hub。
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| GET | /api/agents/me/sip-config | agent:operate | 当前登录坐席的浏览器 SIP 注册配置(坐席与用户按 agents.userId 一对一绑定) |
| GET | /api/agents/sip-configs | agent:operate | 当前用户可见的已启用 SIP 配置列表(admin / supervisor 可见全部) |
| POST | /api/softphone-sessions | agent:operate | 创建会话:先落 agent_call_records,再请求 callflow-esl 创建实时会话;返回 sessionId、serviceNumber 与浏览器拨号必须原样携带的 sipExtraHeaders |
| GET | /api/softphone-sessions/:sessionId/events | agent:operate | SSE 实时事件流(session / message / tts / error);EventSource 场景支持 ?access_token= 查询参数鉴权 |
| POST | /api/softphone-sessions/:sessionId/messages | agent:operate | 坐席打字文本,经 TTS 注入客户腿播放 |
| POST | /api/softphone-sessions/:sessionId/controls | agent:operate | 控制命令:{"type":"hold"} / resume / {"type":"dtmf","digits":"..."} / {"type":"transfer","targetNumber":"..."} |
| POST | /api/softphone-sessions/:sessionId/notes | agent:operate | 更新会话备注 |
| POST | /api/softphone-sessions/:sessionId/end | agent:operate | 结束会话(同时结束会议与录音收尾) |
| GET | /api/softphone-calls/:softphoneCallId | agent:operate | 坐席通话记录详情:消息时间线、TTS 事件、录音信息与分角色转写 |
转人工会议 conference-sessions
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| POST | /api/transfers/route | 内部 token | AI 业务转人工路由:预占空闲坐席 + originate 入会(callflow-esl 调用) |
| POST | /api/transfers/end | 内部 token | 客户挂机幂等收尾:校验客户 UUID,结束会议全部坐席/主管腿并释放坐席 |
| GET | /api/conference-sessions | campaign:dispatch | 进行中会话列表(附 FS 实时成员数) |
| GET | /api/conference-sessions/:conferenceId | campaign:dispatch | 会话详情 + 成员腿 |
| POST | /api/conference-sessions/:conferenceId/participants | agent: agent:operate / supervisor: campaign:dispatch | 加成员(坐席盲转/协商转 或 主管监听/强插) |
| DELETE | /api/conference-sessions/:conferenceId/participants/:softphoneCallId | agent:operate | 踢出一名成员腿 |
录音与转写
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| GET | /api/media/config | 会话 | 返回兼容默认值 freeswitchRecordingBaseUrl、按 cell 映射 freeswitchRecordingBaseUrlsByCell 与 emotionAudioBaseUrl |
| GET | /api/call-attempts/:attemptId | 会话 | 返回单条完整呼叫明细;记录不存在或已软删除时返回 404 |
| GET | /api/call-attempts/:id/transcript-segments | 会话 | 按外呼尝试查询分角色转写片段 |
| POST | /api/call-attempts/delete | compliance:write | 批量软删通话记录,并清除 contacts.lastAttemptId |
外呼录音由业务回写到
call_attempts.recordingUrl,数据库和呼叫尝试 API 响应都只保留文件名短值;前端按attempt.cellId从freeswitchRecordingBaseUrlsByCell选择前缀,历史空 cell 使用兼容默认值,非空失效 cell 不生成错误 URL。情绪分析 worker 使用相同规则。情绪分析音频仍用emotionAudioBaseUrl和短值拼接。
用户管理 users
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| GET | /api/users?page&pageSize&q | admin | 列表 |
| POST | /api/users | admin | 创建 |
| PATCH | /api/users/:id | admin | 改角色/启停/重置密码 |
| DELETE | /api/users/:id | admin | 软删(禁止删自己或最后一个管理员) |