• eKYC Platform
    About TrustVision
TrustVision API Documentation

Standard API

Authentication mechanism

We use AES encryption to encrypt user identify of client and send it to eKYC Platform.

How it works:

1. AES key base64

A pair of:

  • Key: 256 bits (32 bytes)

  • IV (initialization vector): 16 bytes

TrustVision team will provide an (key, iv) pair and client_code for each customers before integration.

2. Client encrypt the user identify to do eKYC journey.

  • Encrypt user identify use AES-256 CBC with padding PKCS7.
  • Encode the Base64 URL of the above encrypted value.
  • Concatenate encrypted value with client_code.
  • Encode Base64 URL of the above-concatenated value.
  • Use the encoded value to send it to the eKYC Platform.

eg.

  1. Use Web or WebView via link
    Philippines: https://ekyc-platform-ph-staging.trustingsocial.com/lu/[ENCODED_VALUE]
    Vietnam: https://ekyc-platform-vn-staging.trustingsocial.com/lu/[ENCODED_VALUE]
    India: https://ekyc-platform-in-staging.trustingsocial.com/lu/[ENCODED_VALUE]
    Mexico: https://ekyc-platform-mx-staging.trustingsocial.com/lu/[ENCODED_VALUE]
  2. Pass [ENCODED_VALUE] to init iOS/Android/Flutter/ReactNative SDK.

Pseudo code:

UserID    = "user_id"    // client-generated value
KeyBytes  = Base64StdDecode(aesKey) // Base64 Standard Decoding
IVBytes   = Base64StdDecode(aesIV)  // Base64 Standard Decoding
// Step 1
EncryptedUserID    = AES256CbcPkcs7PaddingEncrypt(UserID, KeyBytes, IVBytes)
// Step 2
EncodedBase64EncryptedValue = Base64UrlEncode(EncryptedUserID)
// Step 3
UniqueToken        = KeyID + ":" + EncryptedUserID
// Step 4
EncodedBase64Token = Base64UrlEncode(UniqueToken)  // Base64 URL Encoding
URL                = "https://ekyc-platform-ph-staging.trustingsocial.com/lu/" + EncodedBase64

Sample code:

Reference:

go
package main

import (
    "crypto/aes"
    "crypto/cipher"
    "encoding/base64"
    "fmt"

    "github.com/zenazn/pkcs7pad"
)

func main() {

    key := "YOUR_KEY"
    iv := "YOUR_IV"
    keyID := "YOUR_KEY_ID"
    url := "YOUR_URL"

    keyBytes, err := base64.StdEncoding.DecodeString(key)
    if err != nil {
        panic(err)
    }

    ivBytes, err := base64.StdEncoding.DecodeString(iv)
    if err != nil {
        panic(err)
    }

    block, err := aes.NewCipher(keyBytes)
    if err != nil {
        panic(err)
    }

    userID := "PLiOA_EdK3xx1M4sJPsOlQZqgX"
    // Step 1: Padding userID and encrypt
    contentPadding := pkcs7pad.Pad([]byte(userID), block.BlockSize())
    encryptedValue := make([]byte, len(contentPadding))
    blockMode := cipher.NewCBCEncrypter(block, ivBytes)
    blockMode.CryptBlocks(encryptedValue, contentPadding)

    // Step 2: Encode encrypted value to base64
    encodedBase64EncryptedValue := base64.RawURLEncoding.EncodeToString(encryptedValue)
    // Step 3: Generate unique token
    uniqueToken := keyID + ":" + encodedBase64EncryptedValue
    // Step 4: Encode unique token to base64
    encodedBase64Token := base64.RawURLEncoding.EncodeToString([]byte(uniqueToken))
    fmt.Println(url + "/" + encodedBase64Token)
}

Portal API

API Authentication mechanism

1. Request

MethodPathContent-TypeRequest Body
POST/auth/users/loginapplication/json{"user_name":"user_name","password":"password"}

2. Response

