主题
错误处理与重试
两套错误结构
标准 REST 错误
/api/v1/channel/* 与 /api/v2/channel/* 使用 HTTP 状态码 + 统一 Body:
json
{
"code": "CHANNEL_ORDER_NOT_FOUND",
"message": "Channel order not found",
"details": {}
}code 是固定的字符串常量,程序里用它做分支判断;message 是给人看的,仅供排查。
校验失败时 details.errors 列出具体字段:
json
{
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": {
"errors": [
{ "type": "value_error", "loc": ["body"], "msg": "at least one collection type is required" }
]
}
}业务码信封
/api/channel/data/* 的 HTTP 状态码始终是 200,结果在 success / code 里。 见获取数据。
业务码
code | 含义 | 处理 |
|---|---|---|
0 / 200 | 成功 | —— |
60003 | 正在采集 | 15–30 秒后重试当前查询 |
60002 | 失败或无可交付数据 | 查询订单/产品的错误信息 |
10000 | 任务不存在 | 核对渠道编码与两类订单号 |
10001 | 参数或输出范围错误 | 按授权范围修正请求 |
60002 有两种含义
「采集失败」和「查询无数据」都是 60002,msg 会给出具体原因 (如「查询无数据」「税务数据未匹配到已配置表单」「本任务未申请 PDF 报告」)。 需要区分时回到产品查询看 state 与 error_code。
HTTP 状态码
| 状态 | 典型 code | 处理 |
|---|---|---|
| 401 | CHANNEL_AUTH_REQUIRED、INVALID_CHANNEL_KEY、CHANNEL_SIGNATURE_REQUIRED、INVALID_CHANNEL_SIGNATURE、CHANNEL_TIMESTAMP_EXPIRED | 核对认证头、时钟与 canonical,见鉴权与签名 |
| 403 | CHANNEL_ORDER_FORBIDDEN、REPORT_NOT_ENTITLED | 渠道与订单不匹配,或未获定制报告授权 |
| 404 | CHANNEL_ORDER_NOT_FOUND、PRODUCT_NOT_REQUESTED、INVOICE_ORDER_NOT_FOUND | 核对订单号;确认本单申请过该产品 |
| 409 | EXTERNAL_ORDER_CONFLICT、CHANNEL_REQUEST_REPLAYED、INVOICE_*_NOT_READY | 读 code 再决定,不要盲目重试 |
| 410 | RAW_ARTIFACT_EXPIRED、HTML_REPORT_EXPIRED | 资源已过期,重新生成或联系平台 |
| 422 | VALIDATION_ERROR、INVALID_PRODUCT、INVALID_CALLBACK_URL | 修正请求 |
发票交付专属错误
code | 含义 | 处理 |
|---|---|---|
INVOICE_DELIVERY_MODE_MISMATCH | 本单未申请该交付模式 | 用申请时选择的模式取数 |
INVOICE_DATASET_NOT_READY | 发票快照未就绪 | 等产品成功后重试 |
INVOICE_DATASET_EXPIRED | 快照已过期 | 联系平台 |
INVOICE_CURSOR_INVALID | 游标不属于本数据集 | 从头重新拉取 |
INVOICE_CURSOR_EXPIRED | 游标过期(24 小时) | 停止续拉并联系平台 |
INVOICE_EXPORT_NOT_FOUND | exportId 不属于本订单 | 重新查询 package/status |
INVOICE_EXPORT_NOT_READY | 分片包未就绪 | 继续查询 package/status |
INVOICE_EXPORT_EXPIRED | 分片包已过期 | 联系平台 |
重试决策
mermaid
flowchart TD
A[收到错误] --> B{哪一层}
B -->|HTTP 401| C[修认证, 不要重试]
B -->|HTTP 409| D[读 code]
D -->|*_NOT_READY| E[等一段时间再重试]
D -->|EXTERNAL_ORDER_CONFLICT| F[换订单号或改回原请求]
D -->|CHANNEL_REQUEST_REPLAYED| G[换新 Nonce 重发]
B -->|HTTP 422| H[修参数, 不要重试]
B -->|业务码 60003| E
B -->|业务码 60002| I[查产品状态定位原因]
B -->|业务码 10001| J[按授权范围修正]三条原则
| 场景 | 做法 |
|---|---|
PROCESS 或 60003 | 继续等待同一任务,不要重新创建授权 |
| 平台系统重试 | 沿用 authorization_code 和 job_id,attempt_id 会变 |
| 1.5.3 税局会话失效、需重新录入凭据 | 继续打开原 authorization_url 并使用 reenter=1;沿用 authorization_code / job_id,只滚动 attempt_id |
| 平台明确创建新授权代次 | authorization_code 会变,按新代次继续处理 |
不要因部分质量警告重建订单
state=SUCCESS 且 qualityStatus=WARNING / collectionResult=COMPLETED_WITH_WARNINGS 表示已有数据可用但覆盖不完整。保存 qualityIssues 与 dataQuality,按业务规则处理; 不要把它当成 60003 循环重试,也不要自动创建新的外部订单。
CONTACT_SUPPORT
遇到 CONTACT_SUPPORT 类错误码时停止自动重试, 保留错误码、external_order_id、job_id、attempt_id 联系平台。
哪些请求可以放心重试
重试安全(术语叫「幂等」)的意思是:同一个请求重发多次,结果和只发一次一样, 不会产生重复数据或重复动作。
| 接口 | 重试是否安全 | 说明 |
|---|---|---|
| 创建授权 | ✅ 请求体完全一致时 | 任一要素变化 → 409 |
| 订单/产品查询 | ✅ | 只读 |
| 数据接口(Key 模式) | ✅ | 只读 |
| 数据接口(签名模式) | ⚠️ 必须换新 Nonce | 复用 Nonce → 409 CHANNEL_REQUEST_REPLAYED |
| V2 游标 | ⚠️ 换新 Nonce;同一 cursor 可重复请求 | 入库时用 cursor 值去重 |
| V2 分片包状态 | ⚠️ 换新 Nonce;接口本身可安全重复调用 | 会触发/推进准备流程 |
| 文件下载 | ✅ | 中断后可从断点继续 |
建议的重试节奏
失败后不要立刻重试:先等一小段时间,每失败一次就把等待时间拉长(术语叫「指数退避」)。
| 错误类型 | 首次等待 | 上限 | 最多次数 |
|---|---|---|---|
60003 / *_NOT_READY | 20 秒 | 5 分钟 | 按业务超时(如 2 小时) |
| 5xx / 网络超时 | 2 秒起,每次翻倍 | 60 秒 | 5 |
| 401 / 422 | 不重试 | —— | —— |
轮询下限
状态查询间隔不得低于 15 秒。更密集的轮询不会让采集更快, 只会消耗渠道配额并可能触发限流。
建议留存的排查信息
无论走回调还是轮询,客户端都应把下面这些记录下来:
external_order_id、authorization_code、job_id、attempt_id- 每次请求的
X-Channel-Nonce与响应的code/message - 回调的
event_id与X-RPA-Delivery
排查时把这几项一起提供给平台,可以直接定位到具体采集尝试。