Skip to main content

APIs in eKYC flow

eKYC - Electronic Know Your Customer, hereafter referred to as the eKYC flow, is a flow that verifies a customer's identity remotely, entirely on the user's own device and without meeting in person. It is used to determine whether the person performing a transaction is the genuine holder of the identity document they provide, and is typically applied when opening an account or registering for a service for the first time.

This flow is different from the Face Authentication flow: eKYC is used for the first identification, and it reads the identity document to obtain the personal information and the portrait photo. Face Authentication is used for later verifications, and only matches the face against data that has already been verified and stored.

For each identification, the FPT AI eKYC system performs the following steps, in order:

  • Document reading (OCR - Optical Character Recognition): capture an image of the identity document (citizen ID card, ID card, passport, driver's license...) and automatically extract information such as full name, date of birth, and document number.
  • QR code verification on chip-based citizen ID cards: when the document image does not allow the QR code to be read, the user scans the code directly so it can be compared with the information just extracted.
  • NFC chip reading and verification (Near Field Communication, a contactless chip-reading technology): read the data stored in the chip of a chip-based citizen ID card and check that the data is intact and has not been copied or modified.
  • Liveness check: determines whether the face in the image or video belongs to a real person in front of the camera, and not a printed photo, a photo or video replayed on a screen, a mask, or a face forged with technology.
  • Face matching: compares the face just captured with the portrait photo on the document, and returns whether they match along with a similarity score (similarity).

Depending on need, customers can skip some steps (for example, read the document only, or check the face only). These variants are described in the section Flow variants.

Typical use cases

  • Opening a bank account, e-wallet, or securities account fully online.
  • Registering for financial, insurance, or consumer loan services while still meeting customer identification requirements.
  • Registering accounts or services of businesses that need to verify that the user is a real person and the genuine holder of the document, for example agents, collaborators, and partners.

Conditions of use

  • The customer already has an api-key issued by FPT and has configured the flow on the portal. This configuration determines which steps run in each session (see the section Session configuration).
  • The NFC chip reading step only applies when the user's device supports NFC and the document is a chip-based citizen ID card.
  • The customer's application must call the APIs in the correct order and use the same session ID throughout the flow.

Each identification is one eKYC session, issued by the backend through the session initialization API. All subsequent APIs send the session ID in the header. This document is intended for customers who integrate directly through the API, and describes the configuration with no payload encryption: requests and responses are in plain form (JSON, form-data), protected by HTTPS. For each API, the document describes the call order, the headers, and the payload.

  1. Overview: standard flow, flow variants, base URL, common headers, API list, and session configuration.

  2. API details:

  3. Result handling and error codes: how to read results, HTTP codes, and common liveness codes.

  4. Appendix A. Integration checklist: common mistakes when integrating.

Overview​

Standard flow​

Default flow: document → (QR) → (NFC) → face.

Sequence diagram of API calls in the Customer Identification flow

Steps

  • Call API 1. Save the session-id and sdk_config from the response.
  • Capture the document image and send it to API 2. errorCode = "0" means success.
  • If the document is a chip-based citizen ID card (the Type field in the OCR result is 8 or 9), OCR cannot read the QR code (the QRcode field is empty or N/A), and sdk_config.scan_qr_mode = 1: have the user scan the QR code on the card and send it to API 3.
  • If sdk_config.ekyc_flow is not 1, the device has NFC, and the document is a chip-based citizen ID card: read the chip and send it to API 4.
  • Capture face images or record a face video according to sdk_config.liveness_type, and send them to API 5. code = "200" means passed.

Flow variants​

VariantWhen to useAPI sequenceNotes
FullDefault1 → 2 → (3) → (4) → 5Steps 3 and 4 follow the conditions in the Standard flow section
Document onlyOnly document extraction is needed1 → 2 → (3)Send the header only-engine: true in API 1 and API 2
Two-stepDocument and face are done at different timesStep 1: 1 → 2. Step 2: 5Step 2 reuses the session-id from step 1, API 1 is not called again
NFC only, or NFC with livenessThe ID number, date of birth, and expiry date needed to open the chip are already available1 → 4 → (5)No OCR step
Liveness onlyFace verification, ekyc_flow = 61 → 6Calls the liveness engine directly

General information​

Base URL​

Environmentbase_urlUsed for
Productionhttps://api.fpt.ai/vision/ekyc-be/All APIs except API 6
Staginghttps://api.fpt.ai/vision/ekyc/be-stag/All APIs except API 6
Liveness enginehttps://api.fpt.ai/API 6 only
  • The paths in this document are appended directly after base_url (without a leading /), for example base_url + init_session.
  • If you go through the customer's own proxy, call all APIs, including API 6, through the proxy base URL.
  • Recommended timeout: 60 seconds for connect, read, and write.

Common headers​

The headers below appear in most APIs. If a header has no value, omit it entirely and do not send an empty string.

HeaderData typeDescription
api-keyStringAPI key issued by FPT. Required in every API
session-idStringSession ID returned by API 1
device-typeStringClient type: android, ios, web-sdk, api. Keep the same value throughout the session
sdk-versionStringClient version, for example 3.7.3.5
langStringLanguage of messages and of field names in the result: vi, en
manual-timeStringTime the user spent on the current step, in milliseconds, for example 8423
x-sdk-trace-idStringA new UUID generated for each request, used for tracing
x-sdk-trace-valuesStringDevice attestation token (Play Integrity on Android), sent in every request. Send -8 if no token is available yet
Custom headersStringThe customer's own headers, for example for a proxy or API gateway. Send them with every API

Headers containing underscores

API 1 uses the header client_uuid (with an underscore). Many proxies drop headers containing underscores by default (nginx needs underscores_in_headers on;). If you go through your own proxy, check that this header reaches the backend.

API list​

APIMethod and pathBody typeRequired
1. Session initializationPOST init_sessionJSONYes
2. Document OCRPOST ocrform-dataYes, except flows without OCR
3. QR code verificationPOST qrcodeJSONOptional
4. NFC chip data verificationPOST check_chipJSONOptional
5. Liveness and face matchingPOST face/livenessform-dataYes
6. Standalone livenessPOST dmp/liveness/v3form-dataLiveness-only flow
7. Logging and tracingPOST logging/tracingJSONOptional, only when enable_tracing = true

Session configuration​

The response of API 1 returns the session configuration (the sdk_config field), which is set on the portal per api-key. The client uses the fields below to decide the flow; at minimum, read the fields marked with ★.

FieldData typeMeaning
ekyc_flow ★Int1: document and face, no NFC. 6: liveness only (API 6). Other values: an NFC step is added (when the device supports NFC and the document is a chip-based citizen ID card)
liveness_type ★IntFace capture type: 0 PHOTO, 1 VIDEO, 2 ACTIVE_VIDEO, 3 RANDOM, 4 MORE_PHOTO, 5 FLASH. Determines the payload of API 5 (see section 5.3. Request Body)
scan_qr_mode ★Int1: QR scanning is required when OCR cannot read the QR code on a chip-based citizen ID card
is_skip_nfcBooleanAllows the user to skip the NFC step
nfc_retry_limitIntMaximum number of failed chip reads
limitIntMaximum number of attempts for the session (if this field is absent, treat it as 5)
is_show_ekyc_resultBooleanShow the final result screen at the end of the flow
enable_tracingBooleanEnables client-side event logging (see API 7). Can be ignored
block_screenshotBooleanBlocks screenshots during the flow
is_upload_imageBooleanAllows choosing an existing image instead of capturing one
show_guide, show_logo, show_label_step, show_action_step, show_switch_button, show_switch_animation, show_exit_dialog, show_nfc_position, font_path, languageVariousClient-side interface configuration. Can be ignored
document_capture_delay, threshold_yaw, count_stable, iou_threshold, enable_accessibility_serviceVariousParameters for automatic capture and device checks on the client side. Can be used as hints

The on_device_model_config field (at the same level as sdk_config) turns on-device image quality checks on or off: blur, detection, brightness, face_quality, eye_closed. The client can use it as a hint.

1. Session initialization API​

Applies to: all flows. Body type: JSON.

Creates a new eKYC session. Returns the session ID and the session configuration.

1.1. Request URL​

POST base_url/init_session

1.2. Request Headers​

ParameterRequiredData typeDescription
api-keyYesStringAPI key issued by FPT
device-typeYesStringandroid, ios, web-sdk, api
Content-TypeYesStringapplication/json; charset=utf-8
client_uuidNoStringA code generated by the customer's system for each identification, used to reconcile sessions, for example a UUID
sdk-versionNoStringClient version
only-engineNoBooleantrue: document-only flow. Default is false
x-sdk-trace-valuesNoStringUsually -8 at this step, because the device token can only be obtained after a session-id exists

1.3. Request Body​

Device information in JSON format.

ParameterRequiredData typeDescription
memoryNoStringDevice RAM size (GB), for example "5.62"
modelNoStringA string combining the model, operating system version, and manufacturer, separated by the vertical bar character. See the example in the sample request
nfc_supportNoBooleanWhether the device has NFC
rootNoBooleanThe client detected a rooted or jailbroken device, or a tampering tool (Frida...)
emulatorNoBooleanThe client detected that it is running on an emulator
debuggerNoBooleanThe client detected an attached debugger
accessibilityNoBooleanAn accessibility service is enabled

1.4. Sample request​

curl --location --request POST 'base_url/init_session' \
--header 'api-key: your-api-key' \
--header 'client_uuid: 3f1c2b9e-6a1d-4f0e-9d7a-2b8e5c4a1f90' \
--header 'device-type: android' \
--header 'sdk-version: 3.7.3.5' \
--header 'only-engine: false' \
--header 'x-sdk-trace-values: -8' \
--header 'Content-Type: application/json; charset=utf-8' \
--data-raw '{
"memory": "5.62",
"model": "SM-S918B|14|samsung",
"nfc_support": true,
"root": false,
"emulator": false,
"debugger": false,
"accessibility": false
}'