NameDescriptionStatus codeResponse Body
SuccessLogin successfully200{"data":{"access_token":"<JWT>"},"message":"login success","time":"2024-10-31T07:34:52Z","verdict":"success"}
Bad RequestInvalid username pattern400{"data":{"invalid_parameters":["user_name"],"parameter_formats":{"user_name":"[regex: ^[a-z0-9_]{1,63}$]"}},"message":"some parameters has invalid format","time":"2024-11-06T03:30:42Z","verdict":"invalid_parameters"}
UnauthorizedIncorrent username or password401{"data":{},"message":"username or password is incorrect","time":"2024-10-31T07:50:22Z","verdict":"invalid_credential"}
Invalid MethodInvalid URL or method405{"data":{},"message":"method is not correct for the requested route","time":"2024-11-06T03:23:26Z","verdict":"invalid_method"}
Too many requestsToo many failed login attempts429{"data":{},"message":"too many failed login attempts","time":"2024-11-06T03:33:43Z","verdict":"limit_exceeded"}
Internal Server ErrorInternal Server Error500{"data":{},"message":"Unexpected error. Error ID: ff4d5572d9df4560bb6dabc2eadd5a1e","time":"2024-11-06T03:32:09Z","verdict":"failure"}

API Application Detail

1. Request

MethodPathContent-TypeRequest Body
GET/portal/application/detail

Headers:

Header keyHeader ValueDescription
AuthorizationBearer <JWT>Include the JWT access token to support request authentication

Query Params:

Parameter keyDescription
application_idcorressponding with the application_id in the callback result
idcorressponding with the application_user_id in the callback result

2. Response

NameDescriptionStatus codeResponse Body
Successrequest successfully200See the sample success response below
Bad RequestInvalid parameters400{"data":{},"message":"","verdict":"invalid_parameters"}
UnauthorizedInvalid authentication token401{"data":{},"message":"authorization header is missing","time":"2024-10-31T07:50:22Z","verdict":"missing_authorization"}
Token ExpiredToken expired. Client should login again401{"data":{},"message":"Provided token is expired","time":"2024-10-31T07:50:22Z","verdict":"expired_token"}
Not foundNot found404{"data":{},"message":"applications not available","time":"2024-10-31T08:27:27Z","verdict":"record_not_found"}
Invalid MethodInvalid URL or method405{"data":{},"message":"method is not correct for the requested route","time":"2024-11-06T03:23:26Z","verdict":"invalid_method"}
Too many requestsToo many failed login attempts429{"data":{},"message":"too many failed login attempts","time":"2024-11-06T03:33:43Z","verdict":"limit_exceeded"}
Internal Server ErrorInternal Server Error500{"data":{},"message":"Unexpected error. Error ID: ff4d5572d9df4560bb6dabc2eadd5a1e","time":"2024-11-06T03:32:09Z","verdict":"failure"}

On success case: 200 OK

JSON Response

The response payload varies based on the flow configuration. Select a scenario below to see the relevant fields.

{
  "data": {
    "verdict": "pending|approve|review|reject",
    "application_id": 10,
    "application_unique_token": "ad083945-9620-486c-be7d-c3f5dab870a1",
    "application_user_id": "01234567889",
    "file_ids": {
      "id_front_img_uuid": "8fdcb17e-5d41-4814-8d4f-8c1bfb51df20",
      "id_back_img_uuid": "93cef1cd-2782-4264-9c65-cce78ce3f549",
      "qr1_img_uuid": ""
    },
    "ekyc_ocr": {
      "card_info": {
        "card_info": [
          { "confidence_score": 0, "confidence_verdict": "", "field": "crn", "value": "0123456789" },
          { "confidence_score": 0, "confidence_verdict": "", "field": "last_name", "value": "SANTOS" },
          { "confidence_score": 0, "confidence_verdict": "", "field": "first_name", "value": "JOSE" },
          { "confidence_score": 0, "confidence_verdict": "", "field": "middle_name", "value": "CRUZ" },
          { "confidence_score": 0, "confidence_verdict": "", "field": "date_of_birth", "value": "1990/01/01" },
          { "confidence_score": 0, "confidence_verdict": "", "field": "gender", "value": "F" },
          { "confidence_score": 0, "confidence_verdict": "", "field": "address", "value": "CABAROAN SAN ESTEBAN ILOCOS SUR PHL 2706" },
          { "confidence_score": 0, "confidence_verdict": "", "field": "id", "value": "0123456789" },
          { "confidence_score": 0, "confidence_verdict": "", "field": "card_type", "value": "ph.ump" },
          { "confidence_score": 0, "confidence_verdict": "", "field": "card_label", "value": "ph.ump.front" },
          { "confidence_score": 0, "confidence_verdict": "", "field": "name", "value": "JOSE CRUZ SANTOS" }
        ],
        "converted_card_info": {
          "address": "CABAROAN SAN ESTEBAN ILOCOS SUR PHL 2706",
          "birth_date": "1990/01/01",
          "card_label": "ph.ump.front",
          "card_type": "ph.ump",
          "gender": "F",
          "id_number": "0123456789",
          "name": "JOSE CRUZ SANTOS"
        }
      },
      "process_status": "success",
      "processed_at": "2024-09-26T18:11:29+07:00",
      "sanity_check_result": "good",
      "sanity_check_score": 1,
      "sanity_check_status": "success",
      "search_face_result": null,
      "search_face_status": "disabled"
    },
    "id_tampering": {
      "score": 1,
      "verdict": "good"
    },
    "form_data": {
      "contactInfo": {
        "familyAddress": "CABAROAN SAN ESTEBAN ILOCOS SUR PHL 2706"
      },
      "personalInfo": {
        "birthday": "1990/01/01",
        "fullName": "JOSE CRUZ SANTOS",
        "gender": "",
        "idCard": "0123456789"
      }
    },
    "time": "2024-09-27T09:50:32+07:00"
  }
}

