渠道 KYC 与开户

通过 LEGEND 渠道为企业客户完成 KYB、RFI 补件和法币账户开户。每一步附请求与响应示例。

本页给出企业客户在 LEGEND 渠道从 KYB 到开出法币账户的完整流程。每一步都有请求示例和响应示例。

  • 当前只支持 X-Channel-Provider: LEGEND。
  • 返回的是客户在渠道的 KYC 结果,不是 Stabo 平台的 KYC 结果。
  • 接口是渠道直通。Stabo 只做组装和安全所需的最小校验。其余业务规则由 LEGEND 判定,被拒时返回 openapi.channel.rejected。

接口参数和字段定义见 API 参考。法币账户的渠道差异(含 GLDB)见 Payment 法币账户。

全流程

图表加载中…
步骤接口或事件是否必要说明
1GET /openapi/v1/customers/provider-kyc/requirements必要取国家、选项和文件要求。字典会变,提交前取最新值。
2POST /openapi/v1/customers/provider-kyc必要提交企业资料和文件。首次提交时在渠道建档。
3provider.kyc.submitted事件资料已送审。
4provider.kyc.approved / provider.kyc.rejected / provider.kyc.action_required事件审核结果。也可以调 GET /openapi/v1/customers/provider-kyc 查询。
5GET .../provider-kyc/rfis、GET .../rfis/{rfiId}条件必要收到 action_required 后查待作答的 RFI。
6POST .../rfis/{rfiId}/questions/{questionId}/idv-session条件必要只用于 IDV 题。
7POST .../rfis/{rfiId}/answers条件必要一次提交整张 RFI 的答案。
8取 providerCustomerId必要从第 4 步的 approved 事件或 KYC 状态查询中读取。开户要用。
9GET /openapi/v1/fiat-accounts/opening-options可选选开户路由,确认费用。
10POST /openapi/v1/fiat-accounts必要开户。
11fiat.account.activated事件账户可以收款。
12GET /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说明是否必需
AuthorizationBearer {accessToken}是
X-Channel-Provider固定 LEGEND是
Idempotency-Key唯一请求标识,最长 128 字符提交 KYB、提交 RFI 答案、创建法币账户必填
Content-Type提交 KYB 和 RFI 答案用 multipart/form-data。创建法币账户用 application/jsonPOST 请求必填

用 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.submittedSUBMITTED
provider.kyc.approvedAPPROVED
provider.kyc.rejectedREJECTED
provider.kyc.action_requiredACTION_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
      }
    ]
  }
}
题型作答字段
TEXTtext,不超过 maxLength
DOCclientFileIds,不超过 maxFiles
TEXT_DOCtext 和 clientFileIds
CHECKBOXcheckboxSelections,值与 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&currency=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.activatedACTIVE
fiat.account.failedFAILED
fiat.account.closedCLOSED

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.longIdempotency-Key 超过 128 字符缩短
openapi.customer_uid.requiredcustomerUid 为空补齐
openapi.provider_customer_id.requiredproviderCustomerId 为空补齐
provider_kyc.data.invaliddata 缺失、不是合法 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_requiredRFI ID 为空补齐
provider_kyc.rfi.not_actionableRFI 当前不需要作答先查询 RFI 状态
provider_kyc.rfi.question_not_idv题目不是本 RFI 的 IDV 题检查 questionId
fiat.account.field.unsupported / fiat.account.field.required传了渠道不支持的字段,或缺本渠道必填字段按渠道修正
fiat.account.opening_option_id.mismatchopeningOptionId 与币种或开户国家不一致重新查询开户选项
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)。遇到该错误时,请联系技术支持。

本页目录