1.5. Response​

1.5.1. Sample response​

Success: http_code = 200

{
"code": "200",
"message": "success",
"session-id": "7760a743-884b-4195-b781-892e72c7b0b6",
"sdk_config": {
"ekyc_flow": 2,
"liveness_type": 4,
"scan_qr_mode": 1,
"is_skip_nfc": false,
"nfc_retry_limit": 3,
"limit": 5,
"is_show_ekyc_result": true,
"enable_tracing": true,
...
},
"on_device_model_config": {
"blur": true,
"detection": true,
"brightness": 0.3,
"face_quality": true,
"eye_closed": true
}
}

Failure: http_code != 200

1.5.2. Response description​

ParameterData typeDescription
codeString"200": success. Any other value: display message and stop the flow
messageStringReturned message
actionStringHas the value exit when the flow must stop (see the section Result handling and error codes)
session-idStringSession ID. Send it in the session-id header of all subsequent APIs
sdk_configObjectSession configuration, see the section Session configuration
on_device_model_configObjectConfiguration of on-device image quality checks

1.5.3. Sample error responses​

HTTPResponseCause
200{"code": "<not 200>", "message": "..."}The session could not be created, display message
401{"code": "401", "message": "..."}Invalid api-key
422{"detail": [...]}A required header is missing
500–System error

