Skip to main content

KYC

Transaction endpoints refuse an unverified user. Submit each end user's KYC once, wait for it to reach VERIFIED, then transact on their behalf.

Every endpoint here is keyed by userEmail. Within your partner account, that email is the user's identity.

Status values

StatusMeaning
NONENothing submitted.
PROCESSINGSubmitted and under review.
VERIFIEDApproved. The user can transact.
REJECTEDRefused. rejectReason says why. Fix and resubmit.

Submit KYC

POST /api/v2/user/submit-kyc

Signed string

partnerCode|userEmail|nationality|<secretKey>

Request

Every user is verified in full, so the identity document, the two document photos, the selfie and the KYC report are all required. If a submission is missing one, it comes back as errorCode 5 (INVALID_REQUEST_DATA), and the message names what was missing:

Missing required fields: national ID, document type, front image, selfie image.
FieldTypeRequiredNotes
partnerCodestringYes
userEmailstringYesThe user's identity.
signaturestringYes
firstNamestringYesStored uppercase, with accents removed — it reads back that way.
lastNamestringYesSame.
nationalitystringYesISO 3166-1 alpha-2, uppercase — VN, PH, BR. Checked against a list.
typestringYesPASSPORT, ID_CARD, DRIVERS_LICENSE, or RESIDENCE_PERMIT.
nationalIdstringYesThe ID or passport number. The format is not checked. Reusing one that belongs to another of your users is rejected with 52.
dateOfBirthstringYesYYYY-MM-DD.
genderstringNomale or female.
issueDatestringNoYYYY-MM-DD.
expiryDatestringYesYYYY-MM-DD. It is not checked against today, so an expired document passes. For a document that never expires, send its issue date.
frontIdImagestringYesA base64 data URL — see below.
backIdImagestringYes, unless PASSPORTOnly a passport has no back side, so it is ignored for one. Omitting it for any other type fails with 58 rather than 5 — see below.
holdIdImagestringYesA selfie of the user, as a base64 data URL.
kycReportstringYesA KYC report PDF, as a base64 data URL — data:application/pdf;base64,….
addressLine1stringNo
addressLine2stringNo
citystringNo
statestringNoFalls back to city if empty.
zipCodestringNo
phoneNumberstringNo
phoneCountryCodestringNoDigits only, without the +84, 63, 1. Checked against a list of calling codes.

Format the images and the report as data URLs, prefix included: data:image/jpeg;base64,/9j/4AAQ…. A bare base64 string is rejected with 58, so the prefix is the detail to confirm first.

The same format check gives a missing backIdImage its error code: a non-passport document without one returns 58 rather than 5, and data.invalid names the document concerned.

Response data

FieldTypeNotes
idnumberThe KYC record id, distinct from the user's nationalId.
kycStatusstringPROCESSING — every submission goes to review.
signaturestringOver id|kycStatus|secretKey.
JSON
{
"success": true,
"message": "Your request has been successful",
"data": { "id": 90412, "kycStatus": "PROCESSING", "signature": "K3tQ...==" },
"errorCode": 0,
"semanticCode": "SUCCESS"
}

Errors — plus the shared ones.

27 USER_EMAIL_INVALIDuserEmail is malformed.
5 INVALID_REQUEST_DATAA required field or document is missing — the message names them.
35 USER_KYC_UNDER_PROCESSINGA submission is already in flight.
52 KYC_DOCUMENT_ALREADY_REGISTEREDAlready verified, or that nationalId belongs to another of your users.
58 KYC_DOCUMENT_FORMAT_INVALIDA document is not a valid data URL.

Supplement a KYC profile

POST /api/v2/user/submit-kyc-supplement

Backfills fields that a VERIFIED user's profile does not yet hold. The standard it has to meet is the required set in Submit KYC. A user verified before a field joined that set is short of it, and this is where you fill it in. A user who was never verified has nothing to supplement, and goes through Submit KYC instead.

It patches. Send the fields you are filling in, leave the rest out, and they keep their current values. Send nothing and you get errorCode 5.

kycStatus is untouched, so the user stays VERIFIED and keeps transacting while the supplement is reviewed. What moves is kycSupplementStatus, which reads SUBMITTED once the call succeeds.

Signed string — it does not cover the fields you are patching.

partnerCode|userEmail|<secretKey>

Request — every field below carries the same format and meaning as in Submit KYC. Required here means this call rejects the request without it. It does not mean the profile can do without the rest.

