AI 外呼
线索导入(批量/单条)、启停项目、查询通话结果、拨打配置、批量查询与撤销线索。
线索排队与拨打机制说明(常见问题,建议先看一遍):
- 同号覆盖:同一手机号在同一天内重复推送线索(无论批量上传还是 2.3 实时单条),会覆盖当天已有的线索记录并重新置为待拨状态——即使旧记录已经拨打完成,也会被重新拉回排队。如果同一天同一号码有多个独立通知都需要送达(比如同一人当天多场会议),目前无法在同一
programId下同时保留多条待拨记录,请拆分到不同programId推送,或错开到不同自然日推送。- 暂停期间推送:项目处于
PAUSED时推送的线索会正常入队(状态WAITING),不会丢失,也无需重推;项目恢复RUNNING后会自动开始拨打这些线索。- 时段外排队:不在工作时段内推送的线索会保持排队,到下一个允许拨打时段自动补呼;目前没有过期时间(会一直排队到拨出或被撤销为止)。如果线索已经推送但临时不需要再拨了(比如会议被取消),可调用 2.8 撤销线索 撤回。
- 未接通重拨:平台内置随访重拨策略(默认关闭,需联系管理员开启),重拨的退避间隔与最大次数由平台侧配置,暂不支持通过 API 查询或修改。开启后,重拨产生的每一通电话都会作为独立记录出现在 2.5/2.7 查询接口返回的
calls[]里。建议提前和平台确认好重拨策略由哪边负责,避免和自建的重推逻辑重复打扰用户。
2.1 线索导入(批量上传)
步骤一:下载该数字员工的线索 Excel 模板
GET /api/v1/external/programs/:programId/leads/template(所需权限:READ)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| botId | string | - | 指定数字员工,不传则用项目第一个启用的数字员工 |
curl -O -J "https://api.halfcall.cn/api/v1/external/programs/cmk.../leads/template?botId=bot_xxx" \
-H "Authorization: Bearer sk_xxx"
返回 .xlsx 文件,第一行为表头(字段由数字员工话术配置决定),例如:
| 手机号码 | 姓名 | 公司名称 |
|---|---|---|
表头列名即为上传时的字段映射依据,请勿修改列名,否则上传会报字段不匹配错误。
步骤二:按模板填写数据后上传
POST /api/v1/external/programs/:programId/leads/upload(所需权限:LEADS)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| file | File | ✓ | Excel 或 CSV(.xls .xlsx .csv,最大 100MB) |
| botId | string | ✓ | 数字员工 ID,需与下载模板时一致 |
| name | string | - | 线索包名称,不传则用文件名 |
curl -X POST "https://api.halfcall.cn/api/v1/external/programs/cmk.../leads/upload" \
-H "Authorization: Bearer sk_xxx" \
-F "file=@/path/to/leads.xlsx" \
-F "botId=bot_xxx" \
-F "name=茅台外呼-第一批"
{
"code": 0,
"data": {
"threadId": "thread_xxx",
"name": "茅台外呼-第一批",
"total": 980,
"skipped": 20
}
}
响应字段说明:
| 字段 | 说明 |
|---|---|
| threadId | 线索包唯一 ID,可用于后续查看该批次外呼进度 |
| name | 线索包名称 |
| total | 成功写入的线索数量 |
| skipped | 因同一项目下手机号重复被跳过的数量 |
2.2 查询线索字段定义
GET /api/v1/external/programs/:programId/leads/fields(所需权限:READ)
在推送线索之前先调用一次,拿到该数字员工
variables需要传的准确字段 key(而不是 Excel 模板里看到的中文列名)。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| botId | string | - | 指定数字员工,不传则用项目 stage 最小的已启用数字员工(与下载模板默认逻辑一致) |
curl "https://api.halfcall.cn/api/v1/external/programs/cmk.../leads/fields" \
-H "Authorization: Bearer sk_xxx"
{
"code": 0,
"data": {
"programId": "cmk...",
"botId": "bot_xxx",
"fields": [
{ "key": "org_name", "name": "单位名称", "required": true },
{ "key": "contact_name", "name": "联络员姓名", "required": true },
{ "key": "meeting_title", "name": "会议主题", "required": true }
]
}
}
key就是 2.3 节variables里要用的字段名;name只是给人看的中文标签,不要把name当 key 传(会被拒绝,见下方校验说明)。
2.3 线索导入(实时单条)
POST /api/v1/external/leads(所需权限:LEADS)
适合用户实时触发场景(如刚填写表单),立即入队等待外呼。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| programId | string | ✓ | 项目 ID |
| phone | string | ✓ | 手机号 |
| botId | string | - | 指定数字员工(用于按其字段定义做校验),不传则用项目 stage 最小的已启用数字员工 |
| variables | object | - | 自定义参数,key 必须是 2.2 节返回的 key(也兼容直接传 name,会自动转换,但不建议依赖这个兼容行为) |
curl -X POST "https://api.halfcall.cn/api/v1/external/leads" \
-H "Authorization: Bearer sk_xxx" \
-H "Content-Type: application/json" \
-d '{
"programId": "cmkhndatf000jdulm6ez8ojyf",
"phone": "13800138000",
"variables": { "org_name": "示例公司", "contact_name": "张三" }
}'
{
"code": 0,
"threadId": "detail_xxx",
"phone": "13800138000",
"message": "Lead has been queued for processing"
}
响应字段说明:
| 字段 | 说明 |
|---|---|
| threadId | 线索唯一 ID,可传入 GET /api/v1/external/leads?threadId= 查询后续通话结果 |
| phone | 已入队的手机号 |
| message | Lead has been queued for processing=新线索已入队;Lead already exists, re-queued for calling=手机号已存在,重新入队 |
字段校验:variables 会按该数字员工的字段定义校验——传了未知字段、或缺了必填字段,都会直接返回 400 并在 msg 里说明具体字段,线索不会入队。校验通过前后的 key 均按 2.2 节的 key 处理(若传的是 name 会被自动转换)。
2.4 启动项目
线索上传后,需启动项目才会开始外呼。
POST /api/v1/external/programs/:programId/run-state(所需权限:CONTROL)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| action | string | ✓ | start=启动,pause=暂停 |
curl -X POST "https://api.halfcall.cn/api/v1/external/programs/cmk.../run-state" \
-H "Authorization: Bearer sk_xxx" \
-H "Content-Type: application/json" \
-d '{"action": "start"}'
{ "code": 0, "data": { "programId": "cmk...", "runState": "RUNNING" } }
响应字段说明:
| 字段 | 说明 |
|---|---|
| programId | 操作的项目 ID |
| runState | 变更后的运行状态:RUNNING=已启动,PAUSED=已暂停 |
2.5 查询线索通话结果
通话结束后,可主动查询线索的通话记录和意向分析结果。
GET /api/v1/external/leads(所需权限:READ)
# 方式1:通过推送线索时返回的 threadId
curl "https://api.halfcall.cn/api/v1/external/leads?threadId=detail_xxx" \
-H "Authorization: Bearer sk_xxx"
# 方式2:通过 programId + phone
curl "https://api.halfcall.cn/api/v1/external/leads?programId=cmk...&phone=13800138000" \
-H "Authorization: Bearer sk_xxx"
{
"code": 0,
"data": {
"threadDetailId": "detail_xxx",
"phone": "13800138000",
"status": "COMPLETED",
"stage": 1,
"variables": { "name": "张三" },
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:01:30.000Z",
"calls": [
{
"callId": "call_xxx",
"reached": "REACHED",
"duration": 120,
"intention": "HIGH",
"intentionRate": 85,
"summary": "客户感兴趣,约好明天回电",
"recordingUrl": "https://cdn.halfcall.cn/rec/xxx.mp3",
"extractData": { "interested": true },
"createdAt": "2024-01-01T00:01:00.000Z"
}
]
}
}
响应字段说明:
| 字段 | 说明 |
|---|---|
| threadDetailId | 线索唯一 ID |
| phone | 手机号 |
| status | 线索状态:PENDING=待拨打,CALLING=拨打中,COMPLETED=已完成,INVALID=无效 |
| stage | 当前所在轮次(对应数字员工的 stage) |
| variables | 推送线索时传入的自定义变量 |
| calls | 该线索的所有通话记录,按时间倒序 |
| calls[].reached | 接通状态,见下方枚举表 |
| calls[].duration | 通话时长(秒) |
| calls[].intention | AI 判断意向,见下方枚举表 |
| calls[].intentionRate | 意向评分(0-100) |
| calls[].summary | AI 生成的通话摘要 |
| calls[].recordingUrl | 通话录音地址,有效期 7 天 |
| calls[].extractData | AI 从对话中提取的结构化数据(由话术配置决定字段) |
calls[].reached 枚举值(2026-08 更新,此前版本文档写的 REACHED/NO_ANSWER/BUSY/FAILED 已过时,请以下表为准;"平台展示"列是登录 Web 工作台查看通话记录时看到的中文文案,方便和客服对话时对齐口径):
| 值 | 平台展示 | 含义 | 是否接通 |
|---|---|---|---|
REACHED | 已接听 | 通话进行中的中间态(尚未挂断)。通话结束后正常会落到下面三个终态之一,查询接口里较少见到停留在这个值上的记录 | 是 |
AGENT_END | 已接通-AI挂断 | 接通后由 AI 挂断 | 是 |
SEAT_END | 已接通-坐席挂断 | 接通后转人工,由坐席挂断 | 是 |
USER_END | 已接通-客户挂断 | 接通后由用户(被叫)挂断 | 是 |
UNREACHED | 响铃-未接听 | 已振铃,但用户未接听 | 否 |
CALL_REJECTED | 响铃-用户拒接 | 用户主动拒接 | 否 |
EMPTY_PHONE | 未接通-空号 | 空号 | 否 |
SHUTDOWN | 未接通-空号或关机 | 号码关机或空号 | 否 |
UN_CONNECT | 未打通 | 呼叫未能建立连接 | 否 |
INVALID | 未接通 | 未归入以上任何具体原因的无效通话 | 否 |
CALLER_ABNORMAL | 未接通-主叫异常 | 我方线路/主叫侧异常导致未接通 | 否 |
INIT / PREPARE / DIALED | 初始化 / 准备拨打 / 已拨打 | 拨打中间态,还没有最终结果 | - |
判断"是否接通"建议用
reached in [REACHED, AGENT_END, SEAT_END, USER_END],或者用duration > 0兜底。如果您同时登录了平台 Web 工作台查看通话记录,会看到"已接通"/"未接通"两个汇总筛选项——"已接通"就是上表"是否接通=是"的四个值汇总;"未接通"是除这四个值之外的全部其余状态(含
INIT/PREPARE/DIALED这类还没结果的中间态),比 API 语境里习惯理解的"未接通"范围更宽,两边对数据时注意口径差异。
calls[].intention 枚举值:NONE=未分析或不适用,REACHED=已接通但尚未分类,HIGH=高意向,HESITATE=犹豫,LOW_DIFFICULTY/HIGH_DIFFICULTY=意向分类(原文档 LOW 已废弃,这两个新值的具体业务含义正在和研发确认,暂按字面理解,后续版本会补充准确释义),UNKNOW=未知。
通话刚挂断时
intention可能短暂返回UNKNOW(AI 需要基于转写文本做后置分析,有几秒延迟),过几秒重查即可拿到最终结果。
2.6 拨打配置(可选)
GET /api/v1/external/programs/:programId/dialing-config — 查询当前配置(所需权限:READ)
PUT /api/v1/external/programs/:programId/dialing-config — 修改配置(字段均可选,只传需要修改的)(所需权限:CONTROL)
curl -X PUT "https://api.halfcall.cn/api/v1/external/programs/cmk.../dialing-config" \
-H "Authorization: Bearer sk_xxx" \
-H "Content-Type: application/json" \
-d '{
"maxConcurrency": 30,
"workTimeConfig": {
"callTimeSlots": [
{ "id": "slot-1", "start": 540, "end": 1080, "days": [1,2,3,4,5] }
]
}
}'
{
"code": 0,
"data": {
"programId": "cmk...",
"runState": "RUNNING",
"maxConcurrency": 30,
"priorityStrategy": "LIFO",
"workTimeConfig": {
"callTimeSlots": [
{ "id": "slot-1", "start": 540, "end": 1080, "days": [1,2,3,4,5] }
]
},
"blockStartTime": null,
"blockEndTime": null
}
}
字段说明:
| 字段 | 说明 |
|---|---|
| maxConcurrency | 最大并发通话数(1-500) |
| priorityStrategy | 线索拨打顺序:LIFO=后上传先拨(默认),FIFO=先上传先拨 |
| workTimeConfig.callTimeSlots[].start/end | 允许拨打的时间段,单位分钟(540=09:00,1080=18:00) |
| workTimeConfig.callTimeSlots[].days | 允许拨打的星期:1=周一,7=周日 |
| blockStartTime / blockEndTime | 绝对禁呼时段(分钟数),优先级高于 callTimeSlots |
2.7 批量查询线索
GET /api/v1/external/leads/batch(所需权限:READ)
相比 2.5(每次只能查一条),本接口支持一次查询多条线索,或者按项目 + 日期批量拉取当天的拨打记录,适合替代逐条轮询、批量核对"哪些线索还没接通"。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| threadIds | string | 与 programId 二选一 | 逗号分隔的线索 ID 列表,最多 100 个 |
| programId | string | 与 threadIds 二选一 | 项目 ID,配合 date 按天拉取该项目下所有线索 |
| date | string | - | 日期(YYYY-MM-DD),仅支持按单日过滤,暂不支持起止时间范围 |
| page | number | - | 页码,默认 1 |
| pageSize | number | - | 每页数量,默认 20,最大 100 |
# 方式1:按线索 ID 列表批量查询
curl "https://api.halfcall.cn/api/v1/external/leads/batch?threadIds=detail_1,detail_2,detail_3" \
-H "Authorization: Bearer sk_xxx"
# 方式2:按项目 + 日期拉取当天的线索
curl "https://api.halfcall.cn/api/v1/external/leads/batch?programId=cmk...&date=2026-08-20" \
-H "Authorization: Bearer sk_xxx"
{
"code": 0,
"data": {
"total": 128,
"page": 1,
"pageSize": 20,
"list": [
{
"threadDetailId": "detail_xxx",
"phone": "13800138000",
"programId": "cmk...",
"status": "COMPLETED",
"stage": 1,
"createdAt": "2026-08-20T09:00:00.000Z",
"updatedAt": "2026-08-20T09:03:00.000Z",
"latestCall": {
"callId": "call_xxx",
"reached": "USER_END",
"duration": 120,
"intention": "HIGH",
"intentionRate": 85,
"summary": "客户感兴趣,约好明天回电",
"createdAt": "2026-08-20T09:01:00.000Z"
}
}
]
}
}
响应字段说明:
| 字段 | 说明 |
|---|---|
| total | 符合条件的线索总数 |
| list[].status | 线索状态,同 2.5 的 status |
| list[].latestCall | 该线索最近一次通话的摘要(只有最新一次,不含完整通话历史;需要某条线索的完整 calls[] 请用 2.5 按 threadId 单条查询) |
目前该接口没有额外的调用频率限制,但仍建议按需拉取(比如按 date 增量轮询),避免一次性拉取过大范围的数据。
2.8 撤销线索
POST /api/v1/external/leads/cancel(所需权限:LEADS)
撤销尚未拨打(排队中)的线索,适合"线索已经推送,但情况有变不需要再打了"的场景(比如会议被取消)。只能撤销当前仍处于排队中的线索;已经开始拨打或已完成的线索不受影响,也不会报错。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| threadDetailId | string | 二选一 | 撤销单条线索(即 2.1/2.3 返回的线索 ID) |
| programId | string | 二选一 | 撤销该项目下当前全部排队中的线索(用于批量清空队列) |
# 撤销单条线索
curl -X POST "https://api.halfcall.cn/api/v1/external/leads/cancel" \
-H "Authorization: Bearer sk_xxx" \
-H "Content-Type: application/json" \
-d '{ "threadDetailId": "detail_xxx" }'
# 撤销某项目下全部排队中的线索
curl -X POST "https://api.halfcall.cn/api/v1/external/leads/cancel" \
-H "Authorization: Bearer sk_xxx" \
-H "Content-Type: application/json" \
-d '{ "programId": "cmk..." }'
{ "code": 0, "data": { "cancelled": 1 } }
响应字段说明:
| 字段 | 说明 |
|---|---|
| cancelled | 实际被撤销的线索数量 |
注意事项:
threadDetailId和programId只能二选一。- 按
threadDetailId撤销时,如果该线索已经开始拨打或已结束(不再是排队中),接口会返回409并说明当前状态,不会误撤正在进行或已完成的通话。 - 按
programId撤销时,只影响调用瞬间仍处于排队中的线索;如果撤销请求发出的同时某条线索恰好被自动拨号器拾取开始拨打,那一条会被跳过(不计入cancelled)。 - 撤销后的线索不会再出现在自动拨号队列里,但历史记录仍可通过 2.5/2.7 查询到(
status会变化)。