2. Document OCR API​

Applies to: full flow, document-only flow. Body type: form-data.

Extracts information from the identity document. The front and back sides are sent in one request (method A, section 2.4) or in two separate requests (method B).

2.1. Request URL​

POST base_url/ocr

2.2. Request Headers​

ParameterRequiredData typeDescription
api-keyYesStringAPI key issued by FPT
session-idYesStringSession ID from API 1
device-typeYesStringThe same value used in API 1
document-typeYesStringDocument type, see the table in section 2.3
countryNoStringCountry that issued the document. Default is vi
langNoStringvi, en
only-engineNoBooleanThe same value used in API 1
side-typeNoStringfront or back, used only when sending each side separately (section 2.4, method B). The front image must be sent first
sdk-versionNoStringClient version
manual-timeNoStringTotal time spent capturing the front and back sides (ms)
x-sdk-trace-id, x-sdk-trace-valuesNoStringSee section Common headers

2.3. Request Body​

FormData, in exactly the following order:

OrderPartRequiredTypeDescription
1filesYesFileFront image, image/jpeg
2filesDepends on documentFileBack image

Values of document-type and the corresponding number of images:

document-typeDocumentNumber of images
idrCitizen ID card / ID card2 (front, back)
dlrDriver's license2 when country = vi, 1 (front) for other countries
passportPassport1 (information page)
umidrUMID (Philippines)1
tinidrTIN ID (Philippines)1

Images: JPEG, recommended to be resized to at most 1080 × 1080.

2.4. Sample request​

Method A (recommended): send both sides in one request.

