错误与限制
HTTP 状态
| 状态 | code | 含义 | 建议 |
|---|---|---|---|
400 | 400006 | 请求体或参数格式校验失败(缺失必填字段/参数、类型转换失败、非法枚举) | 修正参数后再重试 |
401 | 400001 | 缺少请求头、时间戳超出 ±300 秒窗口、应用不存在、凭证无效(如轮换后已过 24 小时宽限期的旧凭证)、签名不匹配或 nonce 重复 | 检查签名原文、时钟和凭证;重试需换新 nonce |
403 | 400005 | 应用未处于 ACTIVE(未发布、审核中、已停用) | 检查应用状态,见应用生命周期 |
403 | 400007 | 缺少必需的 Scope | 见「Scope 与 403」 |
403 | 400008 | 业务守卫拒绝:资源不属于当前应用、机器人未安装到目标、fileKey 无效或已过期 | 检查资源归属与安装关系 |
403 | 400101 | 客服 V1:访客已停用 | 见「业务错误」 |
404 | 400100 / 400300 / 400401 | 客服 V1:访客不存在 / 会话不存在或关闭失败 / 客服账号不存在 | 见「业务错误」 |
406 | 406001 | 请求头 Accept 无法被满足(接口只返回 application/json) | 声明 Accept: application/json |
409 | 400400 | 客服 V1:分配客服失败 | 见「业务错误」 |
413 | 413001 | 请求体超过限制(普通请求约 256 KiB;机器人文件上传约 22 MiB) | 压缩或拆分请求 |
415 | 415001 | 请求体 Content-Type 不受支持(常见于 JSON 接口漏发 Content-Type: application/json) | 补上正确的 Content-Type |
429 | 429001 | 应用配额(每分钟/每日)或并发超限 | 稍后重试 |
500 | 500000 | 平台内部错误 | 稍后重试;持续失败请携带 reqNo 联系支持 |
注意三点:
Content-Type与接口要求不符时返回415+415001,不是400也不是500。最典型的踩坑:JSON 接口只发了X-Open-*签名头而漏发Content-Type: application/json,此时curl --data-binary(未显式指定该头)会默认发出application/x-www-form-urlencoded,于是命中415。拿到415请先查请求头,不要怀疑签名。响应
message形如:不支持的 Content-Type: application/x-www-form-urlencoded;charset=UTF-8,支持: [application/json, application/octet-stream, application/xml, application/*+json,text/plain, text/xml, application/x-www-form-urlencoded, application/*+xml,multipart/form-data, */*]⚠️ 前半句「不支持的 X」(X 是本次实际收到的类型)可信,后半句「支持: [...]」不可信:这份清单是服务端已注册的消息转换器全局能力(末尾还有
*/*兜底),会把刚刚判为不支持的类型原样列进去,与本次请求是否被接受无关。不要按它判断该改成什么,也不要在代码里解析它——本接口只接受Content-Type: application/json,直接补上即可。缺少必填的查询参数(如
GET /api/open/v1/agent/status不带agentId)由 Spring 参数绑定层拦截,返回400+400006,此时响应体为平台自有文案,message形如Required String parameter 'agentId' is not present。multipart 上传缺少必需分片(如
POST /api/open/v2/bot/files不带file字段)返回400+400006,message形如「缺少必需的上传分片: file」。
500+500000不一定是平台故障。 有一处业务判断也走通用兜底:更新卡片时version与当前版本不一致(乐观锁冲突),返回的是500+500000,message只有「服务内部错误,请稍后重试」,无法与真正的内部错误区分。见卡片消息 → 更新已发送的卡片。请求路径或 HTTP 方法写错时,返回的是容器默认的 404 页面(
{timestamp,status,error,path}),不是ApiResult结构。若解析code失败,先确认路由是否正确。
统一响应结构
开放 API(/api/open/v1/**、/api/open/v2/**)的失败响应统一为 ApiResult:
{
"code": "500000",
"message": "服务内部错误,请稍后重试",
"value": null,
"reqNo": "9f1c2d3e4a5b4c6d8e7f0a1b2c3d4e5f"
}
成功与否请以 code == "000000" 判定,不要依赖 message 的文案。失败响应中 value 为 null。reqNo 是排障的唯一凭据,应写入日志,联系支持时一并提供。
完整错误码
code | 默认 HTTP | 含义 |
|---|---|---|
400001 | 401 | 签名或凭证校验失败(缺请求头、时间戳超窗、应用不存在、凭证无效、签名不匹配、nonce 重复) |
400005 | 403 | 应用未激活(非 ACTIVE) |
400006 | 400 | 参数或请求体无效(含 multipart 请求缺少必需分片、上传文件超过 20 MiB) |
400007 | 403 | 缺少必需的 Scope |
400008 | 403 | 资源不属于当前应用,或业务守卫拒绝(如「机器人未安装到此目标」「fileKey 无效或已过期」) |
400100 | 404 | 客服 V1:访客不存在 |
400101 | 403 | 客服 V1:访客已禁用 |
400300 | 404 | 客服 V1:会话不存在,或关闭会话失败 |
400400 | 409 | 客服 V1:分配客服失败 |
400401 | 404 | 客服 V1:客服账号不存在 |
406001 | 406 | 请求头 Accept 无法被满足 |
413001 | 413 | 请求体超过限制 |
415001 | 415 | 请求体 Content-Type 不受支持 |
429001 | 429 | 应用配额或并发超限 |
500000 | 500 | 平台内部错误 |
400002(时间戳过期)、400003(nonce重复)、400004(IP 不在白名单)为服务端内部保留码,当前对外统一以400001返回,接入方无需单独处理。下列码在服务端有定义但不会作为响应
code出现,接入方不必为其编写分支:
400200(消息内容为空):空白content在参数绑定阶段就被@NotBlank拦成400+400006,轮不到业务层;400201(消息重复):重复消息走幂等成功路径,返回code 000000且isDuplicate: true;429002(访客消息频率超限):当前仅按应用维度限流,未启用访客维度的独立配额。
业务错误
客服 V1 的业务失败一律以 4xx + 业务码 返回,响应体仍是 ApiResult,成败判据是 code == "000000"。
| 接口 | 触发条件 | 实际返回 |
|---|---|---|
POST /api/open/v1/visitor/auth | 应用不存在 | 401 + 400001、message「应用不存在」 |
GET /api/open/v1/visitor/info | 访客不存在 | 404 + 400100、message「访客不存在」 |
POST /api/open/v1/message/send | 访客不存在 | 404 + 400100、message「访客不存在」 |
POST /api/open/v1/message/send、agent/message/send | 访客已停用(status 为 0) | 403 + 400101、message「访客已禁用」 |
POST /api/open/v1/agent/message/send | 客服账号不存在 | 404 + 400401、message「客服账号不存在」 |
POST /api/open/v1/session/create | 访客不存在 | 404 + 400100、message「访客不存在」 |
GET /api/open/v1/session/info | 会话不存在,或 sessionId 非数字 | 404 + 400300、message「会话不存在」 |
POST /api/open/v1/session/close | 会话不存在、不属于当前应用,或 sessionId 非数字 | 404 + 400300、message「关闭会话失败」 |
POST /api/open/v1/agent/assign | 会话不存在、不属于当前应用,或 sessionId 非数字 | 409 + 400400、message「分配客服失败」 |
| 各 JSON 接口 | 请求体必填字段缺失或为空 | 400 + 400006,message 为字段级文案(如「消息内容不能为空」) |
| 各接口 | 必填查询参数缺失或为空 | 400 + 400006,message 形如 Required String parameter 'agentId' is not present |
sessionId传非数字(如"abc")不会返回400006:服务层把它当作「查不到该会话」处理,落到上表对应的404/409。
以下三种「查无此人」不是失败,返回 HTTP 200 + code 000000 的成功信封,用响应字段区分:
| 接口 | 触发条件 | 实际返回 |
|---|---|---|
GET /api/open/v1/agent/status | 客服不存在 | code 000000、exists:false、online:false、agentName:null |
GET /api/open/v1/message/list | 访客不存在 | code 000000、records 为空数组、total:0 |
POST /api/open/v1/message/send、agent/message/send | thirdMsgId 重复 | code 000000、isDuplicate:true |
控制台接口(/api/open/console/**)是另一套契约:凭证错误或缺参返回 Spring 默认错误体({timestamp,status,error,message,path}),不是 ApiResult,见客服开通接口。
与旧版契约的差异(破坏性变更)
客服 V1 此前把所有业务失败都压在 HTTP 200 上:控制器 return 的业务码(400100、400300、400400)是 200 + 业务 code,服务层 throw 出的失败被兜底成 200 + code 500000(业务码如 400101 被吞掉,不出现在响应里)。现在两者都改为对应的 4xx + 真实业务码,只有上表「查无此人」三种情形仍走 200。
已在联调的接入方需要相应调整:
- 判据统一为
code == "000000",HTTP 状态码只作辅助。 原先「HTTP200即成功」的判断会漏掉失败;原先依赖「500000即平台故障」做退避告警的逻辑,现在会收到400/401/403/404/409。 - 4xx 不要盲目重试。 上表的失败几乎都是前置条件不满足(访客未开通、访客被停用、会话不属于本应用),应先满足前置条件再重发;只有
500000与429001适合退避重试。 agent/status与message/list的行为未变,仍是200+code 000000,不要为它们改判定逻辑。
Scope 与 403
缺少 Scope 时返回 403,code 为 400007:
{
"code": "400007",
"message": "所需的开放平台Scope未被授权",
"value": null,
"reqNo": "9f1c2d3e4a5b4c6d8e7f0a1b2c3d4e5f"
}
接口 ↔ 必需 Scope 完整映射
客服 V1
| 接口 | 必需 Scope |
|---|---|
POST /api/open/v1/visitor/auth | customer.visitor.manage |
GET /api/open/v1/visitor/info | customer.visitor.manage |
POST /api/open/v1/message/send | customer.message.send |
GET /api/open/v1/message/list | customer.message.read |
POST /api/open/v1/agent/message/send | customer.message.send |
POST /api/open/v1/session/create | customer.session.manage |
POST /api/open/v1/session/close | customer.session.manage |
GET /api/open/v1/session/info | customer.session.manage |
POST /api/open/v1/agent/assign | customer.session.manage |
GET /api/open/v1/agent/status | customer.agent.read |
机器人 V2
| 接口 | 必需 Scope |
|---|---|
POST /api/open/v2/bot/files | bot.message.send |
POST /api/open/v2/bot/messages | bot.message.send |
POST /api/open/v2/bot/messages/{messageId}/reply | bot.message.reply |
PUT /api/open/v2/bot/messages/{messageId}/card | bot.message.send |
DELETE /api/open/v2/bot/messages/{messageId}/card | bot.message.send |
GET /api/open/v2/bot/apps | bot.installation.read |
GET /api/open/v2/bot/installations | bot.installation.read |
小程序 V2
| 接口 | 必需 Scope |
|---|---|
POST /api/open/v2/miniapp/session | 无(仅校验应用签名) |
事件订阅
事件不通过接口 Scope 控制,而是在订阅时单独申请下列 Scope,订阅成功且授权后平台才会投递对应事件:
| Scope | 订阅的事件类型 |
|---|---|
event.bot.message | im.message.receive_v1 |
event.customer.message | 客服消息事件 |
event.customer.session | 客服会话事件 |
event.customer.agent | 客服状态事件 |
card.interaction(卡片交互)不通过 Scope 控制:只要卡片消息由本应用发送并完成绑定,用户交互就会投递,无需申请额外 Scope。
Scope 说明
| Scope | 名称 | 默认授予 | 风险级 |
|---|---|---|---|
customer.agent.read | 读取客服状态 | ✅ | 低 |
bot.profile.read | 读取机器人资料 | ✅ | 低 |
bot.installation.read | 读取机器人安装 | ✅ | 低 |
customer.visitor.manage | 管理客服访客 | ❌ | 高 |
customer.session.manage | 管理客服会话 | ❌ | 高 |
customer.message.send | 发送客服消息 | ❌ | 高 |
customer.message.read | 读取客服消息 | ❌ | 高 |
bot.message.send | 发送机器人消息 | ❌ | 高 |
bot.message.reply | 回复机器人消息 | ❌ | 高 |
event.bot.message | 订阅机器人消息 | ❌ | 高 |
event.customer.message | 订阅客服消息事件 | ❌ | 高 |
event.customer.session | 订阅客服会话事件 | ❌ | 高 |
event.customer.agent | 订阅客服状态事件 | ❌ | 低 |
Scope 需按应用类型配置:机器人应用的能力(如
bot.message.reply)不会因给客服类型的应用授权而获得,反之亦然。bot.profile.read当前仅用于资料读取,未在接口层强制校验。
请求限制
配额与并发
平台在鉴权通过后按应用维度限流,超限返回 429 + 429001:
| 限制项 | 默认值 |
|---|---|
| 每分钟请求数 | 6000 |
| 每日请求数 | 100000 |
| 并发请求数 | 20 |
具体数值可能按应用单独配置,以控制台展示为准。收到 429 后请退避重试,并复用同一 clientMessageId/thirdMsgId 保证幂等。
请求体大小
| 场景 | 上限 | 超限返回 |
|---|---|---|
| 普通开放 API 请求体 | 约 256 KiB | 413 + 413001 |
POST /api/open/v2/bot/files(文件上传) | 约 22 MiB | 413 + 413001 |
| 单个上传文件(机器人) | 20 MiB | 400 + 400006 |
卡片 JSON(msgType=card) | 64 KiB | 400 + 400006 |
卡片结构限制
卡片消息在发送、更新时都会做结构校验,任一项不满足都返回 400 + 400006 并附具体原因:
| 项 | 上限 |
|---|---|
| 组件总数 | 200 |
| 卡片 JSON 体积 | 64 KiB |
| 组件嵌套层级 | body.elements 之下最多再嵌 8 层子数组(最深第 9 层) |
组件 tag | 必须在白名单内 |
嵌套层级按子数组层数计:column_set → columns → column → elements 一次消耗两级,
完整规则、组件白名单与全部错误文案见卡片消息。
时效限制
| 项 | 值 |
|---|---|
| 签名时间戳窗口 | ±300 秒 |
nonce 有效期 | 300 秒(签名校验通过即消费) |
机器人 fileKey 有效期 | 600 秒(10 分钟) |
小程序登录 code 有效期 | 120 秒(一次性) |
| 控制台 Access Token 有效期 | 7200 秒(2 小时) |