Which verdict response mapping:

verdictDescription
pendingThe user has not completed the flow
approveOur recommendation to approve this application
reviewOur recommendation to review this application
rejectOur recommendation to reject this application

Which file_ids response mapping:

keyDescriptionPresent in
id_front_img_uuidThe image id of front sideID Only, Full Flow
id_back_img_uuidThe image id of back sideID Only, Full Flow
qr1_img_uuidThe image id of QR codeID Only, Full Flow
selfie_image_uuidThe image id of selfie that use to compare facesSelfie Only, Full Flow
gesture_imagesList images during user do livenessSelfie Only, Full Flow
sequence_framesList frames video during user do livenessSelfie Only, Full Flow
liveness_transformed_video_idThe video id merge from sequence framesSelfie Only, Full Flow

Which form_data response object:

json
{
  "contactInfo": {
    "familyAddress": "CABAROAN SAN ESTEBAN ILOCOS SUR PHL 2706"
  },
  "personalInfo": {
    "birthday": "1990/01/01",
    "fullName": "JOSE CRUZ SANTOS",
    "gender": "",
    "idCard": "0123456789"
  }
}

API Download file

1. Request

MethodPathContent-TypeRequest Body
GET/portal/document/download_ekyc_file

Headers:

Header keyHeader ValueDescription
AuthorizationBearer <JWT>Include the JWT access token to support request authentication

Query Params:

Parameter keyDescription
application_idcorressponding with the application_id in the callback result
idcorressponding with the file_ids in the callback result

2. Response

NameDescriptionStatus codeResponse Body
Successrequest successfully200See the sample success response below
Bad RequestInvalid parameters400{"data":{},"message":"","verdict":"invalid_parameters"}
UnauthorizedInvalid authentication token401{"data":{},"message":"authorization header is missing","time":"2024-10-31T07:50:22Z","verdict":"missing_authorization"}
Token ExpiredToken expired. Client should login again401{"data":{},"message":"Provided token is expired","time":"2024-10-31T07:50:22Z","verdict":"expired_token"}
Not foundNot found404{"data":{},"message":"applications not available","time":"2024-10-31T08:27:27Z","verdict":"record_not_found"}
Invalid MethodInvalid URL or method405{"data":{},"message":"method is not correct for the requested route","time":"2024-11-06T03:23:26Z","verdict":"invalid_method"}
Too many requestsToo many failed login attempts429{"data":{},"message":"too many failed login attempts","time":"2024-11-06T03:33:43Z","verdict":"limit_exceeded"}
Internal Server ErrorInternal Server Error500{"data":{},"message":"Unexpected error. Error ID: ff4d5572d9df4560bb6dabc2eadd5a1e","time":"2024-11-06T03:32:09Z","verdict":"failure"}

On success case: the HTTP status code will be 200, and server will send the file as binary content.

API Index Faces

1. Request

MethodPathContent-Type
POST/portal/ekyc/index_facesapplication/json

Headers:

Header keyHeader ValueDescription
AuthorizationBearer <JWT>Include the JWT access token to support request authentication

Body:

JSON
{
    "image_base64": string,
    "collection": string,
    "label": string,
    "metadata": JSON
}

Where

keytyperequireddescription
image_base64stringyesencode base64 of binary image without prefix eg. data:image/jpeg;base64,
labelstringyeslabel of the image as described at Label table
metadatajsonyesmore information of image. eg client_uid, phone_number, dob, name ... client_uid should be not empty
collectionstringnoindex faces to this collection. case-sensitive

Which each metadata's parameters contains:

keytyperequireddescription
client_uidstringyesID of an user or application or any unique token
national_idstringnoid number of ID Card of an user
unique_tokenstringnothe token application_unique_token from application detail