curl --location --request POST 'base_url/ocr' \
--header 'api-key: your-api-key' \
--header 'session-id: 7760a743-884b-4195-b781-892e72c7b0b6' \
--header 'device-type: android' \
--header 'document-type: idr' \
--header 'country: vi' \
--header 'lang: en' \
--header 'only-engine: false' \
--header 'sdk-version: 3.7.3.5' \
--header 'manual-time: 8423' \
--header 'x-sdk-trace-id: 0b6e1f43-2a52-4c1b-9b0e-5f4d3c2a1b00' \
--header 'x-sdk-trace-values: <token>' \
--form 'files=@"front.jpg";type=image/jpeg' \
--form 'files=@"back.jpg";type=image/jpeg'

Method B: send each side separately, one image per request with the side-type header, front side first. If document-type is passport, each request sends only 1 image.

2.5. Response​

2.5.1. Sample response​

Success

{
"errorCode": "0",
"errorMessage": "",
"data": [
{ "key": "ID", "name": "ID No.", "value": "0010xxxxxxxx", "score": "98.66", "locale": "vn" },
{ "key": "Name", "name": "Full name", "value": "NGUYEN VAN A", "score": "99.71", "locale": "vn" },
{ "key": "Date of birth", "name": "Date of birth", "value": "01/01/1990", "score": "98.41", "locale": "vn" },
{ "key": "Type", "name": "Card type", "value": "8", "score": "", "locale": "vn" },
{ "key": "QRcode", "name": "QR code", "value": "0010xxxxxxxx|...", "score": "", "locale": "vn" },
...
]
}

2.5.2. Response description​

ParameterData typeDescription
errorCodeString"0" means success. Any other value is an error, display errorMessage and let the user capture again. For details, see the error code documentation
errorMessageStringError message in the language of the lang header
actionStringexit: stop the flow, do not allow recapture
dataArrayExtracted fields, each element contains key, name, value, score, locale. The list of fields depends on the document type
total_dataArrayExtended list of fields (if any), same structure as data
list_tables, table_titleArray, StringTabular data (if any)

The key values used to navigate the flow (matched case-insensitively):

keyUsed for
Type8 or 9: chip-based citizen ID card, may proceed to the QR step (API 3) or NFC step (API 4)
QRcodeEmpty or N/A: OCR could not read the QR code, scan the QR if scan_qr_mode = 1
ID, Date of birth, Expired Date, MRZUsed to derive the key that opens the NFC chip (ID number, date of birth, expiry date)

2.5.3. Sample error responses​

Business error: HTTP 200, errorCode is not "0"

{
"errorCode": "3",
"errorMessage": "The selected document was not found in the image"
}

Session expired: returned when the session is invalid, for example the session is not registered, the session timed out, or the device type does not match...

{
"errorCode": "403",
"errorMessage": "The session has expired"
}

Error cases by HTTP code:

HTTPResponseCause
403{"errorCode": "403", "errorMessage": "Your device is not secure..."}The device is not secure according to the device attestation token
403{"errorCode": "403", ...}The session does not exist, has expired, or has ended
422{"detail": [...]}A required part or header is missing

3. QR code verification API​

Applies to: chip-based citizen ID cards. Body type: JSON. Required: optional.

Sends the QR string scanned from a chip-based citizen ID card, used when the OCR image cannot yield the QR code. The system compares the QR data with the OCR result from the previous step.

3.1. Request URL​

POST base_url/qrcode

3.2. Request Headers​

ParameterRequiredData typeDescription
api-keyYesStringAPI key issued by FPT
session-idYesStringSession ID from API 1
device-typeYesStringThe same value used in API 1
Content-TypeYesStringapplication/json; charset=utf-8
langNoStringvi, en
sdk-version, manual-time, x-sdk-trace-id, x-sdk-trace-valuesNoStringSee section Common headers

3.3. Request Body​

JSON.

ParameterRequiredData typeDescription
qrcodeYesStringThe exact string read from the QR code on the citizen ID card

3.4. Sample request​

curl --location --request POST 'base_url/qrcode' \
--header 'api-key: your-api-key' \
--header 'session-id: 7760a743-884b-4195-b781-892e72c7b0b6' \
--header 'device-type: android' \
--header 'lang: en' \
--header 'sdk-version: 3.7.3.5' \
--header 'manual-time: 3120' \
--header 'Content-Type: application/json; charset=utf-8' \
--data-raw '{
"qrcode": "0010xxxxxxxx|0123456789|NGUYEN VAN A|01011990|Nam|...|01012021"
}'

