KYC (end user)

Verify a natural-person subaccount, including session cap, enhanced measures, and 409 phone versus email.

This page describes the published API. It is not legal advice. Bit2Me Legal and Compliance must review KYC, KYB, Travel Rule, and 2FA copy before this portal is announced to partners.

Identity is only Bit2Me’s hosted flow (photo ID + video liveness). You cannot send your own ID scan or run liveness on your servers.

Legal entities use KYB — do not mix this flow into companies.

If you are a bank, an electronic money institution, or another regulated entity that already performs an equivalent KYC on your users, the sales team can explain how to reuse it instead of running this flow.

Before you start

ItemRequirement
ParentYour institution is already onboarded
Per end userOne subaccount (userId UUID)
After createX-SUBACCOUNT-ID: {userId} on every call
ConsentUser accepts general and privacy-policy terms in your UI before you create the subaccount

Terms download paths are not in this OpenAPI snapshot. Store the document versions the user accepted in your application.

Store who accepted, when, and which document versions. Do not create the subaccount until this is done.

POST /v1/account/subaccount

Required body fields in the snapshot: email, name, surname, phone. Optional type (person or company). Result: userId. From now on send X-SUBACCOUNT-ID.

PUT /v1/account/update

Send person AML fields under person: nationalityCountryCode, employmentStatus, profession. Also send depositEstimation and fundsOrigin at the root when required.

Do not send person.name, person.surname, or person.birthDate — identity verification collects those. accountPurpose is a GET /v3/account field, not a PUT field.

Validate enums first: GET /v1/account/employment-status, GET /v1/account/purposes, GET /v1/account/funds-origin.

POST /v2/account/address

Required: streetAddress, city, stateCode, zip, countryCode, nationalityCountryCode. stateCode is the FIPS value from GET /v1/misc/country/{countryISOCode}/region.

POST /v3/account/verify/identity/init

Body: locale (required); optional workflowId (default 10011), successUrl, errorUrl. Open redirectUrl in a native system browser. See Identity widget.

The identity process allows a fixed number of retries. Check whether the user can still retry with GET /v1/account/verify/identity/verification/retry (406 when the limit is reached). Once the limit is exceeded, contact support to reset it.

Proof-of-address and proof-of-funds upload paths are not in this snapshot. Complete any extra document requests your onboarding contact specifies before you treat the user as live.

REST: GET /v1/account/verify/identity. Real-time: WebSocket identity.validated.

Success in the snapshot is status: verified. tier is optional. Treat the user as live only when status is verified and tier is 2 when that field is present.

Optional artifacts: GET /v1/verifier/subaccount/file.

Operating rules

These rules apply to any partner subaccount. They are not a single-customer exception.

Document rejection is not a session expiry

Closure dates are document rejections or manual closures, not Didit session expirations. A stored image means the front side was received. Didit may return unknown; an operator can reclassify it (imageDoc = unusable document image). The same image can be closed with no new capture.

CodeMeaning
docMissingImageA required image is missing (back side or selfie). The front may already be on file.
noDocUploadedManual closure. No image was uploaded.
imageDocThe document image is unusable.

Didit labels these Expired and Abandoned. Bit2Me does not convert them into those errors. error and errorReviewed do not block a restart. The user can open a full capture (front, back, and selfie) once they are under the session cap. Operations can reset the attempt counter.

Session cap

The cap is 3 sessions in 15 days (maxRetriesReached). The counter includes every verification session created in that window, including sessions where nothing was captured. It is not the number of sessions the user thinks they started. A QR opened in Android's in-app browser can create and abandon a session before the camera and still increment the counter. See Identity widget.

Those events store no device, browser, user agent, or camera error. The closure email is identity.rejected (English subject: Document pending verification). The payload has no reason and no new link. The HTML body is not reproduced here.

Enhanced measures

Identity can already be captured (email, phone, questionnaire, document image) while the only error is enhancedMeasuresNotReceived and status is waitingUser. The user does not redo identity verification.

ScoreBandWhat happens
8–9HighDocumentation is requested at verification. There is no deposit threshold.
9CapThe engine raises risk to 9 when the nationality score is 5 or 7, or the residence score is 4.
6Medium-highMeasures apply only from €10,000 cumulative. This does not block before a deposit.

Whether a nationality and residence pair should score 6 or 9 is a Compliance confirmation. The API does not decide it.

The system does not ask for a named file list. It opens AML measure MR1 and a generic reinforcedMeasures document in a requested state. Compliance defines the package. There is no partner endpoint to upload it. The channel is the Zoho Sign form in the email identity.enhanced.measures.required (English subject: Documentation pending) until the API supports upload.

On a subaccount that email goes to the partner's internal address, not the end client. The partner must forward it.

HTTP 409 on subaccount creation

409 is two errors. Read errorCode in the response body. A partner link id or party id is not in account or auth logs, so the body is required.

errorCodeMessageRule
B2M_AUTH_PHONE_DUPLICATEDThe phone already existsChecked against any individual Bit2Me user, including direct accounts. A registered phone blocks creation. Phone numbers cannot be reused.
B2M_AUTH_EMAIL_DUPLICATEDDuplicated emailStored as {mainUserId}###{email}. Does not conflict with a direct Bit2Me account. Conflicts with another subaccount of the same partner.

"Same email or phone as a Bit2Me account" is true only for the phone.

Document type when nationality and residence differ (outside the EEA)

unsupportedIdTypeResidenceCountry is a document-type block, not a risk-score block. If nationality is not the residence country and residence is outside the EEA, only a document issued by the residence country is accepted:

CodeDocument
idcardNational ID card of the residence country
foreign-id-cardResident card or residence permit issued by that country

A national ID card or passport of the nationality country is not accepted. Attempts can be reset so the user starts again with a residence-country document. This note does not state the rule inside the EEA, or when nationality and residence are the same country.

Common mistakes

  1. Creating the subaccount before the user accepts terms in your app.
  2. Sending name / surname / birthDate on the AML PUT.
  3. Embedding identity in a WebView without camera or microphone, or opening the QR in an in-app browser so abandoned sessions count toward the cap.
  4. Uploading partner-captured ID or liveness.
  5. Omitting nationalityCountryCode on the address.
  6. Treating the user as live before verified (and tier: 2 when returned).
  7. Reading a document-rejection date as a Didit session expiry.
  8. Telling the partner that a 409 means "same email or phone as a Bit2Me account" without the errorCode.
  9. Asking the end user to redo identity when the only error is enhancedMeasuresNotReceived.

On this page