OpenAPI · REST 参考
REST 请求结构、身份验证、工作流生命周期、错误处理、配置与文件上传。
快速入门
基础地址
| 环境 | 网关 URL |
|---|---|
| 测试环境 | https://api-gateway-platform.width.com/gateway |
| 生产环境 | https://api-gateway.width.com/gateway |
请求格式
POST {base_url}?api={apiName}&apiVersion={apiVersion}&apiKey={apiKey}&sign={sign}
Content-Type: application/json
x-domain: {domainId}
{JSON body}
| 参数 | 位置 | 必填 | 描述 |
|---|---|---|---|
api |
查询参数 | 是 | API 名称 |
apiVersion |
查询参数 | 是 |
按接口而定 — workflow.apply.submit 推荐
2.0.0,其余用 1.0.0(见
工作流生命周期 API)
|
apiKey |
查询参数 | 是 | 您的 API key |
sign |
查询参数 | 是 | 请求体的 HMAC-SHA256 签名 |
x-domain |
请求头 | 是 | 您的业务 domain ID |
发起工作流
接口:
workflow.apply.submit
POST
按 eventType + source
路由到当前已发布(PUBLISHED)的工作流并发起执行。
apiVersion 版本
| apiVersion | 说明 |
|---|---|
1.0.0 |
按 eventType + source 自动路由
|
2.0.0 |
在 1.0.0 基础上支持可选
workflowId,显式指定要执行的已发布工作流;workflowId
为空时行为与 1.0.0 完全一致。推荐使用。
|
请求
{
"eventType": "KYC_EVENT",
"source": "API",
"data": {
"customerId": "demo-customer-001",
"fullName": "ZHANG SAN",
"dateOfBirth": "1990-01-01",
"nationality": "CN"
},
"workflowId": ""
}
| 字段 | 类型 | 必填 | 描述 |
|---|---|---|---|
eventType |
string | 是 |
事件类型:KYC_EVENT / KYB_EVENT /
TRANSACTION_EVENT /
WEB3_TRANSACTION_EVENT /
MANUAL_CASE_EVENT,以租户开通的事件为准。前两项在平台界面上显示为
KYC 和
KYB,那是界面显示名;接口里要传
KYC_EVENT 和 KYB_EVENT。MANUAL_CASE_EVENT
用于把主体提交做名单筛查,在平台界面上显示为 人工。
|
source |
string | 是 |
来源。服务端对接固定填 API。
|
data |
object | 是 |
业务数据。customerId
必填且必须对同一客户保持稳定(workflow.apply.query
依赖它做恢复查询);其余字段依工作流入口事件的字段定义。
|
workflowId |
string | 否 | 仅 2.0.0。指定已发布工作流 ID;为空则按事件自动路由。 |
响应
{
"apiCode": 200,
"apiMsg": "OK",
"data": {
"code": 200,
"message": null,
"executionId": "exec-7208f03b931e4897",
"status": "EXECUTING",
"requestId": "...",
"sync": false
}
}
| 字段 | 类型 | 描述 |
|---|---|---|
executionId |
string |
执行实例 ID。请务必持久化 — 后续用它轮询
workflow.execution.status。
|
status |
string |
初始状态(EXECUTING /
SUBMITTED)。
|
sync |
boolean | 是否同步执行;同步模式下 output 直接携带决策结果。 |
code |
int |
200 = 正常;403 = 额度不足(billing
blocked)。
|
submit 是唯一计费的接口。data.code 返回
403 表示该 eventType 的额度已用尽;不要盲目重试 —
请联系管理员充值或调整额度。
工作流 ID 位置:在 WIDTH One Platform 打开 Workflow,在工作流列表中复制工作流名称旁显示的 Workflow ID,并在使用 apiVersion 2.0.0 时通过 workflowId 传入。
响应格式
成功:
{
"apiCode": 200,
"apiMsg": "OK",
"data": {}
}错误:
{
"apiCode": 999001,
"apiMsg": "SIGN_ERROR",
"data": null
}
4xx/5xx 表示网关未受理请求(无
data)。HTTP 200 时再看
apiCode 判断网关结果(200 =
成功)。部分业务接口会在 data 下再包一层
{ code, data, msg } 业务信封 — 业务数据在
data.data(见注册客户示例)。
按顺序读取响应:先看 HTTP 状态,再看 apiCode;若接口使用业务信封,再看 data.code 和 data.data。仅 apiCode 为 200 不代表业务成功,仍须检查该接口的业务结果。
错误码
| apiCode | apiMsg | 描述 | 处理方式 |
|---|---|---|---|
| 200 | OK | 成功 | — |
| 999000 | GATEWAY_ERROR | 网关通用错误 | 联系技术支持 |
| 999001 | SIGN_ERROR | 签名验证失败 | 参见身份验证 |
| 999301 | API_INVALID_REQUEST | 请求格式错误 | 检查请求体 |
| 999302 | API_NOT_FOUND | API 名称未找到 |
检查 api 参数
|
| 999303 | API_NON_AUTHORIZED | 无此 API 权限 | 检查 apiKey 权限 |
| 999304 | RATE_LIMIT_TRY_AGAIN | 超出频率限制 | 等待后重试 |
| 999401 | SERVICE_TIME_OUT | 后端服务超时 | 退避重试 |
| 999402 | SERVICE_INVOKE_ERROR | 后端调用失败 | 重试;持续失败则联系技术支持 |
| 999403 | SERVICE_NOT_FOUND | 后端服务不可用 | 联系技术支持 |
| 999404 | SERVICE_RESPONSE_FORMAT_ERROR | 后端返回格式异常 | 联系技术支持 |
| 999501 | REQUEST_API_EXCEED_LIMIT | API 调用配额已用尽 | 联系技术支持提升配额 |
身份验证
签名计算
sign = HmacSHA256(apiSecret, requestBody)
密钥和请求体均使用 UTF-8 编码。结果为 64 位小写十六进制字符串。 签名必须基于 HTTP body 的原始字节计算。
代码示例
Java:
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(apiSecret.getBytes("UTF-8"), "HmacSHA256"));
byte[] hash = mac.doFinal(requestBody.getBytes("UTF-8"));
String sign = HexFormat.of().formatHex(hash); // Java 17+
Python:
import hmac, hashlib
sign = hmac.new(
api_secret.encode('utf-8'),
request_body.encode('utf-8'),
hashlib.sha256
).hexdigest()
Node.js:
const crypto = require('crypto');
const sign = crypto.createHmac('sha256', apiSecret)
.update(requestBody, 'utf8').digest('hex');
幂等性
所有节点数据提交接口均基于 callbackKey
幂等。重复提交相同 callbackKey 是安全的
— 仅首次成功提交生效,因此网络超时后可放心重试。
交互模型
-
客户端 (App/Web): 链式驱动流程。
前端由每次提交响应中返回的
nextStep对象驱动,无需全局轮询。仅对异步任务(如 OCR)轮询专用.data接口。 -
服务端 (租户后端): 最终状态轮询。
最后一个用户输入节点完成后,以宽松间隔(1–5 分钟)轮询
workflow.execution.status,直至达到终态。
工作流生命周期 API
工作流生命周期 API 是纯服务端对接路径:由你的后端发起一个事件、轮询执行状态与 Case(案件,一次审核的记录单元)审核结论,并在请求丢失或被重试时查询该客户是否已有进行中的执行。三个接口构成一个闭环。
它与 KYC / KYB 章节记录的节点驱动 SDK 路径不同:那里客户端逐个节点提交数据,这里由平台从头到尾跑完整个已发布工作流,你只需发起并轮询。
流程概览
- 发起
- 轮询状态
- 审核结论
workflow.apply.query 位于这个循环之前,作为恢复路径:当一次
submit
可能已经发生过(超时或上游重试)时先调用它,避免重复提交。
计费
| API | 用途 | 计费 |
|---|---|---|
workflow.apply.submit |
发起工作流执行 | 是 — 按 eventType 扣对应额度 |
workflow.execution.status |
按 executionId 查执行状态与 Case 结论 | 否 |
workflow.apply.query |
查该客户是否已有执行 | 否 |
api / apiVersion /
apiKey / sign)加 x-domain
请求头,以及同样的请求体 HMAC-SHA256 签名。基础地址(Staging
api-gateway-platform.width.com/gateway / 生产
api-gateway.width.com/gateway)与请求格式见
OpenAPI 参考 → 快速入门,完整签名规则见
身份验证
章节。
查询状态与审核结论
接口:
workflow.execution.status
POST
只读接口 — 不触发执行、不计费。executionId 按租户(及
x-domain)校验归属,无法跨租户查询。使用
apiVersion 1.0.0。
请求
{ "executionId": "exec-7208f03b931e4897" }
| 字段 | 类型 | 必填 | 描述 |
|---|---|---|---|
executionId |
string | 是 |
workflow.apply.submit 返回的执行实例 ID。
|
响应
{
"apiCode": 200,
"apiMsg": "OK",
"data": {
"executionId": "exec-7208f03b931e4897",
"status": "COMPLETED",
"caseNo": "C001443",
"caseStatus": "REJECTED",
"riskScore": 45.0,
"resolvedAt": 1784714818000
}
}
| 字段 | 类型 | 描述 |
|---|---|---|
status |
string |
执行状态:RUNNING / WAITING /
COMPLETED / FAILED /
取消状态值:待确认。
|
caseNo |
string |
关联 Case 编号(C%06d);尚未生成 Case
时为空。
|
caseStatus |
string |
Case 处理状态:UNASSIGNED(队列待分配)/
UNDER_REVIEW(审核员处理中)/
RETURNED(退回重做)/
OTHER_GROUP(本组已处理完,流程仍在其他组/节点进行)/
APPROVED / REJECTED /
CLOSED(已完成,流程结束);无 Case 时为空。轮询退出条件依据此字段。
|
riskScore |
number | 风险评分(0–100);无则为空。 |
resolvedAt |
long | 结案时间(epoch 毫秒);未结案为空。 |
currentNode 等
|
object |
SDK 流程控制用的内部字段(currentNode /
output /
pendingCallbackTask),服务端集成请忽略。
|
data 为空字符串时,表示
executionId 不存在,或不属于当前
apiKey 所在的租户/domain。
状态语义
status |
caseStatus |
含义 |
|---|---|---|
RUNNING / WAITING |
空 | 执行中(含等待信息采集),尚未生成 Case。 |
RUNNING / WAITING |
UNASSIGNED / UNDER_REVIEW /
RETURNED
|
执行停在 review 节点等待人工处理 — 继续轮询。 |
RUNNING / WAITING |
OTHER_GROUP |
当前 Case 所在组已处理完,流程仍在其他组/节点进行 — 继续轮询。 |
COMPLETED / 取消状态值:待确认 |
APPROVED / REJECTED / CLOSED
|
已出结论,流程结束。 |
FAILED |
— |
执行失败,请联系支持并提供 executionId。
|
各阶段响应示例
阶段 1 — 自动节点执行中,Case 尚未生成(到达决策节点前的短暂窗口):
{ "apiCode": 200, "apiMsg": "OK",
"data": { "executionId": "exec-xxxx", "status": "RUNNING" } }
无 caseStatus 字段 → 尚未生成 Case,继续轮询。
阶段 2 — Case 已生成,等待人工处理:
{ "apiCode": 200, "apiMsg": "OK",
"data": { "executionId": "exec-xxxx", "status": "RUNNING",
"caseNo": "C001286", "caseStatus": "UNASSIGNED", "riskScore": 49.17 } }
人工处理期间
caseStatus
反映进度:UNASSIGNED(队列待分配)→
UNDER_REVIEW(审核员领取后);退回重做时为
RETURNED。
阶段 3 — 已出结论(人工审核或自动决策):
{ "apiCode": 200, "apiMsg": "OK",
"data": { "executionId": "exec-xxxx", "status": "COMPLETED",
"caseNo": "C001443", "caseStatus": "REJECTED", "riskScore": 45.0,
"resolvedAt": 1784714818000 } }
caseStatus 为
OTHER_GROUP — 请继续轮询至
caseStatus 变为终态(APPROVED /
REJECTED / CLOSED)。
轮询退出条件
每次执行到达决策节点后都会生成 Case(人工审核 →
UNASSIGNED / UNDER_REVIEW;自动通过/拒绝/关闭
→ 直接产生终态 Case),因此:
caseStatus in {APPROVED, REJECTED, CLOSED}
OR status == FAILED
OR status == CONFIRMED_CANCELLATION_ENUMcaseStatus ∈
{UNASSIGNED, UNDER_REVIEW,
RETURNED})可放宽至 30–60 秒。
恢复查询(防重 / 断点恢复)
接口:
workflow.apply.query
POST
按 eventType + source + customerId
查询该客户是否已有执行。只读 — 不触发新执行、不计费。使用
apiVersion 1.0.0。
-
断点恢复 — 调用
submit后超时或宕机、未拿到executionId:先 query,命中则直接拿回executionId继续轮询,避免重复提交(submit 计费)。 -
提交防重 —
上游重试导致同一客户可能重复发起:query 命中
RUNNING/APPROVED时跳过submit。
请求
{
"eventType": "KYC_EVENT",
"source": "API",
"customerId": "demo-customer-001"
}
| 字段 | 类型 | 必填 | 描述 |
|---|---|---|---|
eventType |
string | 是 | 与 submit 时一致。 |
source |
string | 是 | 与 submit 时一致。 |
customerId |
string | 是 |
与 submit 时
data.customerId
一致(必须对同一客户保持稳定)。
|
响应
{
"apiCode": 200,
"apiMsg": "OK",
"data": {
"code": 200,
"executionId": "exec-7208f03b931e4897",
"status": "RUNNING"
}
}
| 命中情况 | executionId |
status |
建议动作 |
|---|---|---|---|
| 客户已审批通过 | 最近一次 COMPLETED 执行 | APPROVED |
不要重复提交。 |
| 存在进行中的执行 | 该执行 | RUNNING |
转轮询 workflow.execution.status。
|
| 未命中(被拒/无记录) | 空 | — |
可正常调用 submit。
|
customerId 必须与 submit 时
data.customerId
完全一致,且对同一客户长期稳定;否则恢复查询无法命中。
错误码
| 层级 | 码 | 说明 | 处理建议 |
|---|---|---|---|
网关 apiCode
|
200 |
成功(业务结果看 data.code)
|
— |
网关 apiCode
|
999302 |
API 名称错误 |
检查 api / apiVersion
|
网关 apiCode
|
999305 |
apiKey 无效或已停用
|
核对 API & Integration 页面的 API Key |
网关 apiCode
|
999001 |
签名错误 | 检查 sign 计算(body 原文签名、无多余换行) |
网关 apiCode
|
999401 |
服务超时 | 稍后重试;持续出现请联系支持 |
data.code |
400 |
参数缺失 | 检查请求 body |
data.code |
403 |
额度不足(仅 submit) | 联系管理员充值/调整额度 |
data 为空
|
— | execution 不存在或不属于当前租户/domain(仅 status) |
核对 executionId 与 apiKey /
x-domain;x-domain 为必填
header,请始终显式携带
|
代码示例
curl(完整闭环)
API_KEY="ak_xxx"
API_SECRET="sk_xxx"
DOMAIN_ID="{domainId}"
BASE_URL="https://api-gateway-platform.width.com/gateway"
# HMAC-SHA256 sign helper / 签名助手
sign() { printf "%s" "$1" | openssl dgst -sha256 -mac HMAC -macopt key:"$API_SECRET" | sed 's/^.* //'; }
# 0. (optional) recovery query — dedup before submit / 恢复查询防重
BODY='{"eventType":"KYC_EVENT","source":"API","customerId":"demo-customer-001"}'
curl -s -X POST "${BASE_URL}?api=workflow.apply.query&apiVersion=1.0.0&apiKey=${API_KEY}&sign=$(sign "$BODY")" \
-H "Content-Type: application/json" -H "x-domain: ${DOMAIN_ID}" -d "$BODY"
# 1. submit / 发起工作流
BODY='{"eventType":"KYC_EVENT","source":"API","data":{"customerId":"demo-customer-001","fullName":"ZHANG SAN"},"workflowId":""}'
curl -s -X POST "${BASE_URL}?api=workflow.apply.submit&apiVersion=2.0.0&apiKey=${API_KEY}&sign=$(sign "$BODY")" \
-H "Content-Type: application/json" -H "x-domain: ${DOMAIN_ID}" -d "$BODY"
# -> record data.executionId / 记下 data.executionId
# 2. poll status / 轮询执行状态
BODY='{"executionId":"exec-7208f03b931e4897"}'
curl -s -X POST "${BASE_URL}?api=workflow.execution.status&apiVersion=1.0.0&apiKey=${API_KEY}&sign=$(sign "$BODY")" \
-H "Content-Type: application/json" -H "x-domain: ${DOMAIN_ID}" -d "$BODY"
Python(轮询循环)
import hashlib, hmac, json, os, time, requests
GATEWAY = "https://api-gateway-platform.width.com/gateway"
API_KEY = os.environ["WIDTH_API_KEY"]
API_SECRET = os.environ["WIDTH_API_SECRET"]
DOMAIN_ID = os.environ["WIDTH_DOMAIN_ID"]
# Supply only the cancellation enum verified by WIDTH engineering.
# Do not guess between the conflicting spellings in the reference.
CANCELLED_STATUS = os.environ["WIDTH_CONFIRMED_CANCEL_STATUS"]
def call(api, version, payload):
body = json.dumps(payload, separators=(",", ":"))
signature = hmac.new(API_SECRET.encode(), body.encode(), hashlib.sha256).hexdigest()
response = requests.post(GATEWAY,
params={"api": api, "apiVersion": version, "apiKey": API_KEY, "sign": signature},
headers={"Content-Type": "application/json", "x-domain": DOMAIN_ID},
data=body, timeout=30)
response.raise_for_status()
result = response.json()
if result.get("apiCode") != 200:
raise RuntimeError(result)
data = result.get("data")
if isinstance(data, dict) and data.get("code", 200) != 200:
raise RuntimeError(data)
return data
identity = {"eventType": "KYC_EVENT", "source": "API", "customerId": "demo-customer-001"}
recovered = call("workflow.apply.query", "1.0.0", identity)
if recovered and recovered.get("status") == "APPROVED":
print(recovered)
else:
if recovered and recovered.get("status") == "RUNNING":
execution_id = recovered["executionId"]
else:
submitted = call("workflow.apply.submit", "2.0.0", {
"eventType": identity["eventType"], "source": identity["source"],
"data": {"customerId": identity["customerId"], "fullName": "ZHANG SAN"},
"workflowId": ""})
execution_id = submitted["executionId"]
# Persist execution_id before polling; recover after an interrupted submit.
while True:
state = call("workflow.execution.status", "1.0.0", {"executionId": execution_id})
if not isinstance(state, dict):
raise RuntimeError("Verify executionId, apiKey and x-domain; no execution returned")
if state.get("caseStatus") in ("APPROVED", "REJECTED", "CLOSED") or state.get("status") in ("FAILED", CANCELLED_STATUS):
print(state)
break
time.sleep(30 if state.get("caseStatus") in ("UNASSIGNED", "UNDER_REVIEW", "RETURNED") else 5)
常见问题
不要直接重试
submit(会重复计费)。先调
workflow.apply.query:命中 RUNNING /
APPROVED 则拿回
executionId 继续轮询;未命中再重新 submit。
data
为空)?确认与提交时使用同一个
apiKey 与
x-domain——接口按租户和 domain 校验归属,跨
domain 查询返回空 data。
不一定。停在 review 节点等待人工处理时执行状态不会变为终态,此时
caseStatus 为 UNASSIGNED /
UNDER_REVIEW / RETURNED。以
caseStatus 判断处理进度。
配置
在启动工作流前,获取平台配置数据(国家列表、证件类型、表单模板等)。
接口:
prepare.avira.config
POST
{
"keys": "particular,avira_ssic,avira_ssoc,avira_countries,avira_document_master,avira_document_default_rule,required-form,avira_phone_code,avira_entity_types,ownership_structure_layer,transaction_behaviour,product_service_complexity,source_of_wealth,avira_source_of_funds"
}
配置项
| Key | 描述 | 使用方 |
|---|---|---|
particular |
个人/企业信息字段定义 | Particular 节点 (KYC & KYB) |
required-form |
带验证规则的表单模板 | Required Forms 节点 |
avira_countries |
国家列表 (ISO 代码 + 名称) | 国籍、居住国等 |
avira_document_master |
各国支持的证件类型 | OCR 及证件上传 |
avira_document_default_rule |
默认证件要求 | 必传文件节点 |
avira_ssic |
行业分类代码 | 行业字段 |
avira_ssoc |
职业分类代码 | 职业字段 |
avira_phone_code |
国际区号 | 手机验证 |
avira_entity_types |
企业实体类型 | KYB 企业信息 |
ownership_structure_layer |
股权结构层级设置 | KYB 工作流 |
transaction_behaviour |
交易行为类别 | KYB 表单 |
product_service_complexity |
产品/服务复杂度类别 | KYB 表单 |
source_of_wealth |
财富来源选项 | KYC/KYB 表单 |
avira_source_of_funds |
资金来源选项 | KYC/KYB 表单 |
文件上传
需要 S3 URI 的接口(证件、照片等),需先通过此接口上传文件。
步骤 1 — 获取上传令牌
接口:
storage.upload.token
POST
请求体: {}。令牌有效期 15 分钟。
响应:
{
"apiCode": 200,
"data": {
"uploadToken": "a1b2c3d4e5f6...",
"expiresIn": 900
}
}
步骤 2 — 上传文件
存储服务地址:
| 环境 | URL |
|---|---|
| 测试环境 | https://api-gateway-platform.width.com/storage |
| 生产环境 | https://api-gateway.width.com/storage |
Multipart 上传:
curl -X POST "${STORAGE_URL}/api/v1/upload/file" \
-F "uploadToken=a1b2c3d4e5f6..." \
-F "file=@passport_front.jpg"
Base64 上传:
curl -X POST "${STORAGE_URL}/api/v1/upload" \
-H "Content-Type: application/json" \
-d '{
"uploadToken": "a1b2c3d4e5f6...",
"image": "data:image/jpeg;base64,/9j/4AAQ...",
"filename": "passport_front.jpg"
}'
响应:
{
"apiCode": 200,
"data": {
"url": "s3://workflow-kyc/tenantImages/2026-04-16/abc123.jpg",
"filename": "passport_front.jpg",
"content_type": "image/jpeg",
"size": 245760
}
}
支持格式: PNG, JPG, JPEG, GIF, WEBP, TIFF, PDF — 大小限制: 20 MB
错误处理与重试策略
| 场景 | 处理方式 |
|---|---|
| SIGN_ERROR / 999001 | 修复签名,不要在未修正时重试。 |
| 999304 | 等待限流恢复后重试。 |
| HTTP 500 / 999401 | 最多重试三次,按 1 秒、2 秒、4 秒指数退避;若 submit 可能已被受理,先用 workflow.apply.query 恢复。 |
| data.code = 403 | 请管理员充值或调整事件额度,不要盲目重试。 |
| 上传令牌 HTTP 401 | 获取新的上传令牌,再重试上传一次。 |
| OTP 验证 | 达到最大验证次数后重新发送 OTP。 |
| 节点提交超时 | 节点数据提交可使用相同 callbackKey 重试;不要将此保证推广至 workflow.apply.submit。 |