3.5. Response​

3.5.1. Sample response​

Success: http status = 200 and errorCode = 0

{
"errorCode": "0",
"errorMessage": ""
}

Failure

QR data mismatch error:

{
"errorCode": "QR01",
"errorMessage": "The data in the QR code does not match"
}

Session expired: returned when the session is invalid, for example the session is not registered, the session timed out, or the device type does not match...

{
"errorCode": "403",
"errorMessage": "The session has expired"
}

3.5.2. Response description​

ParameterData typeDescription
errorCodeString"0" means valid. Any other value is an error, see the error code documentation for details
errorMessageStringError message (if any). The language of the message is set by the lang parameter in the headers
actionStringexit: stop the flow

4. NFC chip data verification API​

Applies to: flows with NFC. Body type: JSON. Required: optional.

Sends the data read from the citizen ID card chip (ICAO 9303 standard) so that the backend can verify integrity (SOD), detect chip cloning (Active Authentication, Chip Authentication), and return the data stored on the chip.

4.1. Request URL​

POST base_url/check_chip

4.2. Request Headers​

ParameterRequiredData typeDescription
api-keyYesStringAPI key issued by FPT
session-idYesStringSession ID from API 1
device-typeYesStringThe same value used in API 1
Content-TypeYesStringapplication/json; charset=utf-8
langNoStringvi, en
sdk-version, manual-time, x-sdk-trace-id, x-sdk-trace-valuesNoStringSee section Common headers

4.3. Request Body​

The JSON body contains the fields below. If a field cannot be read, omit it or set it to null. Base64 fields use standard Base64 with line breaks removed (except face).

ParameterRequiredData typeDescription
sodYesString (Base64)EF.SOD, the digital signature of the chip data
comYesString (Base64)EF.COM
dg1YesString (Base64)DG1, MRZ data
dg2YesString (Base64)DG2, the face image on the chip
dg13YesString (Base64)DG13, citizen information
dg14NoString (Base64)DG14, Chip Authentication information
dg15NoString (Base64)DG15, the Active Authentication public key
challengeNoString (Base64)8 random bytes the client sends to the chip during Active Authentication
aAResultNoString (Base64)The signature returned by the chip for challenge (Active Authentication, SHA256withRSA)
eACCAResultNoString (Base64)Chip Authentication result (EAC-CA)
faceNoString (Base64)Face image decoded from DG2, JPEG
idNoStringCitizen ID number (12 digits)
nameNoStringFull name
dobNoStringDate of birth, dd/MM/yyyy
sexNoStringGender
nationalityNoStringNationality
nationNoStringEthnicity
religionNoStringReligion
homeNoStringPlace of origin
addressNoStringPermanent address
issue-dateNoStringIssue date
expired-dateNoStringExpiry date
featuresNoStringPersonal identification features
prev-numberNoStringPrevious ID card number
father, mother, partnerNoStringFull name of father, mother, spouse
qrcodeNoStringQR string of the citizen ID card (if available)

4.4. Sample request​

Body (file check_chip.json):

{
"id": "0010xxxxxxxx",
"name": "NGUYỄN VĂN A",
"dob": "01/01/1990",
"sex": "Nam",
"nationality": "Việt Nam",
"nation": "Kinh",
"religion": "Không",
"home": "...",
"address": "...",
"issue-date": "01/01/2021",
"expired-date": "01/01/2030",
"features": "...",
"prev-number": "0123456789",
"father": "...",
"mother": "...",
"partner": "",
"qrcode": "...",
"face": "/9j/4AAQSkZJRgABAQ...",
"com": "YBhfAQIwMDAwMDBcBGF1...",
"sod": "d4IG8DCCBuwGCSqGSIb3DQEHAqCC...",
"dg1": "YV1fH1pJRFZOTTAxMDA...",
"dg2": "dYIa0X9hghrLAgEBf2CC...",
"dg13": "bYIBzzCCAcsCAQEwDBMK...",
"dg14": "bh0xGzAZBgoEAH8ABwIC...",
"dg15": "b4IBJDCCASAwDQYJKoZI...",
"challenge": "q83vEjRWeJA=",
"aAResult": "Rk9PQkFSQkFa...",
"eACCAResult": "..."
}

curl command:

curl --location --request POST 'base_url/check_chip' \
--header 'api-key: your-api-key' \
--header 'session-id: 7760a743-884b-4195-b781-892e72c7b0b6' \
--header 'device-type: android' \
--header 'lang: en' \
--header 'sdk-version: 3.7.3.5' \
--header 'manual-time: 15230' \
--header 'Content-Type: application/json; charset=utf-8' \
--data-binary '@check_chip.json'

4.5. Response​

4.5.1. Sample response​

Success: HTTP 200

{
"errorCode": "0",
"errorMessage": "",
"message": "...",
"valid": true,
"verifyCode": "1",
"verifyMessage": "...",
"rar_request_id": "...",
"chip_check": {
"integrity_check": "True",
"clone_check": "True",
"authentication_check": "True"
},
"data": [
{ "key": "id", "name": "Citizen ID number", "value": "0010xxxxxxxx" },
{ "key": "name", "name": "Full name", "value": "NGUYỄN VĂN A" },
...
]
}

4.5.2. Response description​

ParameterData typeDescription
errorCodeString"0": chip data is valid. Any other value: display errorMessage. For details, see the error code documentation
errorMessageStringError message. The language of the message is set by the lang parameter in the headers
actionStringexit: stop the flow
validBooleanChip verification result
verifyCodeString1: verification succeeded. 0: verification failed. 2: not verified
verifyMessageStringVerification message
rar_request_idStringReference ID of the verification request
chip_checkObjectChip check result, containing 3 fields integrity_check, clone_check, authentication_check described below
integrity_checkStringTrue: the data is intact. False: the data is not intact
clone_checkStringTrue: original data, not cloned. False: the data was cloned. N/A: cannot be checked
authentication_checkStringTrue: the data is valid, C06 information verification succeeded. False: the data is invalid, C06 information verification failed. N/A: C06 information has not been verified
dataArrayData on the chip, structure {key, name, value} as in OCR. Prefer displaying this data over the OCR data

5. Liveness and face matching API​

Applies to: full flow, two-step flow (final step). Body type: form-data.

Checks that the person is real (liveness) and matches the face against the portrait photo on the document that was OCR-ed in the session. The payload depends on sdk_config.liveness_type: several selfie images, or one video.

5.1. Request URL​

POST base_url/face/liveness

5.2. Request Headers​

ParameterRequiredData typeDescription
api-keyYesStringAPI key issued by FPT
session-idYesStringSession ID from API 1
device-typeYesStringThe same value used in API 1
mobileNoBooleantrue when sent from a mobile application
submit-resultNoBooleanFlag to submit the session result, send false
langNoStringvi, en
sdk-version, manual-time, x-sdk-trace-id, x-sdk-trace-valuesNoStringSee section Common headers

5.3. Request Body​

FormData, send the parts in exactly the order in the table.

liveness_typePart order
0 PHOTO, 5 FLASH2 selfies parts (2 images: frontal, side)
4 MORE_PHOTO3 selfies parts
2 ACTIVE_VIDEO2 selfies parts for the 2 far-distance images, then near_face (close-up image)
1 VIDEO, 3 RANDOMvideo. near_face can be added before video
PartTypeDescription
selfiesFile, image/jpegSelfie image, recommended at most 1080 × 1440, about 1 MB
near_faceFile, image/jpegClose-up face image
videoFile, video/mp4Face video
cmnd or file[]File, image/jpegFront image of the document for matching (video type or photo type)

5.4. Sample request​

MORE_PHOTO type (3 images):

curl --location --request POST 'base_url/face/liveness' \
--header 'api-key: your-api-key' \
--header 'session-id: 7760a743-884b-4195-b781-892e72c7b0b6' \
--header 'device-type: android' \
--header 'submit-result: false' \
--header 'mobile: true' \
--header 'lang: en' \
--header 'sdk-version: 3.7.3.5' \
--header 'manual-time: 6890' \
--header 'x-sdk-trace-id: 5a8e2c71-3f9d-4b0a-8c6e-1d2f3a4b5c6d' \
--header 'x-sdk-trace-values: <token>' \
--form 'selfies=@"selfie.jpg";type=image/jpeg' \
--form 'selfies=@"selfie_left.jpg";type=image/jpeg' \
--form 'selfies=@"selfie_right.jpg";type=image/jpeg'

VIDEO type:

