KYC China

KYC verification for mainland China citizens — identity, mobile, risk, and bank checks against Ministry of Public Security, telecom carrier, and banking records.

Single endpoint, smart fan-out

You send one request to POST /kyc/cn/request with whatever fields you have, then either receive the result via a webhook (pass callbackUrl in the request) or poll POST /kyc/cn/poll until the result is ready. The service inspects the request, picks the strongest verification variant on each axis (e.g. four-factor instead of two-factor when ID dates are present), and runs every eligible sub-check in parallel against the upstream sources.

The merged response tells you, per category, whether the person matched, plus an execution summary so you can tell at a glance which checks ran, which matched, which came back empty, and which errored.

What can be verified

Category
Verifies
Required upstream fields

Identity

Name + ID card pair belongs to a real registered citizen

name, idNumber

Identity (with validity)

As above, plus the issue/expiry dates match the on-file record

+ idIssueDate, idExpiryDate

Identity (face match)

As above, plus the supplied photo matches the photo on file

+ facePhoto

Identity-mobile

The mobile number is registered to that person at that ID

name, idNumber, mobile (any subset)

Mobile attribution

Carrier, registered province, and registered city of the SIM

mobile

Mobile location

Most-active work-hours city + most-active night-hours city (last 3 months)

mobile

Address verification

Distance segment (≤3km … >50km) between a supplied address and the mobile's common / work / residential area

mobile, addressesToVerify[]

Risk — fraud

Presence on fraud, gambling, and money-mule blacklists; severity 0-3

mobile or idNumber

Risk — criminal

Police-record flags: criminal offender, drug-related, fugitive, etc.

name, idNumber

Bank

A bank card belongs to the person identified by name + ID (+ mobile)

name, idNumber, bankCardNumber (+ optional mobile)

Data sources & residency

All upstream calls go through a Beijing-based proxy and query official sources: Ministry of Public Security (identity & criminal), telecom carriers (mobile & location), UnionPay member banks (bank verification), and the national anti-fraud blacklist (risk). No personal data is stored by Fill Easy after the response is returned.

Cost & rate limits

Each sub-check is a separate billable upstream call. A request with name, idNumber, mobile, and bankCardNumber fans out to 9 calls in parallel (worst case without addressesToVerify). Each entry in addressesToVerify adds one further call. To narrow what runs, simply omit the inputs you don't want to verify — every sub-check whose required fields are present always fires.

KYC China verification - Request

post

Verify a Chinese citizen's identity, mobile, risk profile, and/or bank card in a single request. You send whatever fields you have; the service decides which sub-checks to run, fans them out in parallel against the upstream sources (Ministry of Public Security, telecom carriers, banks, blacklists), and merges the results into one response.


How the request fans out

