跳转到主要内容

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 的额度已用尽;不要盲目重试 — 请联系管理员充值或调整额度。
提交字段表记录 KYC_EVENT、KYB_EVENT、TRANSACTION_EVENT、WEB3_TRANSACTION_EVENT 和 MANUAL_CASE_EVENT,具体以租户开通情况为准。此列表不视为完整枚举:持续监控章节还使用 PERIODIC_FLOW_EVENT,其是否应列入提交枚举仍待工程团队确认。
customerId 契约:通用提交参考要求 data.customerId 必填,且同一客户保持稳定,以供 workflow.apply.query 恢复执行。Web3 字段表却将其标为可选。在工程团队确认 Web3 要求前,请在 Web3 请求中提供稳定的 customerId,不要依赖省略该字段的行为。

工作流 ID 位置:在 WIDTH One Platform 打开 Workflow,在工作流列表中复制工作流名称旁显示的 Workflow ID,并在使用 apiVersion 2.0.0 时通过 workflowId 传入。

响应格式

成功:

{
  "apiCode": 200,
  "apiMsg": "OK",
  "data": {}
}

错误:

{
  "apiCode": 999001,
  "apiMsg": "SIGN_ERROR",
  "data": null
}
分两层读。HTTP 状态码是传输层 — 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');
切勿在浏览器或移动端代码中暴露 apiSecret。请在您控制的服务器上签名请求。SDK 客户端使用会话级 sdkToken。

幂等性

所有节点数据提交接口均基于 callbackKey 幂等。重复提交相同 callbackKey 是安全的 — 仅首次成功提交生效,因此网络超时后可放心重试。

交互模型

工作流生命周期 API

工作流生命周期 API 是纯服务端对接路径:由你的后端发起一个事件、轮询执行状态与 Case(案件,一次审核的记录单元)审核结论,并在请求丢失或被重试时查询该客户是否已有进行中的执行。三个接口构成一个闭环。

它与 KYC / KYB 章节记录的节点驱动 SDK 路径不同:那里客户端逐个节点提交数据,这里由平台从头到尾跑完整个已发布工作流,你只需发起并轮询。

流程概览

  1. 发起
  2. 轮询状态
  3. 审核结论

workflow.apply.query 位于这个循环之前,作为恢复路径:当一次 submit 可能已经发生过(超时或上游重试)时先调用它,避免重复提交。

计费

API 用途 计费
workflow.apply.submit 发起工作流执行 是 — 按 eventType 扣对应额度
workflow.execution.status 按 executionId 查执行状态与 Case 结论 否
workflow.apply.query 查该客户是否已有执行 否
三个接口与其余 OpenAPI 接口共用同一网关基础地址、同一套 query-string 请求信封(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 } }
多审核组流程中,当前 Case 处理完后若整体流程仍在其他组/节点进行,caseStatus 为 OTHER_GROUP — 请继续轮询至 caseStatus 变为终态(APPROVED / REJECTED / CLOSED)。

轮询退出条件

每次执行到达决策节点后都会生成 Case(人工审核 → UNASSIGNED / UNDER_REVIEW;自动通过/拒绝/关闭 → 直接产生终态 Case),因此:

caseStatus in {APPROVED, REJECTED, CLOSED}
OR status == FAILED
OR status == CONFIRMED_CANCELLATION_ENUM
轮询节奏 — 间隔 3–5 秒;进入人工处理阶段(caseStatus ∈ {UNASSIGNED, UNDER_REVIEW, RETURNED})可放宽至 30–60 秒。

恢复查询(防重 / 断点恢复)

接口: workflow.apply.query POST

按 eventType + source + customerId 查询该客户是否已有执行。只读 — 不触发新执行、不计费。使用 apiVersion 1.0.0。

请求

{
  "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 稳定性 — 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 命令是各接口的调用示例。执行 submit 前先检查恢复查询结果:APPROVED 表示不要重复提交;RUNNING 表示继续轮询状态。提交超时后先查询再重试,因为 submit 会计费。Python 示例运行前必须配置已由工程团队确认的取消状态枚举。

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)

常见问题

Q:submit 超时了,不确定是否已发起,怎么办?
不要直接重试 submit(会重复计费)。先调 workflow.apply.query:命中 RUNNING / APPROVED 则拿回 executionId 继续轮询;未命中再重新 submit。
Q:status 查不到刚提交的 execution(data 为空)?
确认与提交时使用同一个 apiKey 与 x-domain——接口按租户和 domain 校验归属,跨 domain 查询返回空 data。
Q:执行长时间停在 RUNNING / WAITING 是异常吗?
不一定。停在 review 节点等待人工处理时执行状态不会变为终态,此时 caseStatus 为 UNASSIGNED / UNDER_REVIEW / RETURNED。以 caseStatus 判断处理进度。
取消状态契约待确认:生命周期参考使用 CANCELED,KYC 参考使用 CANCELLED。确认取消后应停止轮询;部署依赖取消状态的代码前,请向 WIDTH 工程团队确认接口实际返回的枚举值。此处不将任一拼写认定为最终契约。

配置

在启动工作流前,获取平台配置数据(国家列表、证件类型、表单模板等)。

接口: 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
  }
}
S3 URI 是内部引用,非公开可访问的 URL,请勿直接通过此 URI 下载文件。
支持格式: PNG, JPG, JPEG, GIF, WEBP, TIFF, PDF — 大小限制: 20 MB
令牌过期 (HTTP 401)? 重新获取令牌后重试上传。

错误处理与重试策略

场景处理方式
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。
轮询间隔:自动处理时为 3–5 秒,人工处理时为 30–60 秒。OTHER_GROUP 时继续轮询,直至案件状态为 APPROVED、REJECTED 或 CLOSED;FAILED 或确认取消时停止。指南另有通用的 30 分钟轮询上限,与较长的人工审核存在冲突。超过该时长的停止策略待工程团队确认;不要将计时器到期解释为审批通过或拒绝。