curl --location --request POST 'base_url/face/liveness' \
--header 'api-key: your-api-key' \
--header 'session-id: 7760a743-884b-4195-b781-892e72c7b0b6' \
--header 'device-type: android' \
--header 'submit-result: false' \
--header 'mobile: true' \
--header 'lang: en' \
--header 'sdk-version: 3.7.3.5' \
--header 'manual-time: 9120' \
--form 'video=@"video.mp4";type=video/mp4'

5.5. Response​

5.5.1. Sample response​

Passed: HTTP 200

{
"code": "200",
"message": "Liveness check successful",
"liveness": {
"code": "200",
"message": "liveness check successful",
"is_live": "true",
"spoof_prob": "N/A",
"need_to_review": "N/A",
"is_deepfake": "N/A",
"deepfake_prob": "N/A",
"warning": "N/A"
},
"face_match": {
"code": "200",
"message": "face matching successful",
"isMatch": "true",
"similarity": "95.84",
"warning": "N/A"
},
"is_complete_session": true
}

5.5.2. Response description​

ParameterData typeDescription
codeString"200": passed. Any other code: failed, display message. For details, see the error code documentation
messageStringMessage in the language of the lang header
actionStringexit: stop the flow immediately, do not allow a retry
livenessObjectcode, message, is_live, spoof_prob, need_to_review, is_deepfake, deepfake_prob, warning, error_frame_url. Values are strings
face_matchObjectcode, message, isMatch, similarity (%), warning
is_complete_sessionBooleantrue: the session has ended and cannot be retried. false: attempts remain (within the limit sdk_config.limit)
video_face_url, compared_face_urlStringThe face image extracted from the selfie or video, and the image used for matching. Can be shown to the user when the result does not pass
public_url, custom_dataStringAdditional data (if any)

5.5.3. Sample error responses​

Face does not match the document: returned when the face in the selfie image does not match the face in the document image.

{
"code": "303",
"message": "The face does not match the document",
"liveness": {
"code": "200",
"is_live": "true",
...
},
"face_match": {
"code": "303",
"isMatch": "false",
"similarity": "5.84",
...
},
"is_complete_session": false
}

Two faces detected: returned when the system detects more than one face in the selfie image.

{
"code": "407",
"message": "2 faces exist",
"liveness": {
"code": "200",
"message": "liveness check successful",
"is_live": "true",
"spoof_prob": "N/A",
"need_to_review": "N/A",
"is_deepfake": "N/A",
"deepfake_prob": "N/A",
"warning": "N/A"
},
"face_match": {
"code": "407",
"message": "2 faces exist",
"isMatch": "N/A",
"similarity": "N/A",
"warning": "N/A"
},
"is_complete_session": false
}

6. Standalone liveness API​

Applies to: liveness-only flow (ekyc_flow = 6). Body type: form-data.

Used for the face-verification-only flow, with no document step.

6.1. Request URL​

POST https://api.fpt.ai/dmp/liveness/v3

When using your own proxy: base_url_proxy/dmp/liveness/v3.

6.2. Request Headers, Request Body​

Identical to API 5: the same headers, and the same way of choosing parts by liveness_type.

6.3. Response​

The structure is the same as API 5. Because there is no document step, the face matching result (face_match) may be absent.

7. Logging and tracing API​

Applies to: SDK when enable_tracing = true. Body type: JSON.

This API is designed for logging and exception tracing on the SDK side. Normally, the SDK does not send any data. Logging is only activated through a configuration on the Portal.

During the session initialization API call, the server may return a flag:

{ "enable_tracing": true }
  • If enable_tracing = true, the SDK starts sending log and exception information through this API.
  • If enable_tracing = false (or the flag is absent), the SDK must not send log and exception information.

This mechanism ensures that logging is only activated for debugging or error cases, allowing developers to collect detailed tracing information without consuming resources during normal operation.

7.1. Request URL​

POST base_url/logging/tracing

7.2. Request Headers​

ParameterRequiredData typeDescriptionNote
session-idNoStringThe unique ID of the eKYC session received from the session initialization step
api-keyYesStringAPI key used to access the FPT AI eKYC system
device-typeNoStringThe device in use. Allowed values: android, ios, web-sdkAdd this parameter if log information is sent from multiple different clients
langNoStringLanguage in use. Allowed values: en, viAdd this parameter if log information is sent from multiple different clients. Default: en
sdk-versionNoStringVersion of the FPT AI eKYC SDKAdd this parameter if log information is sent from multiple different clients
Content-TypeYesStringSpecifies the content type of the request bodyMust be application/json