The endpoint runs every sub-check whose required inputs are present in your request, with two smart-selection rules that avoid paying for redundant calls:

  • On the identity-document axis, the strongest available variant runs and weaker ones are skipped. Specifically, identity.four-factor (which verifies the ID's validity dates in addition to name + ID) supersedes identity.two-factor when both idIssueDate and idExpiryDate are supplied.

  • On the identity-mobile axis, the strongest available variant runs. identity.three-factor (name + ID + mobile) supersedes identity.name-mobile and identity.id-mobile when all three fields are present.

  • On the bank axis, bank.four-factor (name + ID + card + mobile) supersedes bank.three-factor (name + ID + card) when mobile is supplied.

Other sub-checks run independently — face match (identity.image) is on a separate axis from document verification, mobile lookups, and risk checks.

Every sub-check whose required fields are present always runs. There is no opt-out — if you don't want a category to fire, simply don't supply its inputs.

Sub-check eligibility

Sub-check
Required fields
What it verifies

identity.two-factor

name, idNumber

Name + ID pair exists in MPS records

identity.four-factor

+ idIssueDate, idExpiryDate

As above, plus dates match the on-file record

identity.image

name, idNumber, facePhoto

Photo matches the MPS face on file

identity.name-mobile

name, mobile

Mobile is registered to that name

identity.id-mobile

idNumber, mobile

Mobile is registered to that ID

identity.three-factor

name, idNumber, mobile

All three are tied to one person

mobile.attribution

mobile

Carrier, registered province, city

mobile.location-work

mobile

Most-active city during 07:00-19:00 weekdays (last 3 months)

mobile.location-residential

mobile

Most-active city during 21:00-07:00 (last 3 months)

mobile.address-verify[N]

mobile + entry in addressesToVerify

Distance segment between the supplied address and the mobile's recorded common / work / residential area. One sub-check fires per entry, indexed [0], [1], …

risk.fraud

mobile or idNumber

Fraud / gambling / money-mule blacklist hits

risk.criminal

name, idNumber

Police record flags (criminal, drug, fugitive, etc.)

bank.three-factor

name, idNumber, bankCardNumber

Card belongs to that person

bank.four-factor

+ mobile

As above, plus mobile is the card's registered number


Reading the response

The response always contains a summary object. Each sub-category (identity, mobile, risk, bank) is present only if at least one of its sub-checks ran and returned data. Categories with only errored or not-found outcomes are omitted.

summary buckets

Every attempted sub-check appears in summary.ran. From there it lands in exactly one of these buckets:

Bucket
Meaning

matched

An identity- or bank-axis check returned match

unmatched

An identity- or bank-axis check returned no match (the call ran successfully; the answer was negative)

notFound

The upstream had no record for the input — informational lookups or blacklists routinely return this when nothing is on file

errored

The call itself failed (timeout, upstream service error, network). Carries { check, error } for diagnostics

Mobile lookups (mobile.attribution, mobile.location-*) and risk lookups (risk.fraud, risk.criminal) have no match/no-match semantics. They appear in ran and either notFound or errored — never in matched/unmatched.

Result semantics by category

  • Identity — array of { type, result, verification }. Treat match as a positive identity signal. no match from three-factor typically means the mobile isn't tied to that person; pair it with the result from two-factor / four-factor (also returned) to disambiguate document mismatch from mobile mismatch.

  • Mobile — flat object with whatever the lookups returned. Fields are absent when their underlying call returned no data; check summary.notFound to tell "no data available" from "we never asked".

  • Riskrisk.fraud returns a fixed list of four risk types (fraud, gambling operator, gambling player, money mule) each with severity None / Low / Medium / High. risk.criminal returns a flat boolean map. isNormal: null (rather than a boolean) means the check could not complete and the records are not authoritative.

  • Bank — single { type, result }. The type tells you which variant ran (four-factor if mobile was supplied alongside, otherwise three-factor).


Errors

Code
Meaning

200

Always returned when at least one sub-check was eligible. Per-check failures appear in summary.errored, never as a 4xx/5xx

400

No sub-checks were eligible with the supplied fields (e.g. only facePhoto provided without name + idNumber)

502

Internal failure invoking the upstream — only thrown when something fails outside the per-check fan-out

Partial failures are intentional: a single misbehaving upstream sub-call should not prevent the rest of the response from being delivered.


Common patterns

Lightweight identity check. Send name + idNumber only — runs identity.two-factor and risk.criminal for the cost of two upstream calls.

Document verification with face match. Send name, idNumber, idIssueDate, idExpiryDate, facePhoto — runs identity.four-factor + identity.image plus risk.criminal. The strongest non-mobile identity confirmation available.

Mobile-anchored screening. Send mobile only — runs mobile.attribution, both location lookups, and risk.fraud. Useful when only a phone number is known.

Full KYC. Send name, idNumber, mobile, bankCardNumber (and optionally idIssueDate / idExpiryDate / facePhoto) — fans out to up to 9 calls in parallel, plus one extra call per entry in addressesToVerify.

Address verification. Pass an addressesToVerify[] entry to ask "is this address near the user's work / residential / common area?". Each entry returns a distance bucket (≤3km>50km). Useful for verifying a user's stated address against where their phone is actually used — runs alongside the standard mobile location lookups, which return the actual cities.

Staged verification. To gate later steps on earlier ones (e.g. only screen risk and bank after identity passes), call the endpoint twice: first with name + idNumber only, then re-call with the additional fields after you've inspected the identity result. Each call is independent.

Webhook Delivery: Provide callbackUrl to receive a POST with the completed report when it is ready. The webhook body is a WebhookPayload containing the same data as a 200 response from /kyc/cn/poll. The request includes X-Webhook-Event: kyc.cn.report.completed and X-Request-Id headers, plus any custom headers supplied in callbackHeaders. Polling via /kyc/cn/poll remains available regardless of whether a callbackUrl is provided.

Authorizations
x-client-idstringRequired

Client ID in x-client-id header.

x-client-secretstringRequired

Client Secret in x-client-secret header.

Body

All fields are optional. The endpoint runs every sub-check whose required fields are present (see the Sub-check eligibility table above). At least one sub-check must be eligible — otherwise a 400 is returned.

namestringOptional

Full legal name in Chinese characters (simplified). Must match the name on the resident ID card exactly. Latin pinyin transliterations are not accepted by the upstream.

Enables: identity-document checks, mobile-cross checks, bank checks, criminal records.

Example: 张三
idNumberstringOptional

18-digit Chinese Resident Identity Card number (居民身份证号码). The 18th character may be X for older IDs.

Enables: identity-document checks, mobile-cross checks, bank checks, fraud, criminal records.

Example: 110101199001011234Pattern: ^[0-9]{17}[0-9Xx]$
idIssueDatestringOptional

ID card issue date in YYYYMMDD format (no separators). Found on the back of the physical card under 签发日期.

Pair with idExpiryDate to upgrade identity.two-factor to the stronger identity.four-factor check that also validates the on-file dates.

Example: 20180501Pattern: ^[0-9]{8}$
idExpiryDatestringOptional

ID card expiry date in YYYYMMDD format, or the literal string 长期 when the card has no expiry (issued to citizens 46+ in some cases). Found on the back of the physical card under 有效期限.

Example: 20281231
facePhotostringOptional

Base64-encoded facial photo. Do not include the data:image/...;base64, prefix — only the raw base64 payload.

Recommended: clear front-facing photo, no occlusions, JPEG/PNG, ≤2 MB. Enables identity.image, which compares the photo against the MPS face on file for the supplied name + ID. Requires name and idNumber.

Example: iVBORw0KGgoAAAANSUhEUgAA...
mobilestringOptional

11-digit mainland China mobile number. No country code, no +86, no spaces. First two digits must be 13-19.

Enables: mobile lookups, mobile-cross identity checks, fraud screening, bank four-factor. Mobile alone is enough to run mobile/risk lookups without any identity fields.

Example: 13800138000Pattern: ^1[3-9][0-9]{9}$
bankCardNumberstringOptional

Bank card number — debit or credit. Digits only, no spaces. Enables bank.three-factor (with name + idNumber) or bank.four-factor (also with mobile).

Example: 6222021234567890123
callbackUrlstring · uriOptional

HTTPS URL to receive a webhook POST when the report is ready. When provided, the completed report (same payload as a 200 response from /kyc/cn/poll) is delivered to this URL. Polling remains available as a fallback.

Pattern: ^https://.+
Responses
chevron-right
200

KYC verification request initiated. Use the returned token with /kyc/cn/poll to fetch the result.

application/json
tokenstringRequired

JWT token encoding the request — pass it to /kyc/cn/poll until a 200 response is returned (typically within 10-30 seconds for a full KYC fan-out).

Example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
post
/kyc/cn/request

KYC China verification - Poll

post

Check the status of a KYC verification request initiated via POST /kyc/cn/request. Returns 202 while the verification is still running, and 200 with the full result when complete.

Verification typically completes within 10-30 seconds depending on the number of sub-checks and PDF rendering. Poll every 2-3 seconds.

See the /kyc/cn/request documentation for the response shape semantics (sub-check eligibility, summary buckets, category-by-category interpretation).

Authorizations
x-client-idstringRequired

Client ID in x-client-id header.

x-client-secretstringRequired

Client Secret in x-client-secret header.

Body
tokenstringRequired

JWT token

Example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8UPattern: ^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$
Responses
chevron-right
200

KYC China result. Categories are present only when at least one of their sub-checks ran.

application/json
pdfUrlstring · uriRequired

Presigned URL to download a PDF version of this report. Valid for 7 days. The underlying object is deleted after 30 days by bucket lifecycle rule.

Example: https://kyc-bucket-ap-east-1-staging-canary.s3.ap-east-1.amazonaws.com/kyc/abc123.pdf?X-Amz-...
pdfFileNamestringRequired

Suggested filename for the PDF download.

Example: kyc-cn-abc123.pdf
post
/kyc/cn/poll

Last updated