2. Response

NameDescriptionStatus codeResponse Body
Successrequest successfully200See the sample success response below
Bad RequestInvalid parameters400{"data":{},"message":"","verdict":"invalid_parameters"}
UnauthorizedInvalid authentication token401{"data":{},"message":"authorization header is missing","time":"2024-10-31T07:50:22Z","verdict":"missing_authorization"}
Token ExpiredToken expired. Client should login again401{"data":{},"message":"Provided token is expired","time":"2024-10-31T07:50:22Z","verdict":"expired_token"}
Not foundNot found404{"data":{},"message":"applications not available","time":"2024-10-31T08:27:27Z","verdict":"record_not_found"}
Invalid MethodInvalid URL or method405{"data":{},"message":"method is not correct for the requested route","time":"2024-11-06T03:23:26Z","verdict":"invalid_method"}
Too many requestsToo many failed login attempts429{"data":{},"message":"too many failed login attempts","time":"2024-11-06T03:33:43Z","verdict":"limit_exceeded"}
Internal Server ErrorInternal Server Error500{"data":{},"message":"Unexpected error. Error ID: ff4d5572d9df4560bb6dabc2eadd5a1e","time":"2024-11-06T03:32:09Z","verdict":"failure"}

On success case: the HTTP status code will be 200, and server will send the file as binary content.

API eKYC full checks

1. Request

MethodPathContent-Type
POST/portal/ekyc/full_checksapplication/json

Headers:

Header keyHeader ValueDescription
AuthorizationBearer <JWT>Include the JWT access token to support request authentication

Body:

JSON
{
  "card_type": string,
  "image1": {
    "base64": string,
    "label": string,
    "metadata": JSON
  },
  "image2": {
    "base64": string,
    "label": string,
    "metadata": JSON
  },
  "selfies": [
    {
      "base64": string,
      "label": string,
      "metadata": JSON
    },
    ...
    {
      "base64": string,
      "label": string,
      "metadata": JSON
    }
  ]
  "metadata": JSON
}

Where