7.3. Request Body​

ParameterData typeRequiredDescription
eventstringNoName or action of the event (for example: face.liveness.start)
levelstringNoLog level: debug, info, warn, error, fatal
messagestringNoDescriptive message
timestampstring (ISO 8601)NoThe time the event occurred on the client side. The server assigns a value if it is absent
duration_msnumberNoExecution time for timed tasks (milliseconds)
attributesobjectNoA set of custom key-value pairs (for example: device information, user context)
session_metaobjectNoAdditional metadata of the eKYC session, for example {"sdk": "android", "version": "1.2.3"}
trace_idstringNoIdentifier that links tracing information together
span_idstringNoIdentifier of the span within the trace (if any)
exceptionobjectNoDetails of the exception or error (see the table below)

The exception object:

ParameterData typeRequiredDescription
typestringNoException type or class name (for example: NullPointerException)
messagestringNoDescriptive message
stacktracestringNoStack trace information
filestringNoName of the source file where the error occurred
linenumberNoLine number in the file where the error occurred
codestringNoError code (if any)
handledbooleanNotrue if the exception was traced or handled, false if not
detailobjectNoA free-form JSON object containing additional information (if the SDK can send more data)

7.4. Sample request​

curl --location --request POST 'base_url/logging/tracing' \
--header 'api-key: your-api-key' \
--header 'session-id: 7760a743-884b-4195-b781-892e72c7b0b6' \
--header 'Content-Type: application/json' \
--data '{
"event": "face.match",
"level": "error",
"message": "Face match failed on SDK",
"timestamp": "2025-10-03T04:15:23.412Z",
"attributes": {
"device_type": "android",
"os_version": "14",
"sdk_version": "2.4.0"
},
"exception": {
"type": "IllegalArgumentException",
"message": "Invalid image format",
"stacktrace": "com.fpt.sdk.FaceMatch.process(FaceMatch.java:102)\ncom.fpt.sdk.MainActivity.onResult(MainActivity.kt:45)",
"file": "FaceMatch.java",
"line": 102,
"handled": true,
"detail": {}
}
}'

7.5. Response​

7.5.1. Sample response​

Success: http_code = 200

{
"code": "200",
"message": "Success"
}

Failure: http_code != 200

Result handling and error codes​

A response can carry an error at two levels: the HTTP code and the business code in the body. The client needs to check both.

Rules for reading results​

APISuccessful whenNot successful
1. Session initializationcode = "200"Display message, stop the flow
2. OCR, 3. QR, 4. NFCerrorCode = "0"Display errorMessage, let the user redo that step
5, 6. Livenesscode = "200"Display message. Allow a retry if is_complete_session = false

action = "exit" always takes priority

In any API, if the response has action: "exit", the client must stop the flow immediately and not allow a retry, even if the error code looks like a temporary error. Display message to the user and end the flow.

HTTP codes​

HTTPMeaningHandling
200Success or business errorRead the business code in the body
400The data sent is invalidCheck the payload again
401Invalid api-keyStop the flow
403The device is not secure; the session does not exist, has expired, or has endedStop the flow. For liveness, code = "403" means the session has ended
422A required header or field is missingFix the request
500, 502, 503System or gateway error, the body may be empty or HTMLShow "system busy", allow a retry

Common liveness codes​

codeMeaning
200Passed liveness and face matching
303The face does not match the document. The images video_face_url and compared_face_url can be shown to the user
403The session has ended, retry is not possible
Other codesDid not pass liveness, see message and liveness.code

For the full list of OCR and liveness error codes, see the page Error codes of the eKYC flow.

Appendix A. Integration checklist​

Common mistakes when integrating.

No.CheckConsequence if wrong
1Save the session-id from API 1; the two-step flow reuses this session-id and does not call API 1 againStep 2 creates a new session that cannot be linked to the document result of step 1
2Send parts in the exact order of the table in section 2.3 (OCR) and the table in section 5.3 (liveness, by liveness_type)The backend processes the wrong image, for example reading the back side as the front side
3Keep device-type at the same value throughout the sessionThe request is rejected
4The proxy forwards headers containing underscores (client_uuid)The session reconciliation code is lost
5Handle action: "exit" before any other error codeThe user is allowed to retry when the backend has already locked the session
6The api-key is not in public source code or logsSecurity information is exposed