Base endpoint:
Vision Score is called through the eKYC Platform's Service API, so it shares that API's login flow. See Service API — Authentication mechanism for the /auth/users/login request/response reference.
Synchronously runs Vision Score (face liveness + selfie quality) for a set of selfie images. Each image may be supplied inline as base64 or by id (returned from API Upload Image). Both return a Vision Score; this one is JWT/encrypted_token-authenticated and returns the same TS-native application shape used across the rest of the eKYC Platform Service API. You can also call Vision Score standalone via the eKYC Core API endpoint.
| Method | Path | Content-Type | Auth Required |
|---|---|---|---|
| POST | /portal/ekyc/vision_score | application/json | Yes |
| Header key | Header Value | Description |
|---|---|---|
Authorization | Bearer <JWT> | Include the JWT access token to support request authentication |
Request Body:
{
"images": [
{
"id": "8fdcb17e-5d41-4814-8d4f-8c1bfb51df20"
},
{
"base64": "<base64_string>",
"metadata": "{\"frame_index\":1}"
}
],
"gesture_images": [
{
"id": "fa9a4fee-41c3-4988-a126-1431c130aa94"
}
],
"videos": [
{
"id": "b5a23e55-773f-4cf2-985b-835b9b3d0077"
}
],
"metadata": {
"applicant_name": "e3b0c44298fc1c149afb...a1b2c3",
"id_number": "a665a45920422f9d417e...f4c8b1",
"dob": "9f86d081884c7d659a2f...3d5c2a",
"gender": "male",
"phone_number": "b14a7b8059d9c055954c...8e7f12",
"email": "2cf24dba5fb0a30e26e8...9d1c4a",
"application_time": "2025-05-02T10:30:00+07:00",
"loan_amount": 5000000,
"loan_currency": "VND",
"loan_product_type": "personal_loan",
"acquisition_channel": "agent",
"is_existing_customer": false,
"device_type": "android",
"device_model": "Samsung Galaxy A54"
},
"selfie_type": "passive",
"flow_id": "vision_score_only",
"lender_id": 0
}
| Name | Type | Required | Description |
|---|---|---|---|
images | []ImageData | Yes | Selfie images. The last element is treated as the primary selfie. |
gesture_images | []ImageData | No | Gesture (active liveness) images. |
videos | []ImageData | No | Video frames captured during the liveness flow. |
metadata | json | No | Contextual data about the loan application and applicant. See Metadata Reference. |
selfie_type | string | No | Selfie liveness type. Common values: passive, flash, active. |
flow_id | string | No | Routes the request to the lender config mapped to this flow via the encryption key's flow_lender_config_mapping. Defaults to vision_score_only. |
lender_id | int | No | Admin-only override that scopes the call to a specific tenant. Non-admin callers always resolve the lender from their JWT and any supplied value is ignored. |
Where each ImageData object accepts either id (preferred — references a pre-uploaded image) or base64 (inline upload):
| Name | Type | Required | Description |
|---|---|---|---|
id | string | One of | image_id returned by API Upload Image. Use this to avoid re-sending large payloads. |
base64 | string | One of | Base64-encoded image content without the data:image/jpeg;base64, prefix. Required when id is not supplied. |
metadata | string | No | Per-image metadata string forwarded to eKYC. Example: "{\"frame_index\":0}". |
| Mode | images | gesture_images | videos | selfie_type | flow_id |
|---|---|---|---|---|---|
| Vision Score only | 1 image | — | — | — | vision_score_only |
| Vision Score with Passive Liveness | 1–3 images | — | optional | passive | vision_score_sanity_liveness |
The metadata field accepts a dictionary of contextual data associated with the loan application and applicant. Providing this information enables TS to improve model performance over time and unlocks eligibility for model fine-tuning. All fields are optional. Keys outside the list below are accepted and stored but carry no guaranteed semantic interpretation.
| Key | Type | Encrypt | Description |
|---|---|---|---|
applicant_name | string | Yes | Full name as captured from ID document |
id_number | string | Yes | National ID or passport number |
dob | string | Yes | Date of birth, ISO 8601 format (e.g. "1990-03-15") |
gender | string | No | Accepted values: "male", "female", "other" |
phone_number | string | Yes | Registered mobile number in E.164 format (e.g. "+84901234567") |
email | string | Yes | Applicant email address |
| Key | Type | Encrypt | Description |
|---|---|---|---|
application_time | string | No | ISO 8601 timestamp of when the loan application was submitted (e.g. "2025-05-02T10:30:00+07:00") |
loan_amount | float | No | Requested loan amount in local currency |
loan_currency | string | No | ISO 4217 currency code (e.g. "VND", "PHP", "USD") |
loan_product_type | string | No | Product category (e.g. "personal_loan", "bnpl", "salary_advance", "sme_loan") |
acquisition_channel | string | No | Channel through which the applicant was acquired (e.g. "organic", "agent", "referral", "digital_ads") |
is_existing_customer | bool | No | Whether the applicant is an existing borrower |
| Key | Type | Encrypt | Description |
|---|---|---|---|
device_type | string | No | Accepted values: "android", "ios", "web" |
device_model | string | No | Device model name (e.g. "Samsung Galaxy A54") |
PII fields below are shown as SHA-256 hashes. Plain-text values must never be transmitted.
{
"metadata": {
"applicant_name": "e3b0c44298fc1c149afb...a1b2c3",
"id_number": "a665a45920422f9d417e...f4c8b1",
"dob": "9f86d081884c7d659a2f...3d5c2a",
"gender": "male",
"phone_number": "b14a7b8059d9c055954c...8e7f12",
"email": "2cf24dba5fb0a30e26e8...9d1c4a",
"application_time": "2025-05-02T10:30:00+07:00",
"loan_amount": 5000000,
"loan_currency": "VND",
"loan_product_type": "personal_loan",
"acquisition_channel": "agent",
"is_existing_customer": false,
"device_type": "android",
"device_model": "Samsung Galaxy A54"
}
}
The tables below describe the richer
image,video,FrameData, andgesture_imageobject shapes and theselfie_typeenum values shared with the standalone Vision Score endpoint in the eKYC Core API. This Service-API endpoint's own request format is simpler (see Where above), but these definitions are provided for reference when working across both endpoints.
Which each image's parameters contains:
| Name | Type | Required | Description |
|---|---|---|---|
id | string | No | ID of image is returned from API Upload |
base64 | string | No | base64 encoded text of image data |
label | string | No | label of the image as described at Label table |
metadata | string_json | No | key-value string, key should be string, value should be string, int, float, bool. Example "{\"id\":\"123456789\",\"type\":1}" |
Which each video's parameters contains:
| Name | Type | Required | Description |
|---|---|---|---|
id | string | No | ID of video is returned from API Upload |
metadata | string_json | No | key-value string, key should be string, value should be string, int, float, bool. Example "{\"id\":\"123456789\",\"type\":1}" |
frames | []FrameData | No | list of video frames to be uploaded |
Which FrameData's parameters contains:
| Name | Type | Required | Description |
|---|---|---|---|
base64 | string | No | base64 encoded text of image data |
label | string | No | label of the image as described at Label table |
index | int | No | the index of this frame in the video |
metadata | string_json | No | key-value string, key should be string, value should be string, int, float, bool. Example "{\"id\":\"123456789\",\"type\":1}" |
Which each gesture_image's parameters contains:
| Name | Type | Required | Description |
|---|---|---|---|
gesture | string | Yes | specify a gesture value as below table. Example left, right, up or down |
images | []image | Yes | an array of images for each gesture |
Which gesture parameter contains value:
| Gestures | Description |
|---|---|
left | the face turns left |
right | the face turns left |
up | the face up |
down | the face down |
selfie_type parameter is defined as below table valuesselfie_type | Description |
|---|---|
passive | Passive mode |
active | Active mode |
flash | Flash mode with 32 frames |
flash_8 | Flash mode with 8 frames |
flash_16 | Flash mode with 16 framess |
flash_32 | Flash mode with 32 frames |
| Name | Description | Status code | Response Body |
|---|---|---|---|
| Success | request successfully | 200 | See the sample success response below |
| Bad Request | Invalid parameters / missing config | 400 | {"data":{},"message":"eKYC platform URL not configured","verdict":"failure"} |
| Unauthorized | Invalid authentication token | 401 | {"data":{},"message":"authorization header is missing","time":"2024-10-31T07:50:22Z","verdict":"missing_authorization"} |
| Token Expired | Token expired. Client should login again | 401 | {"data":{},"message":"Provided token is expired","time":"2024-10-31T07:50:22Z","verdict":"expired_token"} |
| Bad Gateway | Upstream Platform Init / Verify failed | 502 | {"data":{},"message":"platform init failed: ...","verdict":"failure"} / {"data":{},"message":"platform verify selfie failed: ...","verdict":"failure"} |
| Internal Server Error | Cannot create encrypted token | 500 | {"data":{},"message":"cannot create encrypted token","verdict":"failure"} |
On success case: 200 OK
The success response follows this structure:
{
"data": {
"unique_token": string, // eKYC application unique token (issued by Platform Init)
"verify_response": { // upstream Platform Verify Face Liveness result
"status": string, // "success" | "failure"
"application_state": {
"current_step": string, // current step in the eKYC flow, e.g. "success"
"fully_completed": bool, // true when every required state is completed
"required_states": [string], // states the flow requires the user to complete
"all_completed_states": [string], // states the user has completed so far
"client_custom_settings": {
"allow_card_types": [string], // card types permitted for this flow
"redirect_url": string // post-flow redirect URL, if configured
}
}
},
"application": { // optional — present only when the application is found
"verdict": string, // "pending" | "approve" | "review" | "reject"
"application_id": int,
"application_unique_token": string,
"application_user_id": string,
"ekyc_face": {
"vision_score": number, // range 0–100; higher = more trustworthy / lower risk
"vision_score_status": string, // "success" | "failure"
"vision_score_request_id": string, // request id for tracing Vision Score logs
"liveness_score": number, // passive-liveness score, range 0–1
"liveness_status": string, // "success" | "failure"
"sanity_check_score": number, // selfie quality score
"sanity_check_result": string, // selfie quality verdict, e.g. "good"
"sanity_check_status": string, // "success" | "failure"
"search_face_result": object, // face-search hits; null when disabled
"search_face_status": string, // e.g. "found"; "" when disabled
"process_status": string, // "success" | "failure"
"processed_at": string, // ISO 8601 timestamp
"selfie_image_uuid": string, // image id of the primary selfie
"gesture_images": [ { "gesture": string, "image_id": string } ],
"sequence_frames": [ ... ], // liveness video frames
"liveness_transformed_video_id": string // video merged from sequence frames
},
"file_ids": { // image/video ids captured during the flow
"selfie_image_uuid": string,
"gesture_images": [ { "gesture": string, "image_id": string } ],
"sequence_frames": [ ... ],
"liveness_transformed_video_id": string
},
"time": string // ISO 8601 timestamp
}
},
"message": string, // human-readable status, e.g. "vision score check completed"
"time": string, // ISO 8601 timestamp of the response
"verdict": string // "success" on HTTP 200; see the error table above for other values
}
The scoring outputs of this endpoint live under application.ekyc_face. vision_score is the primary output; the liveness and sanity-check fields describe the quality of the captured selfie.
vision_score (0–100). A higher score means a more trustworthy / lower-risk profile. It is computed independently of the liveness result — a failed liveness check does not necessarily lower the Vision Score.| Group | Field | Type | Description |
|---|---|---|---|
| Vision Score | vision_score | number | Vision Score, range 0–100. Higher = more trustworthy / lower risk. |
| Vision Score | vision_score_status | string | "success" when the score was computed, otherwise "failure". |
| Vision Score | vision_score_request_id | string | Request id used to trace Vision Score processing in logs. |
| Liveness | liveness_score | number | Passive-liveness score, range 0–1. |
| Liveness | liveness_status | string | "success" or "failure". |
| Sanity check | sanity_check_score | number | Selfie quality score. |
| Sanity check | sanity_check_result | string | Selfie quality verdict, e.g. "good". |
| Sanity check | sanity_check_status | string | "success" or "failure". |
{
"data": {
"application": {
"application_id": 100010221,
"application_unique_token": "7ae4e342-6cec-4a4e-bd6e-831784391d40",
"application_user_id": "5b506b16-7928-44ea-b690-1ef61d39ed08",
"ekyc_face": {
"gesture_images": [
{
"gesture": "frontal",
"image_id": "186c1ca9-cc99-4fc7-8742-036a2ea8e244"
}
],
"liveness_score": 0.99999034,
"liveness_status": "success",
"liveness_transformed_video_id": "",
"process_status": "success",
"processed_at": "2026-06-17T03:17:22Z",
"sanity_check_result": "good",
"sanity_check_score": 1,
"sanity_check_status": "success",
"search_face_result": null,
"search_face_status": "",
"selfie_image_uuid": "186c1ca9-cc99-4fc7-8742-036a2ea8e244",
"sequence_frames": [],
"vision_score": 86,
"vision_score_request_id": "eec55581-33a0-4d3a-8247-81202609f75b",
"vision_score_status": "success"
},
"file_ids": {
"gesture_images": [
{
"gesture": "frontal",
"image_id": "186c1ca9-cc99-4fc7-8742-036a2ea8e244"
}
],
"liveness_transformed_video_id": "",
"selfie_image_uuid": "186c1ca9-cc99-4fc7-8742-036a2ea8e244",
"sequence_frames": []
},
"time": "2026-06-17T03:17:22Z",
"verdict": "approve"
},
"unique_token": "7ae4e342-6cec-4a4e-bd6e-831784391d40",
"verify_response": {
"application_state": {
"all_completed_states": ["ekyc.selfie.passive_v2"],
"client_custom_settings": {
"allow_card_types": ["vn.national_id"],
"redirect_url": ""
},
"current_step": "success",
"fully_completed": true,
"required_states": ["ekyc.selfie.passive_v2"]
},
"status": "success"
}
},
"message": "vision score check completed",
"time": "2026-06-17T03:17:22Z",
"verdict": "success"
}
| Name | Type | Description | |
|---|---|---|---|
unique_token | string | The eKYC application unique token issued by Platform Init. Use it to look up the application via API Application Detail. | |
verify_response | object | The data block returned by the upstream Platform Verify Face Liveness API — same shape as the Selfie Verification API response. | |
application | object | Optional — only present when an application matching unique_token is found for the lender. Same shape as the Application Detail response. | |
application.verdict follows the same mapping as API Application Detail (pending | approve | review | reject). |
The scoring outputs under application.ekyc_face are summarized in Key Result Fields above.
The remaining ekyc_face and file_ids fields (compare_score, search_face_result, gesture_images, sequence_frames, liveness_transformed_video_id, …) follow the same shape as the API Application Detail response.
verify_response.application_state reports the flow's progress:
| Name | Type | Description |
|---|---|---|
current_step | string | Current step in the eKYC flow, e.g. "success". |
fully_completed | bool | true when every required state has been completed. |
required_states | []string | States the flow requires the user to complete. |
all_completed_states | []string | States the user has completed so far. |
client_custom_settings | object | Per-client flow settings, e.g. allow_card_types, redirect_url. |