FieldTypeRequiredNotes
partnerCodestringYes
userEmailstringYesIdentifies the profile to patch.
signaturestringYes
firstName, lastNamestringNoUppercased and stripped of accents, as on submit.
typestringNoPASSPORT, ID_CARD, DRIVERS_LICENSE, or RESIDENCE_PERMIT. Switching to PASSPORT clears the stored back image, and a backIdImage sent with it is ignored.
nationalIdstringNoChanging it to one already held by another of your users is rejected with 52.
dateOfBirth, issueDate, expiryDatestringNoYYYY-MM-DD. For a document that never expires, expiryDate is its issue date.
genderstringNomale or female.
nationalitystringNoISO 3166-1 alpha-2.
frontIdImage, backIdImage, holdIdImagestringNoBase64 data URLs — data:image/jpeg;base64,…. Each one you send replaces the stored image.
kycReportstringNoA KYC report PDF, as a base64 data URL — data:application/pdf;base64,….
addressLine1, addressLine2, city, state, zipCodestringNo
phoneNumber, phoneCountryCodestringNo

Response data

FieldTypeNotes
idnumberThe KYC record id.
kycStatusstringThe main status. This call does not change it.
kycSupplementStatusstringSUBMITTED.
signaturestringOver id|kycStatus|secretKey, using the main status rather than the supplement one.
JSON
{
"success": true,
"message": "Your request has been successful",
"data": {
"id": 90412,
"kycStatus": "VERIFIED",
"kycSupplementStatus": "SUBMITTED",
"signature": "K3tQ...=="
},
"errorCode": 0,
"semanticCode": "SUCCESS"
}

Errors — plus the shared ones.

27 USER_EMAIL_INVALIDuserEmail is malformed.
5 INVALID_REQUEST_DATAYou sent no patchable field at all.
50 REQUESTED_INFO_NOT_FOUNDNo such user, or the user has no KYC profile — they were never submitted.
52 KYC_DOCUMENT_ALREADY_REGISTEREDThe new nationalId belongs to another of your users.
58 KYC_DOCUMENT_FORMAT_INVALIDA document you sent is not a valid data URL. The message names which.

Get KYC information

POST /api/v2/user/get-kyc-information

Signed string

partnerCode|userEmail|<secretKey>

RequestpartnerCode, userEmail, signature. All required.

Response data

FieldTypeNotes
kycStatusstringPROCESSING, VERIFIED, or REJECTED.
kycSupplementStatusstringNONE, SUBMITTED, REJECTED, or APPROVED. The state of a supplement, independent of kycStatus.
rejectReasonstringOnly when REJECTED.
firstName, lastNamestring
dateOfBirth, issueDate, expiryDatestringYYYY-MM-DD.
genderstring
nationalitystring
nationalIdstring
addressLine1, addressLine2, city, state, zipCodestring
phoneNumber, phoneCountryCodestring
frontUrl, backUrl, holdUrlstringURLs, not the base64 you uploaded. backUrl is absent for a passport.
signaturestringOver nationalId|kycStatus|secretKey.

Three details of this response to plan for.

The images come back as URLs rather than the base64 you uploaded.

The profile does not carry the document type, so keep your own record of whether you submitted a passport or an ID card.

A REJECTED profile returns kycStatus, kycSupplementStatus, rejectReason and signature only. Because nationalId belongs to the signed string, its segment is signed as the literal text undefined:

undefined|REJECTED|<secretKey>

If you verify response signatures, build this case in.

Errors50 REQUESTED_INFO_NOT_FOUND when the user does not exist or has never submitted KYC, plus the shared ones. A rejected profile arrives as a success envelope, with the outcome in kycStatus.

Check service access

POST /api/v2/user/check-access-service

Asks whether a user is allowed to use a service in a given currency — verified KYC, permitted nationality. Call it before quoting, so you fail early with a reason instead of failing at order creation.

Signed string

partnerCode|userEmail|fiatCurrency|service|<secretKey>

Request

FieldTypeRequiredNotes
partnerCodestringYes
userEmailstringYes
fiatCurrencystringYesNot enforced against a list; use a real currency — VND, PHP, BRL.
servicestringYesOne of SCAN_TO_PAY, ON_RAMP, OFF_RAMP.
signaturestringYes

Response data

FieldTypeNotes
accessbooleantrue on success.
signaturestringOver access|secretKey — so the signed string is true|<secretKey>.

A permitted user answers access: true. A user who may not transact answers with an error envelope that names the reason:

34 USER_KYC_VERIFICATION_REQUIREDNo profile, or KYC is not VERIFIED.
59 NATIONALITY_NOT_SUPPORTEDNationality missing, or blocked for this service and currency.

So branch on success rather than on data.access.