Z Zise Developers 简体中文
Account Center › Guides

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

LevelHow it is determinedTypically enables
0Nothing approvedReceiving funds, viewing balances, and internal transfers (depending on configuration)
1L1 profile approvedMost services, including cards, QR payments, wealth, exchange, and withdrawals
2L2 profile approved in addition to L1Services requiring more complete information, such as remittances

The field returned by GET /v1/kyc is level, not kyc_level.

⚠ Reading kyc_level gives undefined, and undefined >= 1 in JS is

false—the result is “nobody is verified,” without an error.

(kyc_level is 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_required at 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.

⚠ card uses 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:

FieldMeaning
kyc_trigger_amountUpgrade after the cumulative amount exceeds this value (omitted when no trigger exists)
kyc_trigger_levelLevel to upgrade to (1)

⚠ Looking only at required_level: 0 misses 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:

  1. GET /v1/kyc → read the current level
  2. GET /v1/kyc/requirements → read this service's required level (note the “only five” caveat above)
  3. 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 level will 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 level after 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:

SituationResult
Same person, same email, creates memberships under two merchantsSame identity. Completing L1 under your merchant will not be treated as a duplicate
Same person, different emailTwo 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/kyc remains none.

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 ≠ level 1. 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

Related Endpoints

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 pending returns state_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.