事半AI

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

参数类型必填说明
botIdstring-指定数字员工,不传则用项目第一个启用的数字员工
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

字段类型必填说明
fileFileExcel 或 CSV(.xls .xlsx .csv,最大 100MB)
botIdstring数字员工 ID,需与下载模板时一致
namestring-线索包名称,不传则用文件名
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 模板里看到的中文列名)。

参数类型必填说明
botIdstring-指定数字员工,不传则用项目 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

适合用户实时触发场景(如刚填写表单),立即入队等待外呼。

字段类型必填说明
programIdstring项目 ID
phonestring手机号
botIdstring-指定数字员工(用于按其字段定义做校验),不传则用项目 stage 最小的已启用数字员工
variablesobject-自定义参数,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已入队的手机号
messageLead 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

字段类型必填说明
actionstringstart=启动,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[].intentionAI 判断意向,见下方枚举表
calls[].intentionRate意向评分(0-100)
calls[].summaryAI 生成的通话摘要
calls[].recordingUrl通话录音地址,有效期 7 天
calls[].extractDataAI 从对话中提取的结构化数据(由话术配置决定字段)

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(每次只能查一条),本接口支持一次查询多条线索,或者按项目 + 日期批量拉取当天的拨打记录,适合替代逐条轮询、批量核对"哪些线索还没接通"。

参数类型必填说明
threadIdsstring与 programId 二选一逗号分隔的线索 ID 列表,最多 100 个
programIdstring与 threadIds 二选一项目 ID,配合 date 按天拉取该项目下所有线索
datestring-日期(YYYY-MM-DD),仅支持按单日过滤,暂不支持起止时间范围
pagenumber-页码,默认 1
pageSizenumber-每页数量,默认 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

撤销尚未拨打(排队中)的线索,适合"线索已经推送,但情况有变不需要再打了"的场景(比如会议被取消)。只能撤销当前仍处于排队中的线索;已经开始拨打或已完成的线索不受影响,也不会报错。

字段类型必填说明
threadDetailIdstring二选一撤销单条线索(即 2.1/2.3 返回的线索 ID)
programIdstring二选一撤销该项目下当前全部排队中的线索(用于批量清空队列)
# 撤销单条线索
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实际被撤销的线索数量

注意事项

  • threadDetailIdprogramId 只能二选一。
  • threadDetailId 撤销时,如果该线索已经开始拨打或已结束(不再是排队中),接口会返回 409 并说明当前状态,不会误撤正在进行或已完成的通话。
  • programId 撤销时,只影响调用瞬间仍处于排队中的线索;如果撤销请求发出的同时某条线索恰好被自动拨号器拾取开始拨打,那一条会被跳过(不计入 cancelled)。
  • 撤销后的线索不会再出现在自动拨号队列里,但历史记录仍可通过 2.5/2.7 查询到(status 会变化)。

接下来是什么?

以下是建议的下一步操作:

接入概述
外部系统对接事半AI平台的前置条件、认证方式、权限范围与错误码说明。
阅读指南
基础查询
获取项目列表、数字员工列表、线路状态、音色列表,以及创建项目、关联数字员工。
阅读指南
AI 呼入
通过 SIP Header 传入自定义参数,实现个性化 AI 呼入接入。
阅读指南