渠道 KYC 与开户
通过 LEGEND 渠道为企业客户完成 KYB、RFI 补件和法币账户开户。每一步附请求与响应示例。
本页给出企业客户在 LEGEND 渠道从 KYB 到开出法币账户的完整流程。每一步都有请求示例和响应示例。
- 当前只支持
X-Channel-Provider: LEGEND。 - 返回的是客户在渠道的 KYC 结果,不是 Stabo 平台的 KYC 结果。
- 接口是渠道直通。Stabo 只做组装和安全所需的最小校验。其余业务规则由 LEGEND 判定,被拒时返回
openapi.channel.rejected。
接口参数和字段定义见 API 参考。法币账户的渠道差异(含 GLDB)见 Payment 法币账户。
全流程
| 步骤 | 接口或事件 | 是否必要 | 说明 |
|---|---|---|---|
| 1 | GET /openapi/v1/customers/provider-kyc/requirements | 必要 | 取国家、选项和文件要求。字典会变,提交前取最新值。 |
| 2 | POST /openapi/v1/customers/provider-kyc | 必要 | 提交企业资料和文件。首次提交时在渠道建档。 |
| 3 | provider.kyc.submitted | 事件 | 资料已送审。 |
| 4 | provider.kyc.approved / provider.kyc.rejected / provider.kyc.action_required | 事件 | 审核结果。也可以调 GET /openapi/v1/customers/provider-kyc 查询。 |
| 5 | GET .../provider-kyc/rfis、GET .../rfis/{rfiId} | 条件必要 | 收到 action_required 后查待作答的 RFI。 |
| 6 | POST .../rfis/{rfiId}/questions/{questionId}/idv-session | 条件必要 | 只用于 IDV 题。 |
| 7 | POST .../rfis/{rfiId}/answers | 条件必要 | 一次提交整张 RFI 的答案。 |
| 8 | 取 providerCustomerId | 必要 | 从第 4 步的 approved 事件或 KYC 状态查询中读取。开户要用。 |
| 9 | GET /openapi/v1/fiat-accounts/opening-options | 可选 | 选开户路由,确认费用。 |
| 10 | POST /openapi/v1/fiat-accounts | 必要 | 开户。 |
| 11 | fiat.account.activated | 事件 | 账户可以收款。 |
| 12 | GET /openapi/v1/fiat-accounts/{accountId} | 必要 | 取账号等收款信息。Webhook 不带这些字段。 |
调用前提
获取 Token
按鉴权的通用流程获取 Token。请求头、参数和多语言示例见鉴权页。
curl -X POST "https://api.test.stabofx.com/oauth2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "X-Tenant-ID: your_tenant_id" \
-H "X-Tenant-Type: COMPANY" \
-d "grant_type=client_credentials&client_id=your_client_id&client_secret=your_client_secret&scope=company:TENANT:admin"响应示例:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 31535999
}expires_in 的单位为秒。有效期各环境不同,以响应为准。
请求头
| Header | 说明 | 是否必需 |
|---|---|---|
Authorization | Bearer {accessToken} | 是 |
X-Channel-Provider | 固定 LEGEND | 是 |
Idempotency-Key | 唯一请求标识,最长 128 字符 | 提交 KYB、提交 RFI 答案、创建法币账户必填 |
Content-Type | 提交 KYB 和 RFI 答案用 multipart/form-data。创建法币账户用 application/json | POST 请求必填 |
用 curl 的 -F 时不要手动设置 Content-Type。curl 会自动带上 boundary。
订阅 Webhook
provider.kyc.* 和 fiat.account.* 事件推送到你配置的 Webhook 端点。Webhook 端点需要先开通。请联系平台配置端点,再订阅这些事件。签名验证和重试规则见 Webhook。
查询提交要求
GET /openapi/v1/customers/provider-kyc/requirements
返回提交 KYB 所需的国家、选项和文件要求。label 按请求语言返回,只用于展示。提交时用 code。
customerUid可选。不传时,options有 8 个键。- 带
customerUid时,已在渠道建档的客户额外返回questionnaire.purposes,共 9 个键。purpose 选项按客户实时查询。 - 新客户还没有在渠道建档。带
customerUid也正常返回,但结果不含questionnaire.purposes。 - 新客户首次提交时,
questionnaire.purposes可以填参考值invest_trade、crypto_fiat。这不是完整列表,取值由 LEGEND 校验。 - 首次调用约 10 秒,之后命中缓存会更快。客户端超时建议不低于 60 秒。
不带 customerUid:
curl "https://api.test.stabofx.com/openapi/v1/customers/provider-kyc/requirements" \
-H "Authorization: Bearer {accessToken}" \
-H "X-Channel-Provider: LEGEND"带 customerUid:
curl "https://api.test.stabofx.com/openapi/v1/customers/provider-kyc/requirements?customerUid=cust_example_001" \
-H "Authorization: Bearer {accessToken}" \
-H "X-Channel-Provider: LEGEND"响应示例(带 customerUid,列表只保留几项):
{
"code": 200,
"message": "OK",
"traceId": "trace-uuid-string",
"data": {
"countries": [
{ "code": "SG", "label": "Singapore" },
{ "code": "HK", "label": "Hong Kong" }
// ... 约 191 项,已排除渠道禁止的国家
],
"options": {
"business.sourceOfFunds": [{ "code": "<code>", "label": "<label>" }],
"questionnaire.industry": [{ "code": "<code>", "label": "<label>" }],
"questionnaire.currency": [{ "code": "USD", "label": "<label>" }],
"questionnaire.annualRevenue": [{ "code": "<code>", "label": "<label>" }],
"questionnaire.transactionEstimates[].currency": [{ "code": "USD", "label": "<label>" }],
"questionnaire.transactionEstimates[].expectedMonthlyVolume": [{ "code": "<code>", "label": "<label>" }],
"questionnaire.transactionEstimates[].expectedMonthlyTransactionActivity": [{ "code": "<code>", "label": "<label>" }],
"documents[].category": [{ "code": "utility_bill", "label": "Utility bill" }],
"questionnaire.purposes": [
{ "code": "invest_trade", "label": "<label>" },
{ "code": "crypto_fiat", "label": "<label>" }
// ... 共 7 项;只有传 customerUid 时返回
]
},
"documents": [
{ "type": "INCORPORATION", "owner": "COMPANY", "required": true, "categoryRequired": false },
{ "type": "BUSINESS_ADDRESS", "owner": "COMPANY", "required": true, "categoryRequired": true },
{
"type": "AML_POLICY",
"owner": "COMPANY",
"required": false,
"requiredWhen": "business.regulatedFinancialInstitution=true",
"categoryRequired": false
},
{ "type": "RESIDENTIAL_ADDRESS", "owner": "REPRESENTATIVE", "required": true, "categoryRequired": true }
// ... 共 13 项
],
"fileConstraints": {
"maxBytes": 2097152,
"allowedContentTypes": ["application/pdf", "image/jpeg", "image/png"]
}
}
}字段说明:
| 字段 | 说明 |
|---|---|
countries | 渠道接受的国家,已排除禁止国家。所有国家字段都填 code。 |
options | 按提交请求的字段路径分组的可选值。键即字段路径,如 questionnaire.industry 对应 data.questionnaire.industry。 |
options["documents[].category"] | 文件分类的参考值,当前只有 utility_bill。这是已知被渠道接受的值,不是完整列表。其他值原样转给渠道,由渠道判定。 |
documents[] | 每类文件的要求。owner 为 COMPANY、REPRESENTATIVE 或 BENEFICIAL_OWNER。required 为 true 表示至少一份。requiredWhen 是 required 为 false 时的必填条件。categoryRequired 为 true 时该文件必须带 category。 |
fileConstraints | 所有文件的限制。单个文件不超过 maxBytes(2 MB)。类型限 PDF、JPEG、PNG。 |
同一人兼任代表人和受益所有人时,按两个角色的文件要求取并集。
提交 KYB
POST /openapi/v1/customers/provider-kyc
请求格式为 multipart/form-data:
data部分:JSON 格式的企业资料。可以作为普通表单字段,也可以作为 JSON 文件部分。- 文件部分:每个文件一个部分。部分名等于
data.documents[].clientFileId。 - 文件部分与
clientFileId必须一一对应。缺失、重复或多出未引用的文件部分都会被拒绝。
data 示例
尖括号中的值从 requirements 的 options 中取 code。示例中一人兼任代表人和受益所有人。
questionnaire.purposes 用 requirements 返回的值。新客户拿不到这项时,可以填参考值 invest_trade、crypto_fiat,由 LEGEND 校验。
{
"customerUid": "cust_example_001",
"company": {
"legalName": "Example Co Pte Ltd",
"registrationNumber": "202000000X",
"registrationCountry": "SG",
"incorporationDate": "2020-01-02",
"contactEmail": "ops@example.com",
"telegram": "@example_co",
"phone": { "callingCode": "+65", "number": "80000000" },
"registeredAddress": {
"country": "SG",
"state": "Singapore",
"city": "Singapore",
"postalCode": "000000",
"addressLine": "1 Example Street #01-01"
},
"businessAddress": {
"country": "SG",
"state": "Singapore",
"city": "Singapore",
"postalCode": "000000",
"addressLine": "1 Example Street #01-01"
}
},
"business": {
"bankCountry": "SG",
"regulatedFinancialInstitution": false,
"sourceOfFunds": ["<business.sourceOfFunds code>"],
"sourceOfFundsDescription": "Revenue from software services",
"customerCountries": ["SG", "HK"]
},
"questionnaire": {
"industry": "<questionnaire.industry code>",
"operatingCountries": ["SG"],
"description": "B2B software services",
"currency": "USD",
"annualRevenue": "<questionnaire.annualRevenue code>",
"purposes": ["invest_trade", "crypto_fiat"],
"transactionEstimates": [
{
"currency": "USD",
"expectedMonthlyVolume": "<expectedMonthlyVolume code>",
"expectedMonthlyTransactionActivity": "<expectedMonthlyTransactionActivity code>"
}
],
"anyDealing": false
},
"persons": [
{
"personId": "p1",
"roles": ["REPRESENTATIVE", "BENEFICIAL_OWNER"],
"firstName": "Alex",
"lastName": "Example",
"position": "Director",
"dateOfBirth": "1990-01-02",
"birthCountry": "SG",
"nationalities": ["SG"],
"email": "ops@example.com",
"phone": { "callingCode": "+65", "number": "80000000", "country": "SG" },
"residentialAddress": {
"country": "SG",
"state": "Singapore",
"city": "Singapore",
"postalCode": "000000",
"addressLine": "2 Example Road #02-02"
},
"criticalRole": true,
"politicallyExposed": false,
"ownershipPercentage": 100
}
],
"documents": [
{ "clientFileId": "incorporation", "type": "INCORPORATION" },
{ "clientFileId": "constitution", "type": "CONSTITUTION" },
{ "clientFileId": "ownership-chart", "type": "OWNERSHIP_CHART" },
{ "clientFileId": "business-address", "type": "BUSINESS_ADDRESS", "category": "utility_bill" },
{ "clientFileId": "board-resolution", "type": "BOARD_RESOLUTION" },
{ "clientFileId": "source-of-funds", "type": "SOURCE_OF_FUNDS" },
{ "clientFileId": "p1-id", "type": "IDENTITY_DOCUMENT", "personId": "p1" },
{ "clientFileId": "p1-selfie", "type": "SELFIE", "personId": "p1" },
{ "clientFileId": "p1-address", "type": "RESIDENTIAL_ADDRESS", "personId": "p1", "category": "utility_bill" }
]
}| 对象 | 说明 |
|---|---|
customerUid | 调用方为客户定义的 UID,最长 128 字符。首次提交时在渠道建档。后续查询、RFI 都用它。 |
company | 企业信息。phone、registeredAddress、businessAddress 必填。注册国为 US 时 taxNumber 必填。 |
business | 经营与银行信息。受监管金融机构(regulatedFinancialInstitution 为 true)要填 policy,并上传 AML_POLICY 文件。 |
questionnaire | 问卷。transactionEstimates[].currency 不能重复。 |
persons | 恰好一位代表人,至少一位受益所有人。一人可以兼任。personId 在本次请求内唯一。受益所有人要填 ownershipPercentage。 |
documents | 文件声明。clientFileId 只能含字母、数字、下划线和短横线,最长 128 字符。人员文件(IDENTITY_DOCUMENT、SELFIE、RESIDENTIAL_ADDRESS)的 personId 必须指向 persons 中的人员。企业文件不填 personId。 |
字段长度和取值规则见 API 参考。
请求示例
curl -X POST "https://api.test.stabofx.com/openapi/v1/customers/provider-kyc" \
-H "Authorization: Bearer {accessToken}" \
-H "X-Channel-Provider: LEGEND" \
-H "Idempotency-Key: 5f0c7b2e-1d3a-4c8e-9b6f-2a4d6e8f0a1c" \
-F "data=@./kyb.json;type=application/json" \
-F "incorporation=@./incorporation.pdf" \
-F "constitution=@./constitution.pdf" \
-F "ownership-chart=@./ownership-chart.pdf" \
-F "business-address=@./business-address.pdf" \
-F "board-resolution=@./board-resolution.pdf" \
-F "source-of-funds=@./source-of-funds.pdf" \
-F "p1-id=@./p1-id.jpg" \
-F "p1-selfie=@./p1-selfie.jpg" \
-F "p1-address=@./p1-address.pdf"提交耗时视文件数量,可能超过 30 秒。客户端超时建议不低于 60 秒。超时后按重复提交与重试处理。
响应示例
本次送审成功:
{
"code": 200,
"message": "OK",
"traceId": "trace-uuid-string",
"data": {
"customerUid": "cust_example_001",
"status": "SUBMITTED",
"submittedAt": "2026-11-09T17:36:21.687+08:00"
}
}渠道已受理过(重复提交):
{
"code": 200,
"message": "OK",
"traceId": "trace-uuid-string",
"data": {
"customerUid": "cust_example_001",
"providerCustomerId": "usr_xxx",
"status": "APPROVED"
}
}submittedAt 为 ISO 8601 格式,带时区偏移(如 +08:00),小数位数不固定。请按 ISO 8601 标准解析,不要按固定格式截取。
重复提交与重试
- 每次提交先查渠道状态。渠道已受理(
SUBMITTED或APPROVED)时,直接返回当前状态,不写资料,也不再送审。这时响应不带submittedAt。 - 超时或没收到响应时,用同一份资料和原
Idempotency-Key重试即可。如果上次已送审,重试会返回当前状态。 Idempotency-Key必填,用于审计关联。LEGEND 的 KYB 写接口不收幂等键。重复提交的安全由上面的状态检查保证。- 渠道状态不允许修改资料时,返回
provider_kyc.not_editable。先查询 KYC 状态。 providerCustomerId在SUBMITTED阶段可能为空。审核通过(APPROVED)后才返回。
校验口径
本地只做组装和安全所需的最小校验:
- 组装渠道请求必需的对象和字段不能为空,如
company.phone、persons[].personId。错误码provider_kyc.field.required。 data缺失、不是合法 JSON 或含未知字段,返回provider_kyc.data.invalid。personId或clientFileId重复,返回provider_kyc.field.duplicate。人员文件指向不存在的人员,返回provider_kyc.field.invalid。- 文件部分与
clientFileId不对应,返回provider_kyc.file.missing或provider_kyc.file.unreferenced。 - 单个文件超过 2 MB,返回
provider_kyc.file.too_large。文件头不是 PDF、PNG 或 JPEG,返回provider_kyc.file.type_unsupported。
字段格式、取值范围、人员角色数量、必备文件等规则由 LEGEND 判定。不符合时返回 openapi.channel.rejected。
查询 KYC 状态
GET /openapi/v1/customers/provider-kyc?customerUid=
curl "https://api.test.stabofx.com/openapi/v1/customers/provider-kyc?customerUid=cust_example_001" \
-H "Authorization: Bearer {accessToken}" \
-H "X-Channel-Provider: LEGEND"响应示例:
{
"code": 200,
"message": "OK",
"traceId": "trace-uuid-string",
"data": {
"customerUid": "cust_example_001",
"providerCustomerId": "usr_xxx",
"status": "APPROVED"
}
}| 状态 | 说明 | 调用方动作 |
|---|---|---|
PENDING | 渠道已建档,资料未提交或仍在填写 | 提交 KYB |
SUBMITTED | 已提交,渠道审核中 | 等待 Webhook |
APPROVED | 审核通过,可以开户 | 取 providerCustomerId 开户 |
REJECTED | 审核拒绝,原因见 failureReason | 按原因处理 |
ACTION_REQUIRED | 渠道要求补件或复核 | 查 RFI 并作答,或按渠道要求重新提交 KYB |
providerCustomerId是渠道分配的客户标识。SUBMITTED阶段可能为空,APPROVED后才返回。开户和查账户都要用它。failureReason只在REJECTED时返回。
KYC Webhook
渠道侧 KYC 状态变化时推送以下事件。PENDING 不推送。
| 事件 | 状态 |
|---|---|
provider.kyc.submitted | SUBMITTED |
provider.kyc.approved | APPROVED |
provider.kyc.rejected | REJECTED |
provider.kyc.action_required | ACTION_REQUIRED |
data.object 与查询 KYC 状态的 data 字段相同。渠道重投时同一状态可能推送多次。按 customerUid + status 去重。
provider.kyc.submitted:
{
"id": "evt_xxx",
"object": "event",
"type": "provider.kyc.submitted",
"created": 1762680981,
"data": {
"object": {
"customerUid": "cust_example_001",
"status": "SUBMITTED"
}
}
}provider.kyc.approved:
{
"id": "evt_xxx",
"object": "event",
"type": "provider.kyc.approved",
"created": 1762681981,
"data": {
"object": {
"status": "APPROVED",
"customerUid": "cust_example_001",
"providerCustomerId": "usr_xxx"
}
}
}provider.kyc.rejected:
{
"id": "evt_xxx",
"object": "event",
"type": "provider.kyc.rejected",
"created": 1762681981,
"data": {
"object": {
"customerUid": "cust_example_001",
"status": "REJECTED",
"failureReason": "<reason from the channel>"
}
}
}provider.kyc.action_required:
{
"id": "evt_xxx",
"object": "event",
"type": "provider.kyc.action_required",
"created": 1762681981,
"data": {
"object": {
"customerUid": "cust_example_001",
"providerCustomerId": "usr_xxx",
"status": "ACTION_REQUIRED"
}
}
}providerCustomerId 只在渠道已分配时出现。
RFI 补件
RFI(Request for Information)是渠道发起的补件请求。RFI 没有单独的 Webhook。收到 provider.kyc.action_required 后,查 ACTIVE 的 RFI。只有 ACTION_REQUIRED 状态的 RFI 可以作答。
| RFI 状态 | 说明 |
|---|---|
ACTION_REQUIRED | 等待作答 |
UNDER_REVIEW | 答案已受理,渠道审核中。受理不解除 restrictions |
APPROVED | 审核通过 |
REJECTED | 审核拒绝 |
EXPIRED | 已过期,未作答 |
CANCELLED | 渠道已取消 |
查询 RFI 列表
GET /openapi/v1/customers/provider-kyc/rfis?customerUid=&status=
status 取 ACTIVE(只返回待作答)或 ALL(含终态)。缺省为 ALL。列表不返回题目。
curl "https://api.test.stabofx.com/openapi/v1/customers/provider-kyc/rfis?customerUid=cust_example_001&status=ACTIVE" \
-H "Authorization: Bearer {accessToken}" \
-H "X-Channel-Provider: LEGEND"响应示例:
{
"code": 200,
"message": "OK",
"traceId": "trace-uuid-string",
"data": [
{
"rfiId": "rfc_xxx",
"customerUid": "cust_example_001",
"status": "ACTION_REQUIRED",
"restrictions": ["swap", "fiat_withdrawal"],
"expiresAt": "2026-11-20T00:00:00+08:00",
"updatedAt": "2026-11-10T10:00:00+08:00"
}
]
}restrictions 是 RFI 解决前渠道施加的限制。
查询 RFI 详情
GET /openapi/v1/customers/provider-kyc/rfis/{rfiId}?customerUid=
curl "https://api.test.stabofx.com/openapi/v1/customers/provider-kyc/rfis/rfc_xxx?customerUid=cust_example_001" \
-H "Authorization: Bearer {accessToken}" \
-H "X-Channel-Provider: LEGEND"响应示例(五种题型各一题):
{
"code": 200,
"message": "OK",
"traceId": "trace-uuid-string",
"data": {
"rfiId": "rfc_xxx",
"customerUid": "cust_example_001",
"status": "ACTION_REQUIRED",
"restrictions": ["swap", "fiat_withdrawal"],
"expiresAt": "2026-11-20T00:00:00+08:00",
"updatedAt": "2026-11-10T10:00:00+08:00",
"questions": [
{
"questionId": "rfq_text",
"replyType": "TEXT",
"questionText": "Describe the main business of the company.",
"required": true,
"maxLength": 500
},
{
"questionId": "rfq_doc",
"replyType": "DOC",
"questionText": "Upload the latest bank statement.",
"required": true,
"fileRequired": true,
"maxFiles": 3
},
{
"questionId": "rfq_text_doc",
"replyType": "TEXT_DOC",
"questionText": "Explain the source of the last large deposit and upload proof.",
"required": true,
"fileRequired": true,
"maxLength": 500,
"maxFiles": 2,
"rejectionReason": "The document is not readable."
},
{
"questionId": "rfq_checkbox",
"replyType": "CHECKBOX",
"questionText": "Select the expected payment purposes.",
"options": ["Supplier payments", "Payroll", "Others"],
"required": true
},
{
"questionId": "rfq_idv",
"replyType": "IDV",
"questionText": "Complete identity verification for the director.",
"required": true,
"idvStarted": false
}
]
}
}| 题型 | 作答字段 |
|---|---|
TEXT | text,不超过 maxLength |
DOC | clientFileIds,不超过 maxFiles |
TEXT_DOC | text 和 clientFileIds |
CHECKBOX | checkboxSelections,值与 options 原样一致。选了 Others 时填 othersDetails |
IDV | 先创建 IDV 会话完成核验,再只提交 questionId |
rejectionReason 只在上次答案被拒后出现。
创建 IDV 会话
POST /openapi/v1/customers/provider-kyc/rfis/{rfiId}/questions/{questionId}/idv-session?customerUid=
只用于 ACTION_REQUIRED 状态 RFI 中的 IDV 题。其他情况返回 provider_kyc.rfi.not_actionable 或 provider_kyc.rfi.question_not_idv。
curl -X POST "https://api.test.stabofx.com/openapi/v1/customers/provider-kyc/rfis/rfc_xxx/questions/rfq_idv/idv-session?customerUid=cust_example_001" \
-H "Authorization: Bearer {accessToken}" \
-H "X-Channel-Provider: LEGEND"响应示例:
{
"code": 200,
"message": "OK",
"traceId": "trace-uuid-string",
"data": {
"url": "https://idv.example.com/session/xxx"
}
}url 是一次性会话链接。只交给完成核验的人员,在该人员的设备上打开。不要记录或转发。核验完成后,仍要随整张 RFI 提交这道题。
提交 RFI 答案
POST /openapi/v1/customers/provider-kyc/rfis/{rfiId}/answers
请求格式为 multipart/form-data:
data部分:JSON 格式的答案。一次提交整张 RFI,每道必答题一个答案。- 文件部分:部分名等于
answers[].clientFileIds中的值。所有题目的clientFileIds不能重复。 Idempotency-Key必填。重试同一次提交时复用原键。
data 示例(每种题型一个答案):
{
"customerUid": "cust_example_001",
"answers": [
{
"questionId": "rfq_text",
"text": "B2B software services for retail merchants."
},
{
"questionId": "rfq_doc",
"clientFileIds": ["bank-statement"]
},
{
"questionId": "rfq_text_doc",
"text": "Payment from a customer for an annual contract.",
"clientFileIds": ["contract", "invoice"]
},
{
"questionId": "rfq_checkbox",
"checkboxSelections": ["Supplier payments", "Others"],
"othersDetails": "Refunds to customers"
},
{
"questionId": "rfq_idv"
}
]
}请求示例:
curl -X POST "https://api.test.stabofx.com/openapi/v1/customers/provider-kyc/rfis/rfc_xxx/answers" \
-H "Authorization: Bearer {accessToken}" \
-H "X-Channel-Provider: LEGEND" \
-H "Idempotency-Key: 0b8e4c1a-6f2d-4e3b-a5c7-9d1e2f3a4b5c" \
-F "data=@./rfi-answers.json;type=application/json" \
-F "bank-statement=@./bank-statement.pdf" \
-F "contract=@./contract.pdf" \
-F "invoice=@./invoice.pdf"本次提交成功:
{
"code": 200,
"message": "OK",
"traceId": "trace-uuid-string",
"data": {
"rfiId": "rfc_xxx",
"status": "UNDER_REVIEW",
"submittedAt": "2026-11-10T10:00:00+08:00"
}
}RFI 已在审核中(重复提交),不写入,不带 submittedAt:
{
"code": 200,
"message": "OK",
"traceId": "trace-uuid-string",
"data": {
"rfiId": "rfc_xxx",
"status": "UNDER_REVIEW"
}
}- 受理不等于审核通过,也不解除
restrictions。审核结果以 KYC 状态和 RFI 状态为准。 - 本地只校验必需字段非空、
clientFileId安全字符、文件部分对应关系、文件大小和类型。题目归属、必答、长度、文件数、勾选值由 LEGEND 判定,不符合时返回openapi.channel.rejected。 - RFI 不是
ACTION_REQUIRED也不是UNDER_REVIEW时,返回provider_kyc.rfi.not_actionable。
查询开户选项
GET /openapi/v1/fiat-accounts/opening-options
KYC 为 APPROVED 后,用 providerCustomerId 查询。可选,用于选择开户路由或提前确认费用。
curl "https://api.test.stabofx.com/openapi/v1/fiat-accounts/opening-options?providerCustomerId=usr_xxx¤cy=SGD&bankCountry=SG" \
-H "Authorization: Bearer {accessToken}" \
-H "X-Channel-Provider: LEGEND"响应示例:
{
"code": 200,
"message": "OK",
"traceId": "trace-uuid-string",
"data": {
"options": [
{
"openingOptionId": "brt_xxx",
"currency": "SGD",
"bankCountry": "SG",
"partnerBanks": [
{ "bankCode": "DBS", "bankName": "DBS Bank" },
{ "bankCode": "SCB", "bankName": "Standard Chartered Bank" }
],
"transferMethods": {
"deposit": ["fast"],
"payout": ["fast", "swift"]
},
"feeSchedule": {
"accountOpenFee": "10.00",
"monthlyFee": "5.00",
"depositFee": { "rate": "0.001", "flat": "0.50", "min": "1.00", "max": "50.00" },
"payoutFee": { "rate": "0.001", "flat": "0.50", "min": "1.00" }
},
"nameMatchRequired": true
}
]
}
}currency 和 bankCountry 都是可选参数。两个都不传时,返回该客户的全部选项。
| 字段 | 说明 |
|---|---|
openingOptionId | 开户时传回,指定路由。 |
partnerBanks | 候选银行。实际开户银行以账户详情为准,可能不在候选中。例如测试环境中,选项列出 DBS 和 SCB,实际开在 Xfers(BANK_CODE 为 FAZZ)。 |
transferMethods | 入金和出金方式,保留渠道的方法名。 |
feeSchedule | 费用,保留渠道精度的字符串。字段缺失不代表费用为 0。 |
nameMatchRequired | 为 true 时,入金付款方的名称必须与户名一致。 |
创建法币账户
POST /openapi/v1/fiat-accounts
LEGEND 用 application/json。providerCustomerId 和 Idempotency-Key 必填。不传 openingOptionId 时由渠道选择路由。alias 可选,只能含字母、数字、空格和短横线,最长 50 字符。
开户请求可能较慢。客户端超时建议不低于 60 秒。
curl -X POST "https://api.test.stabofx.com/openapi/v1/fiat-accounts" \
-H "Authorization: Bearer {accessToken}" \
-H "X-Channel-Provider: LEGEND" \
-H "Idempotency-Key: 9c2d4e6f-8a0b-4c1d-b3e5-7f9a1b3c5d7e" \
-H "Content-Type: application/json" \
-d '{
"providerCustomerId": "usr_xxx",
"currency": "SGD",
"bankCountry": "SG",
"alias": "Main SGD Account",
"openingOptionId": "brt_xxx"
}'响应示例(同步开通):
{
"code": 200,
"message": "OK",
"traceId": "trace-uuid-string",
"data": {
"accountId": "bnk_xxx",
"providerCustomerId": "usr_xxx",
"currency": "SGD",
"status": "ACTIVE",
"alias": "Main SGD Account",
"bankCountry": "SG",
"bankName": "Xfers Pte Ltd",
"accountHolderName": "Example Co",
"accountNumber": "0000000000",
"routingCodes": [{ "type": "BANK_CODE", "value": "FAZZ" }],
"openedAt": "2026-11-09T10:06:32Z"
}
}- 返回
PENDING时,等 Webhook 或轮询账户详情。 - 返回
fiat.account.channel.detail_unavailable时,账户已创建。用消息里的账户 ID 查询,不要再次开户。 accountHolderName取自 KYB 的company.legalName。nameMatchRequired为true时,入金付款方的名称必须与户名一致。openedAt为 UTC 时间,以Z结尾。请按 ISO 8601 标准解析。- 超时或返回
openapi.channel.unavailable时,用原Idempotency-Key重试。重试几次仍失败时,停止重试,带上traceId联系技术支持,或换一个开户选项。 - 请求体出现未定义字段会被拒绝。GLDB 专属字段返回
fiat.account.field.unsupported。
法币账户 Webhook
账户进入终态时推送以下事件。PENDING 不推送。
| 事件 | 状态 |
|---|---|
fiat.account.activated | ACTIVE |
fiat.account.failed | FAILED |
fiat.account.closed | CLOSED |
Webhook 只带少量字段,不带账号和银行信息。收到后调用查询账户详情取收款信息。渠道重投时同一状态可能推送多次。按 accountId + status 去重。
fiat.account.activated:
{
"id": "evt_xxx",
"object": "event",
"type": "fiat.account.activated",
"created": 1762682792,
"data": {
"object": {
"status": "ACTIVE",
"currency": "SGD",
"accountId": "bnk_xxx",
"routingCodes": [],
"providerCustomerId": "usr_xxx"
}
}
}fiat.account.failed:
{
"id": "evt_xxx",
"object": "event",
"type": "fiat.account.failed",
"created": 1762682792,
"data": {
"object": {
"status": "FAILED",
"currency": "SGD",
"accountId": "bnk_xxx",
"routingCodes": [],
"providerCustomerId": "usr_xxx"
}
}
}fiat.account.closed:
{
"id": "evt_xxx",
"object": "event",
"type": "fiat.account.closed",
"created": 1762682792,
"data": {
"object": {
"status": "CLOSED",
"currency": "SGD",
"accountId": "bnk_xxx",
"routingCodes": [],
"providerCustomerId": "usr_xxx"
}
}
}providerCustomerId 可能缺失。
查询账户详情
GET /openapi/v1/fiat-accounts/{accountId}?providerCustomerId=
curl "https://api.test.stabofx.com/openapi/v1/fiat-accounts/bnk_xxx?providerCustomerId=usr_xxx" \
-H "Authorization: Bearer {accessToken}" \
-H "X-Channel-Provider: LEGEND"响应示例:
{
"code": 200,
"message": "OK",
"traceId": "trace-uuid-string",
"data": {
"accountId": "bnk_xxx",
"providerCustomerId": "usr_xxx",
"currency": "SGD",
"status": "ACTIVE",
"alias": "Main SGD Account",
"bankCountry": "SG",
"bankName": "Xfers Pte Ltd",
"accountHolderName": "Example Co",
"accountNumber": "0000000000",
"routingCodes": [{ "type": "BANK_CODE", "value": "FAZZ" }],
"openedAt": "2026-11-09T10:06:32Z"
}
}ACTIVE 至少带 accountNumber 或 iban 其中一项。把 bankName、accountNumber、routingCodes 等交给付款方。
错误响应
格式
业务错误返回 HTTP 400。响应体 code 固定为 500,data.errorCode 是错误码。程序按 data.errorCode 判断,message 只用于展示和排查。是否重试只看 errorCode,不看 HTTP 状态码。
{
"code": 500,
"message": "The channel rejected the request",
"traceId": "trace-uuid-string",
"data": {
"errorCode": "openapi.channel.rejected"
}
}参数错误
缺请求头、缺查询参数或缺 multipart 部分时,返回 openapi.param.missing。message 只写参数名。
{
"code": 500,
"message": "Required parameter X-Channel-Provider is missing",
"traceId": "trace-uuid-string",
"data": {
"errorCode": "openapi.param.missing"
}
}参数类型不匹配(如 status=FOO)返回 error.invalid.param。
渠道不可用
渠道超时、限流或网络异常时返回。稍后重试。创建类请求必须复用原 Idempotency-Key。
- 是否重试只看
errorCode,不看 HTTP 状态码。这类错误目前返回 HTTP 400。 - 用原 key 重试几次仍然失败时,停止重试。带上
traceId联系技术支持,或换一个开户选项。
{
"code": 500,
"message": "The channel is temporarily unavailable. Retry later; for create requests, reuse the original Idempotency-Key",
"traceId": "trace-uuid-string",
"data": {
"errorCode": "openapi.channel.unavailable"
}
}渠道拒绝
渠道确定性拒绝时返回。不要原样重试。按渠道规则修正资料后再提交。
{
"code": 500,
"message": "The channel rejected the request",
"traceId": "trace-uuid-string",
"data": {
"errorCode": "openapi.channel.rejected"
}
}错误码
| errorCode | 说明 | 处理方式 |
|---|---|---|
openapi.channel.unsupported | 渠道不支持该接口 | 检查 X-Channel-Provider |
openapi.param.missing | 缺少必填参数 | 补齐参数 |
error.invalid.param | 参数类型或枚举值不合法 | 修正参数 |
error.idempotent.key.missing | 缺少 Idempotency-Key | 补请求头 |
error.idempotent.key.too.long | Idempotency-Key 超过 128 字符 | 缩短 |
openapi.customer_uid.required | customerUid 为空 | 补齐 |
openapi.provider_customer_id.required | providerCustomerId 为空 | 补齐 |
provider_kyc.data.invalid | data 缺失、不是合法 JSON 或含未知字段 | 修正 data |
provider_kyc.field.required / provider_kyc.field.invalid / provider_kyc.field.too_long / provider_kyc.field.duplicate | 字段为空、不合法、超长或重复。message 带字段路径 | 修正对应字段 |
provider_kyc.file.missing / provider_kyc.file.unreferenced | 文件部分缺失或多出 | 让文件部分与 clientFileId 一一对应 |
provider_kyc.file.too_large / provider_kyc.file.type_unsupported | 文件超过 2 MB,或不是 PDF、PNG、JPEG | 更换文件 |
provider_kyc.account_type_unsupported | 该 customerUid 在渠道不是企业账户 | 换用新的 customerUid |
provider_kyc.not_editable | 渠道当前不允许修改资料 | 先查询 KYC 状态 |
provider_kyc.rfi.id_required | RFI ID 为空 | 补齐 |
provider_kyc.rfi.not_actionable | RFI 当前不需要作答 | 先查询 RFI 状态 |
provider_kyc.rfi.question_not_idv | 题目不是本 RFI 的 IDV 题 | 检查 questionId |
fiat.account.field.unsupported / fiat.account.field.required | 传了渠道不支持的字段,或缺本渠道必填字段 | 按渠道修正 |
fiat.account.opening_option_id.mismatch | openingOptionId 与币种或开户国家不一致 | 重新查询开户选项 |
fiat.account.channel.detail_unavailable | 账户已创建,详情暂不可用 | 按消息中的账户 ID 查询,不要再次开户 |
openapi.channel.rejected | 渠道拒绝 | 修正后再提交 |
openapi.channel.unavailable | 渠道暂时不可用 | 稍后用原 Idempotency-Key 重试。几次仍失败时停止重试,带 traceId 联系技术支持,或换开户选项 |
openapi.channel.invalid_response / openapi.channel.unknown_status | 渠道响应无效或状态无法识别 | 联系技术支持,提供 traceId |
已知限制
- 测试环境中,新批准的企业开 USD 账户(开户国家
BH,候选银行 SGB)会持续返回openapi.channel.unavailable。原因仍在排查。在此之前,可以先开 SGD 账户(开户国家SG)。遇到该错误时,请联系技术支持。