keytyperequireddescription
`card_typestringyescard type of the image as described at Card types table
image1ImageDatayesimage of the identity card's front side
image2ImageDatanoimage of the identity card's back side
selfies[]ImageDatanolist selfie images of customer. Max 3 images
metadatajsonyesmore information of identity. eg client_uid, phone_number, dob, name ... client_uid should be not empty

Which each image's parameters contains:

keytyperequireddescription
base64stringyesbase64 encoded text of image data
labelstringnolabel of the image as described at Label table
metadatajsonnokey-value, key should be string, value should be string, int, float, bool. Example "{\"id\":\"123456789\",\"type\":1}"

Which each metadata's parameters contains:

keytyperequireddescription
client_uidstringyesID of an user or application or any unique token
national_idstringnoid number of ID Card of an user
unique_tokenstringnothe token application_unique_token from application detail

2. Response

NameDescriptionStatus codeResponse Body
Successrequest successfully200See the sample success response below
Bad RequestInvalid parameters400{"data":{},"message":"","verdict":"invalid_parameters"}
UnauthorizedInvalid authentication token401{"data":{},"message":"authorization header is missing","time":"2024-10-31T07:50:22Z","verdict":"missing_authorization"}
Token ExpiredToken expired. Client should login again401{"data":{},"message":"Provided token is expired","time":"2024-10-31T07:50:22Z","verdict":"expired_token"}
Invalid MethodInvalid URL or method405{"data":{},"message":"method is not correct for the requested route","time":"2024-11-06T03:23:26Z","verdict":"invalid_method"}
Too many requestsToo many failed login attempts429{"data":{},"message":"too many failed login attempts","time":"2024-11-06T03:33:43Z","verdict":"limit_exceeded"}
Internal Server ErrorInternal Server Error500{"data":{},"message":"Unexpected error. Error ID: ff4d5572d9df4560bb6dabc2eadd5a1e","time":"2024-11-06T03:32:09Z","verdict":"failure"}

On success case: 200 OK

JSON Response

json
{
  "data": {
    "request_id": "538cdfe9-7009-4bcf-b514-6faf8904e82c",
    "card_information": [
      {
        "field": "id",
        "value": "123456789012",
        "confidence_verdict": "",
        "confidence_score": 0
      },
      {
        "field": "name",
        "value": "FULL NAME",
        "confidence_verdict": "",
        "confidence_score": 0
      },
      {
        "field": "date_of_birth",
        "value": "2005/03/02",
        "confidence_verdict": "",
        "confidence_score": 0
      },
      {
        "field": "gender",
        "value": "F",
        "confidence_verdict": "",
        "confidence_score": 0
      },
      {
        "field": "address",
        "value": "RD 1000 NDC COMPOUND,, SITH DISTRICT",
        "confidence_verdict": "",
        "confidence_score": 0
      },
      {
        "field": "last_name",
        "value": "LAST NAME",
        "confidence_verdict": "",
        "confidence_score": 0
      },
      {
        "field": "first_name",
        "value": "FIRST NAME",
        "confidence_verdict": "",
        "confidence_score": 0
      },
      {
        "field": "middle_name",
        "value": "MIDDLE NAME",
        "confidence_verdict": "",
        "confidence_score": 0
      },
      {
        "field": "card_type",
        "value": "ph.philhealth",
        "confidence_verdict": "",
        "confidence_score": 0
      },
      {
        "field": "card_label",
        "value": "ph.philhealth.qr.front",
        "confidence_verdict": "",
        "confidence_score": 0
      }
    ],
    "compare_faces": [
      {
        "face1_id": "7d71e6e2-a6bb-4ea6-8826-be0947afd6f4",
        "face2_id": "1647fae2-7ff2-4500-89cc-4196e42512ce",
        "result": "matched",
        "score": 0.8136464
      }
    ],
    "liveness_check": {
      "is_live": true,
      "score": 0.99999917
    },
    "portrait_sanity": {
      "verdict": "good",
      "score": 1
    },
    "card_sanity": {
      "verdict": "photo_not_qualified",
      "score": 0.8199463,
      "score_details": [
        {
          "verdict": "good",
          "score": 1
        },
        {
          "verdict": "photo_not_qualified",
          "score": 0.8199463
        }
      ],
      "verdict_details": {}
    },
    "card_tampering": {
      "verdict": "alert",
      "score": 1,
      "details": [
        {
          "verdict": "non_liveness",
          "score": 0.99,
          "name": "alert_1",
          "info": "image1"
        }
      ]
    }
  },
  "message": "fully check eKYC successfully",
  "time": "2026-01-27T18:03:56+07:00",
  "verdict": "success"
}

Which verdict response mapping:

verdictDescription
successProcess the request successfully.
failureProcess the request failure.

API Upload Image

Uploads a single image file to eKYC and returns the image_id that can be referenced from API Vision Score's images[].id (and other batch endpoints) instead of re-sending the bytes.

1. Request

MethodPathContent-Type
POST/portal/ekyc/upload/imagemultipart/form-data

Headers:

Header keyHeader ValueDescription
AuthorizationBearer <JWT>Include the JWT access token to support request authentication

Form fields:

FieldTypeRequiredDescription
filefileyesImage file. Must be non-empty. Maximum size is governed by the server's tv_max_file_size setting.
labelstringnoImage label as described at Label table.
metadatastringnoJSON-encoded string with per-image metadata.

2. Response

json
{
  "data": {
    "image_id": "8fdcb17e-5d41-4814-8d4f-8c1bfb51df20"
  },
  "message": "image uploaded",
  "time": "2026-05-04T09:00:00+07:00",
  "verdict": "success"
}

API Vision Score

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).

1. Request

MethodPathContent-Type
POST/portal/ekyc/vision_scoreapplication/json

Headers:

Header keyHeader ValueDescription
AuthorizationBearer <JWT>Include the JWT access token to support request authentication

Body:

json
{
  "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
}

Where

keytyperequireddescription
images[]ImageDatayesSelfie images. The last element is treated as the primary selfie.
gesture_images[]ImageDatanoGesture (active liveness) images.
videos[]ImageDatanoVideo frames captured during the liveness flow.
metadatajsonnoContextual data about the loan application and applicant. See Metadata Reference.
selfie_typestringnoSelfie liveness type. Common values: passive, flash, active.
flow_idstringnoRoutes 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_idintnoAdmin-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):

keytyperequireddescription
idstringone ofimage_id returned by API Upload Image. Use this to avoid re-sending large payloads.
base64stringone ofBase64-encoded image content without the data:image/jpeg;base64, prefix. Required when id is not supplied.
metadatastringnoPer-image metadata string forwarded to eKYC. Example: "{\"frame_index\":0}".

Modes

Modeimagesgesture_imagesvideosselfie_typeflow_id
Vision Score only1 imagevision_score_only
Vision Score with Passive Liveness1–3 imagesoptionalpassivevision_score_sanity_liveness

Metadata Reference

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.

PII Notice: Fields marked Encrypt: Yes contain personally identifiable information. Data must be hashed (SHA-256) or encrypted (AES-256) before transmission, in compliance with applicable data protection regulations (GDPR, PDPA, and equivalent local laws).
Applicant Identity
KeyTypeEncryptDescription
applicant_namestringYesFull name as captured from ID document
id_numberstringYesNational ID or passport number
dobstringYesDate of birth, ISO 8601 format (e.g. "1990-03-15")
genderstringNoAccepted values: "male", "female", "other"
phone_numberstringYesRegistered mobile number in E.164 format (e.g. "+84901234567")
emailstringYesApplicant email address
Loan Application Context
KeyTypeEncryptDescription
application_timestringNoISO 8601 timestamp of when the loan application was submitted (e.g. "2025-05-02T10:30:00+07:00")
loan_amountfloatNoRequested loan amount in local currency
loan_currencystringNoISO 4217 currency code (e.g. "VND", "PHP", "USD")
loan_product_typestringNoProduct category (e.g. "personal_loan", "bnpl", "salary_advance", "sme_loan")
acquisition_channelstringNoChannel through which the applicant was acquired (e.g. "organic", "agent", "referral", "digital_ads")
is_existing_customerboolNoWhether the applicant is an existing borrower
Device Context
KeyTypeEncryptDescription
device_typestringNoAccepted values: "android", "ios", "web"
device_modelstringNoDevice model name (e.g. "Samsung Galaxy A54")
Sample — metadata object

PII fields below are shown as SHA-256 hashes. Plain-text values must never be transmitted.

json
{
  "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"
  }
}

2. Response

NameDescriptionStatus codeResponse Body
Successrequest successfully200See the sample success response below
Bad RequestInvalid parameters / missing config400{"data":{},"message":"eKYC platform URL not configured","verdict":"failure"}
UnauthorizedInvalid authentication token401{"data":{},"message":"authorization header is missing","time":"2024-10-31T07:50:22Z","verdict":"missing_authorization"}
Token ExpiredToken expired. Client should login again401{"data":{},"message":"Provided token is expired","time":"2024-10-31T07:50:22Z","verdict":"expired_token"}
Bad GatewayUpstream Platform Init / Verify failed502{"data":{},"message":"platform init failed: ...","verdict":"failure"} / {"data":{},"message":"platform verify selfie failed: ...","verdict":"failure"}
Internal Server ErrorCannot create encrypted token500{"data":{},"message":"cannot create encrypted token","verdict":"failure"}

On success case: 200 OK

Response Structure

The success response follows this structure:

javascript
{
  "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
}

Key Result Fields

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.

Primary output: 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.
GroupFieldTypeDescription
Vision Scorevision_scorenumberVision Score, range 0100. Higher = more trustworthy / lower risk.
Vision Scorevision_score_statusstring"success" when the score was computed, otherwise "failure".
Vision Scorevision_score_request_idstringRequest id used to trace Vision Score processing in logs.
Livenessliveness_scorenumberPassive-liveness score, range 01.
Livenessliveness_statusstring"success" or "failure".
Sanity checksanity_check_scorenumberSelfie quality score.
Sanity checksanity_check_resultstringSelfie quality verdict, e.g. "good".
Sanity checksanity_check_statusstring"success" or "failure".

Sample Response

json
{
  "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"
}

Where

keytypedescription
unique_tokenstringThe eKYC application unique token issued by Platform Init. Use it to look up the application via API Application Detail.
verify_responseobjectThe data block returned by the upstream Platform Verify Face Liveness API — same shape as the Selfie Verification API response.
applicationobjectOptional — 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:

keytypedescription
current_stepstringCurrent step in the eKYC flow, e.g. "success".
fully_completedbooltrue when every required state has been completed.
required_states[]stringStates the flow requires the user to complete.
all_completed_states[]stringStates the user has completed so far.
client_custom_settingsobjectPer-client flow settings, e.g. allow_card_types, redirect_url.

Platform API

The Platform API allows clients to drive the eKYC journey programmatically via direct HTTP calls, without using WebView or mobile SDKs.

Base endpoint:

Authentication Mechanism

  • Call Platform Init first to obtain an access_token.
  • Pass access_token as a Bearer token in subsequent calls:
    Authorization: Bearer <access_token>
    

1. Initialization API

Initializes an eKYC session from the client-generated encrypted token. This single call handles token parsing, application creation (or lookup), bootstrap configuration, and access token issuance.

1. Request

MethodPathContent-TypeAuth Required
GET/ekyc/platform/initNo

Query Parameters:

NameTypeRequiredDescription
encrypted_tokenstringYesThe ENCODED_VALUE generated by the Authentication mechanism above (AES-256 CBC encrypted user identity, Base64 URL encoded with key ID prefix)
flow_idstringNoSpecific flow to initialize
card_typesstringNoComma-separated list of allowed card types (e.g. vn.national_id,vn.passport)
langstringNoLanguage code (e.g. vi, en)

2. Response

Success (200):

json
{
  "data": {
    "token": "<unique_token>",
    "access_token": "<JWT>",
    "bootstrap": {
      "lender_code": "lender_vn",
      "lender_country": "VN",
      "lender_language": "vi",
      "ui_version": "4.0",
      "masked_phone_number": "09*****123",
      "required_steps": [],
      "flow_selected_at": 1700000000,
      "lead_source": "sms",
      "client_custom_settings": {}
    }
  },
  "message": "platform init successfully",
  "time": "2026-01-01T00:00:00Z",
  "verdict": "success"
}
verdictStatus codeDescription
success200Session initialized successfully.
invalid_parameters400encrypted_token is missing or has invalid format.
record_not_found404Encryption key, lender, or application not found / expired.
limit_exceeded429Concurrent init request in progress — retry shortly.
failure500Internal server error.

2. ID Verification API

Uploads ID card images (and optional QR images), runs OCR and tampering checks, and returns the final verification result synchronously.

1. Request

MethodPathContent-TypeAuth Required
POST/ekyc/platform/verify_idapplication/jsonYes

Request Body:

NameTypeRequiredDescription
card_typestringYesCard type to verify (e.g. vn.national_id, vn.passport, ph.philhealth)
imageobjectYesFront side image: { "base64": "<base64_string>", "metadata": "<string>", "qr_code": [{"result": "...", "from_image_type": "..."}] }
image2objectNoBack side image: same structure as image
qrsarrayNoQR code images: [{ "base64": "<base64_string>", "metadata": "<string>" }]

2. Response

Success (200):

json
{
  "data": {
    "status": "success",
    "error_code": "",
    "error_message": {},
    "label": "vn.national_id.front",
    "next_action": "",
    "application_state": ""
  },
  "message": "verify id card completed",
  "time": "2026-01-01T00:00:00Z",
  "verdict": "success"
}

Response data fields:

FieldTypeDescription
statusstringVerification result: success, failure, partial_success, etc.
error_codestringError code when status is failure (omitted on success)
error_messageobjectHuman-readable error details (omitted on success)
labelstringImage label that was processed (e.g. vn.national_id.front)
next_actionstringAction the client should take next (e.g. process_qr_code) — if applicable
application_statestringUpdated application state after verification
verdictStatus codeDescription
success200Verification completed (check data.status for result).
invalid_parameters400Missing or invalid card_type, image, or base64 data.
limit_exceeded429A verification is already in progress for this session.
failure500Internal server error or verification timeout.

3. Selfie Verification API

Uploads selfie images, runs liveness detection and face matching, and returns the final verification result synchronously. The last image in the images array is treated as the primary selfie.

1. Request

MethodPathContent-TypeAuth Required
POST/ekyc/platform/verify_selfieapplication/jsonYes

Request Body:

NameTypeRequiredDescription
imagesarrayYesArray of selfie images: [{ "base64": "<base64_string>", "metadata": "<string>" }]. The last element is the primary selfie.
selfie_typestringNoType of selfie flow (e.g. passive, flash)

2. Response

Success (200):

json
{
  "data": {
    "status": "success",
    "error_code": "",
    "error_message": {},
    "label": "portrait",
    "next_action": "",
    "application_state": ""
  },
  "message": "verify selfie completed",
  "time": "2026-01-01T00:00:00Z",
  "verdict": "success"
}

Response data fields are identical to Platform Verify ID above.

verdictStatus codeDescription
success200Verification completed (check data.status for result).
invalid_parameters400images is empty or contains invalid base64 data.
limit_exceeded429A verification is already in progress for this session.
failure500Internal server error or verification timeout.

4. Aggregate API

Bundles the three calls above (Init → ID Verification → Selfie Verification) into a single synchronous request. It imports a complete dossier, runs the configured ID and selfie checks in sequence (waiting for each), drives the application to a terminal state so the configured Post-check / AML / Government checks run, and returns the full TS-native result — the same shape as API Application Detail — in the response.

Use this when you already hold a user's ID/selfie assets (e.g. captured on another channel or by another vendor) and want to re-check them through the TrustVision pipeline in one call, without driving Init / Verify ID / Verify Selfie separately.

Authentication: unlike Verify ID / Verify Selfie, this endpoint does not require a prior Init or a Bearer access_token — it authenticates with the encrypted_token in the body (the same token as Init). The response still returns an access_token you can use to re-pull API Application Detail later.

Note: the request blocks until all checks finish (ID + selfie run sequentially), so allow a generous client timeout.

1. Request

MethodPathContent-TypeAuth Required
POST/ekyc/platform/aggregateapplication/jsonNo

Request Body:

NameTypeRequiredDescription
encrypted_tokenstringYesThe ENCODED_VALUE from the Authentication mechanism (same as Init). Identifies the user; the user reference inside it is the idempotency key.
flow_idstringNoSpecific flow to use.
card_typesstringNoComma-separated allowed card types (e.g. vn.national_id,vn.passport).
langstringNoLanguage code (e.g. vi, en).
card_typestringYes (for ID)Card type to verify (e.g. vn.national_id). Must be within card_types when that is provided.
image1objectYes (for ID)ID front: { "base64": "<base64>", "metadata": "<string>", "qr_code": [{"result": "...", "from_image_type": "..."}] }.
image2objectNoID back: same structure as image1.
qrsarrayNoStandalone QR images: [{ "base64": "<base64>", "metadata": "<string>" }].
selfiesarrayYes (for selfie)Frontal selfie image(s): [{ "base64" \| "id", "metadata" }]. The last element is the primary selfie used for face compare. An element with id references a pre-uploaded image.
selfie_typestringNoSelfie / liveness type (e.g. passive, passive_v2, flash).
videosarrayNoLiveness video frames: [{ "id"?, "metadata"?, "frames": [{ "base64", "label", "index", "metadata"? }] }] (≤ 10). Required for passive/flash liveness to pass — a still-only selfie gives the engine nothing to score.
application_metadataobjectNoPrepared applicant identity PII (applicant_name, id_number, dob, gender, phone_number, email), JSON ≤ 8 KB. Stored encrypted and echoed back only — never fed into any check or the verdict.

At least one of image1 (ID) or selfies (selfie) must be present — supply only one group for an ID-only or selfie-only re-check. Put liveness frames in videos, not in selfies.

2. Response

The endpoint blocks until all checks complete, then returns the full result — the same TS-native shape as API Application Detail (overall verdict plus each module's status). Conditional blocks (ekyc_ocr / ekyc_face / id_tampering / file_ids / form_data / application_metadata) appear only when the corresponding data exists.

Success (200):

json
{
  "data": {
    "verdict": "approve",
    "application_id": 10,
    "application_unique_token": "ad083945-...",
    "application_user_id": "0123456789",
    "ekyc_ocr": {
      "card_info": {},
      "process_status": "success",
      "sanity_check_status": "success"
    },
    "ekyc_face": {
      "compare_status": "matched",
      "compare_score": 0.98,
      "liveness_status": "live",
      "search_face_status": "not_found"
    },
    "id_tampering": { "score": 0.01, "verdict": "genuine" },
    "application_metadata": {
      "applicant_name": "Jane Doe",
      "gender": "female"
    },
    "access_token": "<JWT>"
  },
  "message": "aggregate completed",
  "time": "2026-01-01T00:00:00Z",
  "verdict": "success"
}

Response data fields:

FieldDescription
data.verdictOverall application verdict: approve, review, reject, or pending.
ekyc_ocr / ekyc_face / id_tamperingPer-module results + status (same as API Application Detail). Present only when run.
application_metadataThe prepared PII you supplied, decrypted and echoed back (omitted if none).
access_tokenJWT to re-pull API Application Detail for this application later.

See API Application Detail above for the full field reference (the verdict, file_ids, and form_data mappings).

verdictStatus codeDescription
success200Processing completed — read data.verdict for the outcome.
missing_parameters400encrypted_token is missing.
invalid_parameters400Token undecodable, no ID/selfie assets, bad card_type, or invalid base64.
record_not_found404Encryption key or lender not found / inactive.
limit_exceeded429Another submission for the same user is in progress, or rate limit reached.
failure500Internal server error.

Dedupe / indexing-only mode: when enabled for the lender (client_custom_settings.aggregate_dedupe_only), the endpoint runs only face index/search, leaves data.verdict as pending, and returns the match result on data.ekyc_face.search_face_result / search_face_status (the Post-check / AML / verdict pipeline is skipped).