Verification levels determine which services a member can use. We make the decision; merchants may submit and update information through their server or use hosted pages.
KYC Overview and Levels
Three Levels
| Level | How it is determined | Typically enables |
|---|---|---|
| 0 | Nothing approved | Receiving funds, viewing balances, and internal transfers (depending on configuration) |
| 1 | L1 profile approved | Most services, including cards, QR payments, wealth, exchange, and withdrawals |
| 2 | L2 profile approved in addition to L1 | Services requiring more complete information, such as remittances |
The field returned by GET /v1/kyc is level, not kyc_level.
⚠ Reading
kyc_levelgivesundefined, andundefined >= 1in JS is
false—the result is “nobody is verified,” without an error.(
kyc_levelis the field on the member object; the two use different names.)
⚠ Use the stricter result: if L2 is approved but L1 is not (a data anomaly), the level is 0, not 1 or 2.
The rule is “no L1 approval means 0.” Doing otherwise would treat someone without L1 as verified.
Do Not Hard-Code Required Levels by Service
GET /v1/kyc/requirements
Returns the currently required level for each service. These can change when our configuration or yours changes, so:
⚠ A hard-coded table such as “cards require L1, remittance requires L2” becomes invalid the day requirements change—
your users may be blocked by your own frontend, or complete six screens only to receive
kyc_requiredat the final step.
Covers Nine Services
remittance.express · remittance.pobo · card · exchange · earn · withdraw · transfer.send · transfer.receive · qrpay
⚠ Sending and receiving transfers are separate rows. Combining them using the stricter rule incorrectly disqualifies members who only receive.
⚠
carduses the minimum among card products visible to you, not a constant 1—products can require L2.
Pay Particular Attention to QR Payments
Its gate is a cumulative trigger, not a level: members without L1 can keep paying until cumulative spending exceeds kyc_trigger_amount, at which point L1 is required.
Its required_level is therefore 0, with two additional fields:
| Field | Meaning |
|---|---|
kyc_trigger_amount | Upgrade after the cumulative amount exceeds this value (omitted when no trigger exists) |
kyc_trigger_level | Level to upgrade to (1) |
⚠ Looking only at
required_level: 0misses this condition—a user will suddenly be rejected on a later payment.Treating it as 1 overstates the requirement and blocks users who can currently pay. Use both numbers together.
This Table Does Not Tell You Whether a Service Is Available
It only tells you the required level. Availability—whether enabled or halted—is provided by GET /v1/merchant/lines. This endpoint still returns 0 when no configuration row exists.
What to Do After kyc_required
The error code does not tell you whether L1 or L2 is missing—six internal reasons (missing L1 / missing L2 × remittance / card issuance / exchange / wealth) are all mapped to the same public code, kyc_required.
Recovery always takes three steps:
GET /v1/kyc→ read the currentlevelGET /v1/kyc/requirements→ read this service's required level (note the “only five” caveat above)- Missing L1 →
POST /v1/kyc/applications; missing L2 →POST /v1/kyc/l2/applications, see the L2 page
⚠ Without the branch in step 3, your integration will
repeatedly issue L1 hosted-page links to someone missing L2. They can complete and pass each time,
yet
levelwill remain 1 forever.
Typical Integration Sequence
POST /v1/members → 建会员
↓ 会员去下单
拿到 kyc_required
↓
GET /v1/kyc/requirements → 得知这条线要 level 1
↓
POST /v1/kyc/sessions → 换一条托管屏链接,转给终端用户
↓ 他在我方的页面上填表 + 拍照
kyc.result.updated 事件 → 通过 / 驳回 / 待补件
For a swimlane diagram of this sequence—who does what, when, and where failures go—see Member Creation and the Complete KYC Flow.
A Member's KYC Result Does Not Carry Across Merchants
A person who has completed KYC under another merchant does not automatically have level 1 under yours.
The decision applies to this member record under your merchant. A newly created member points to no L1 profile, so after POST /v1/members, level is always 0, without exception.
⚠ Do not write “read
levelafter member creation and proceed if ≥1”—that branch will never be reached.
But Identity Is Shared Across Merchants, Which Creates a Real Failure Path
We identify the same person by email (case-insensitive). Therefore:
| Situation | Result |
|---|---|
| Same person, same email, creates memberships under two merchants | Same identity. Completing L1 under your merchant will not be treated as a duplicate |
| Same person, different email | Two identities. Their L1 ID number under your merchant conflicts with their own profile elsewhere → kyc_identity_taken |
⚠⚠ There is no manual override for
kyc_identity_taken. The end user fills in 15 fields,uploads three ID photos, receives a red error at submission, and this path is permanently closed to them.
Meanwhile, your side receives no signal: no event, and
GET /v1/kycremainsnone.You will keep waiting for a decision that never arrives.
There is only one preventive measure: create the member with the person's real, long-term email address,
not an alias you generated (such as
u88123@yourapp.com).
The same rule applies to the email itself: if it has already been used by a profile under another identity → kyc_email_taken, also a submission-time 400 with no manual override.
Show the Reason When Rejected
GET /v1/kyc includes reject_reason when status: "rejected"— plain language written by the reviewer, not an error code. The L2 equivalent is l2_reject_reason. The two levels have separate reasons, never merged (L1 approval with L2 rejection is common; combining them would hide which level's documents the user needs to correct).
Both are always empty strings when not rejected. Keeping an old rejection reason on an approved profile would make your interface continue asking an already-approved user to make corrections.
⚠ This is free text for direct display to the end user. Do not branch on its content—
a different phrasing by a reviewer would break your logic. An empty string may simply mean older data has no recorded reason;
handle that as “No reason provided.”
Update Information Directly: PATCH /v1/kyc
Merchants collect updated information on their own pages and call the endpoint from their server, without redirecting to a hosted page.
PATCH /v1/kyc
x-on-behalf-of: <会员 external_member_id 或 mem_uuid>
x-idempotency-key: <本次更新的唯一值>
Content-Type: application/json
{"profile":{"first_name":"Alex","occupation":"designer","address":"25 Main Street","phone_zone_number":"1","phone":"2025550123"}}
Names, sex, birth date, email, phone, ID type and validity, photos, residence type, addresses, and other information can be updated. ID number identity_number, nationality nationality, ID issuing country identity_issue_country, and residence country address_country cannot be changed; including them rejects the entire update.
Requires kyc:write, with the usual access token and request signature. Send only fields to change; omitted ID photos, addresses, and other fields are retained. Photo objects merge by subfield; residence permit URL arrays are replaced as a whole. Omitted fields keep their original values. Empty strings and arrays are explicit changes and still require validation; do not use null to mean unchanged. For the field list, see Update KYC Directly.
Success returns kyc_id, status: pending, review_status: UNREVIEWED, and level: 0. Approved profiles are also sent for review again, and corresponding card product KYC records return to pending review. Services requiring a verified level remain unavailable until the new review is approved.
This endpoint updates the identity profile; it does not directly synchronize contact information on existing cards or upstream cardholders. For an issuance application that failed because the phone number was already in use, update the information, wait for review approval, and submit a new application with a new idempotency key. The old application does not resume automatically. POST /v1/kyc/applications reuses an existing approved profile and cannot overwrite the phone number.
Empty updates, unknown fields, and invalid phone numbers are rejected. No existing profile returns not_found; Quick KYC profiles cannot be changed here. Concurrent edits return state_invalid; retry with a new idempotency key.
Hosted Pages Are an Optional Integration Method
If a hosted page is needed, POST /v1/kyc/profile-sessions still provides an update link. Merchant servers can use PATCH /v1/kyc directly without obtaining a link first. Use POST /v1/kyc/applications to create profiles and POST /v1/kyc/files to upload images. GET /v1/kyc still returns only results, not the complete profile or ID images.
Quick KYC Is Not Identity Verification
GET /v1/kyc/quick and the quick_kyc field on GET /v1/kyc describe an identity package from the platform pool exclusively bound to this member. For card issuance only.
⚠ A Quick KYC binding ≠
level1. Remittance, exchange, wealth, and withdrawals still check the member's own L1.Do not confuse this flow with
POST /v1/kyc/applications(which reuses the member's own approved profile and raises
level) or card “Quick Apply” (issuance from inventory without selection).
See Quick KYC.
Read Next
- L1: Hosted Page and Required Information
- Quick KYC
- L2: Advanced Verification Requirements
- Supplementary Documents
Related Endpoints
GET /v1/kycGET /v1/kyc/quickGET /v1/kyc/requirementsPOST /v1/kyc/sessionsPOST /v1/kyc/applicationsGET /v1/kyc/supplements
Submit L1 from Your Server: POST /v1/kyc/applications
Use this to submit one L1 profile for this member when collecting information yourself instead of using a hosted page. Review is per person, not per card BIN. Upload each ID photo through POST /v1/kyc/files, then place the returned private file_url in profile.identity_photos. Do not send your own object storage URLs or base64.
The body requires profile (with x-on-behalf-of). card_product_id is optional: including it only adds a product snapshot; we resolve the issuer and BIN from the product. Omit it to submit only the person's profile. Do not supply an issuer yourself.
POST /v1/kyc/applications 完整 profile(card_product_id 可省略)
↓
GET /v1/kyc pending 则等;rejected 则改资料再交
↓ status=approved / level=1
POST /v1/cards/applications 换 product_id 开第二款、第三款
⚠ After L1 approval, do not call this endpoint again for each BIN. Card issuance checks only whether the person is approved.
Submitting another product while the first profile remains
pendingreturnsstate_invalid.Repeating the same unfinished or approved product returns the existing application with
200; rejected applications may be submitted again.
After receiving pending, poll GET /v1/kyc (or wait for kyc.result.updated). Submission itself does not emit an event. Products requiring L2 still need a separate L2 flow, regardless of the number of BINs.