Face Authentication

Authenticate

POST/v1/face_auth

Checks a captured selfie against the customer’s registered face and returns a match verdict with a confidence score.

Request

MethodPOST
Path/v1/face_auth
Content-Typeapplication/json
Auth requiredYes — HMAC signing

Clients can provide image data to the TrustVision API by specifying the ID the image by using upload API, or by sending the image data as base64-encoded text.

NameTypeRequiredDescription
cus_user_idstringRequiredUser id of customer (max 256)
faces[]imageRequiredImages of the selfie (max 50). Any flash mode (flash, flash_8, flash_16, flash_32, flash_edge, flash_advanced) and passive_v2 need multiple images covering the captured frames — a single still image gives the model nothing to score.
gesture_faces[]gesture_imageOptionalArray of gestures contain images (max 50)
videos[]videoOptionalArray of videos selfie (max 50)
gesture_videos[]gesture_videoOptionalArray of videos gesture (max 50)
device_idstringOptionalDevice id of user (max 256)
app_idstringOptionalApp id of customer (max 128)
latfloatOptionalLatitude of device
lngfloatOptionalLongitude of device
auth_typestringOptionalSpecify different authentication type (or different use-cases) (max 64). Selects the backend cluster setting for this request — controls which checks run (liveness, portrait sanity, face compare). Left unset, the full default FA pipeline runs every check.
client_transaction_idstringOptionalClient transaction ID
selfie_typestringOptionalSpecify the characteristic of authentication type eg. Light, Standard, Edge, Advanced (max 64). Must be set explicitly to match the captured data — sending multiple faces images without setting this to the matching flash value (or passive_v2) means that liveness model never runs against them.
history_return_countintOptionalNumber of history authenticate records to return
history_return_statusstringOptionalStatus of history authenticate records to return (success, failure or all)
metadatajson_objectOptionalAny key-value metadata to store with this login time
transaction_datajson_objectOptionalPayment context for this authentication, read by the face authentication policy rules configured for your account. See Transaction data below.
camera_challenge_performedboolOptionalSet automatically by the Mobile SDK: reports whether the camera challenge ran for this session. Report-only — it never affects processing or the result. Server-side callers that own the API call may set it themselves.
  • For parameter type image, if you use id (result id is returned from Upload Image API), omit parameter base64, label and metadata. If you use base64, label and metadata, omit parameter id.
  • For parameter type video, if you use id (result id is returned from Upload File API), omit parameter frames, metadata. If you use frames, metadata, omit parameter id.

The image, gesture_image, video, FrameData and gesture_video shapes, and the gesture values left, right, up and down, are defined once on Common objects & errors.

JSON
{
    "cus_user_id": string,
    "client_transaction_id": string,
    "faces": [
        {
            "id": string,
            "base64": string,
            "label": string,
            "metadata": string_json,
        }
    ],
    "gesture_faces":[
        {
            "gesture": string,
            "images": [
                {
                        "id": string,
                        "base64": string,
                        "label": string,
                        "metadata": string_json,
                    },
                    {
                        "id": string,
                        "base64": string,
                        "label": string,
                        "metadata": string_json,
                    }
            ],
        }
    ],
    "videos": [
        {
            "id": string,
            "metadata": string_json,
            "frames": [
                {
                    "base64": string,
                    "label": string,
                    "index": int,
                    "metadata": string_json,
                },
                {
                    "base64": string,
                    "label": string,
                    "index": int,
                    "metadata": string_json,
                },
                // other frames
            ],
        },
        {
            "id": string,
            "metadata": string_json,
            "frames": [
                {
                    "base64": string,
                    "label": string,
                    "index": int,
                    "metadata": string_json,
                },
                {
                    "base64": string,
                    "label": string,
                    "index": int,
                    "metadata": string_json,
                },
                // other frames
            ],
        },
        ...
    ],
    "gesture_videos": [
        {
            "gesture": string,
            "videos": [
                {
                    "id": string,
                    "metadata": string_json,
                    "frames": [
                        {
                            "base64": string,
                            "label": string,
                            "index": int,
                            "metadata": string_json,
                        },
                        {
                            "base64": string,
                            "label": string,
                            "index": int,
                            "metadata": string_json,
                        },
                        // other frames
                    ],
                },
                ... // other videos
            ]
        },
        ... // other gesture videos
    ],
    "device_id": string,
    "app_id": string,
    "lat": float,
    "lng": float,
    "auth_type": string,
    "selfie_type": string,
    "history_return_count": int,
    "history_return_status": string,
    "metadata": json_object,
    "transaction_data": json_object,
    "camera_challenge_performed": bool
}

camera_challenge_performed is only meaningful when selfie_type is a flash variant or passive_v2 — for other flows it is always false. A malformed (non-boolean) value is ignored with a server-side warning and the request still processes normally; the value is stored with the authentication history.

The selfie_type parameter

selfie_type picks the liveness technique the call runs, which is what separates the Light, Standard, Edge and Advanced tiers named in the parameter table.

selfie_typeDescription
lightLight Authen
passiveStandard Authen
passive_v2Standard Authen (v2)
activeAdvanced Authen use active liveness (gestures)
flashAdvanced Authen uses full flash liveness
flash_8Edge Authen uses flash liveness with 8 frames
flash_16Edge Authen uses flash liveness with 16 frames
flash_32Advanced Authen uses flash liveness with 32 frames
flash_edgeEdge Authen uses flash liveness with minimum time
flash_advancedAdvanced Authen uses full flash liveness

The auth_type parameter

auth_type labels the use-case this authentication belongs to; the values below are the common ones, and you can define any authentication type based on your business. Each auth_type maps to a backend cluster setting that decides which checks run for the request — liveness, portrait sanity, face compare — so it is how you scope a request to fewer checks than the full default pipeline; leaving it unset runs every check.

auth_typeDescription
reset_passwordAuthenticate to reset password flow
loginUse face to replace text password
paymentAuthenticate payment
transferAuthenticate transfer
transfer_type_AAuthenticate transfer type A
transfer_type_BAuthenticate transfer type B
transfer_type_CAuthenticate transfer type C
transfer_type_DAuthenticate transfer type D
...You can define any authentication type based on your business

Sample request

curl -X POST \
https://tv-staging.trustingsocial.com/api/v1/face_auth \
-H 'Authorization: TV <YOUR ACCESS KEY>:<CREATED SIGNATURE>' \
-H 'X-TV-Timestamp: 2019-04-21T18:00:15+07:00' \
-H 'Content-Type: application/json' \
-d \
'
{
    "cus_user_id": "3b4bf7b6-088b-4931-9279-9259f5c34fe3",
    "faces": [
        {
            "id": "a4facba3-334b-41fb-97e3-4766cb70ae29"
        },
        {
            "id": "c5a2a405-f152-4248-b746-3af76b241ef7"
        },
        {
            "id": "5d0345f7-da7c-44e8-bd6c-a8c08c806227"
        }
    ],
    "gesture_faces": [
        {
            "gesture": "left",
            "images": [
                {
                    "id": "d849a3ce-d209-4605-99a7-3d369234fd5e"
                }
            ]
        },
        {
            "gesture": "up",
            "images": [
                {
                    "id": "551fc06a-14fd-44dc-8838-abe895007c36"
                }
            ]
        },
        {
            "gesture": "right",
            "images": [
                {
                    "id": "536d2457-bfc0-4249-85f2-8c4053bdec58"
                }
            ]
        }
    ],
    "videos": [
        {
            "id": "61801fcd-b109-49f7-b871-ae82c2a666cc",
        },
        {
            "id": "1f93d229-7f1c-44f9-a85b-1be4c2d4be41",
        }
    ],
    "metadata": {
        "key1": {
          "key11": "value11",
          "key12": "value12"
        },
        "key2": "value2"
    }
}
'

A Flash mode request sends multiple faces — one per captured frame — and sets selfie_type to the matching flash value, instead of the single image a passive request would use:

Shell
curl -X POST \
https://tv-staging.trustingsocial.com/api/v1/face_auth \
-H 'Authorization: TV <YOUR ACCESS KEY>:<CREATED SIGNATURE>' \
-H 'X-TV-Timestamp: 2019-04-21T18:00:15+07:00' \
-H 'Content-Type: application/json' \
-d \
'
{
    "cus_user_id": "3b4bf7b6-088b-4931-9279-9259f5c34fe3",
    "selfie_type": "flash_16",
    "auth_type": "login",
    "faces": [
        {
            "id": "b635e407-13e4-403b-8731-80821cfa4922"
        },
        {
            "id": "1cb8712c-56da-4c26-a850-08618248a370"
        }
    ]
}
'

Response

data.auth_check carries the verdict — a result of matched or unmatched, and a score for how likely it is that the two faces match (0-1). A 200 only means the request finished processing: whether the authentication passed is in data.status, which is "success" or "failure", and in auth_check — never in the HTTP status code.

Each entry under histories is a history record, and errors uses the shared error envelope; both are documented once on Common objects & errors.

JSON
{
    "data": {
        "status": string, // "success" or "failure"
        "auth_check": {
            "score": float, // how likely that face1 and face2 are matched (0-1)
            "result": string // "matched", "unmatched"
        },
        "request_id": string, // return request id to the client and can use request-id to check logs.
        "user_metadata": json_object, // return user metadata
        "validate_faces_result": {
            "sanity_verdict": string, // portrait_sanity check result, if field not exist or value is "", it means portrait_sanity check is not performed
            "is_live": bool, // liveness check result, if field not exist or value is null, it means liveness check is not performed
            "sanity_check: {...}, // full sanity check response
            "liveness_check: {...} // full liveness check response
        },
        "auth_id": string, // id of this authentication request
        "histories": [
            {
                "id": string, // id of this history
                "client_id": string, // client id
                "cus_user_id": string, // user id
                "face_auth_id": string, // id of selfie image containing user's face
                "id_card_face_id": string, // id of id card image containing user's face
                "live_face_id": string, // id of input image for this login request
                "device_id": string, // device id input for this login request
                "app_id": string, // app id input for this login request
                "latitude": float, // latitude input for this login request
                "longitude": float, // longitude input for this login request
                "created_at": string, // time of this login request
                "auth_action": string, // "login"
                "status": string, // "success" or "failure"
                "result": string, // detail result of this login request
                "metadata": json_object, // metadata input for this login request
                "face_type": string, // type of face used to compare against the input face to authenticate ("selfie", "id_card")
            },
            ... // other history
        ]
    },
    "errors": [
        {
            "code": string,
            "message": string,
            "detail": {
                "field": string, // optional, which parameter is invalid.
                ... // any other information that can be useful for client
            },
        },
        ... // other errors
    ]
}

The expiry_date of selfie face will be returned in user_metadata field as expiry_date_face in RFC3339 format.

A matched authentication, with two history records, comes back like this:

JSON
{
    "data": {
        "status": "success",
        "auth_check": {
            "score": 0.9999999862517992,
            "result": "matched"
        },
        "request_id": "cfc7b8d5-f2e2-437b-80e5-d600dfad26e2",
        "user_metadata": {
            "key1": "value1",
            "key2": 2,
            "expiry_date_face": "2023-12-27T03:33:06.307977Z",
        },
        "validate_faces_result": {
            "sanity_verdict": "good",
            "is_live": true,
            "sanity_check": {
                "portrait_sanity": {
                    "score": 1,
                    "verdict": "good"
                },
                "request_id": "9f89377f-4bb4-4ecb-884c-f46f1d7d6099",
                "status": "success"
            },
            "liveness_check": {
                "images": null,
                "is_live": true,
                "request_id": "baee3d03-8af4-421b-8968-a0f6172b4df8",
                "score": 1,
                "status": "success"
            }
        },
        "auth_id": "d4f99f9b-4862-437f-a9c9-608e050d06c6",
        "histories": [
            {
                "id": "f1ca6795-cbd4-4eec-821f-b4ac646db627",
                "client_id": "0ef0f7e9-7f2f-4767-84b8-f76da7701c46",
                "cus_user_id": "3b4bf7b6-088b-4931-9279-9259f5c34fe3",
                "face_auth_id": "887249dc-681c-4231-aff9-67734833aca7",
                "id_card_face_id": "4f959f4a-f5de-4b50-8724-224e709d06c2",
                "live_face_id": "009161c9-9c89-4573-8119-8f513de4449c",
                "created_at": "2023-12-27T03:33:06.307977Z",
                "auth_action": "login",
                "status": "success",
                "result": "",
                "face_type": "selfie"
            },
            {
                "id": "8c23af95-aaa0-4448-bfba-68acce31236e",
                "client_id": "0ef0f7e9-7f2f-4767-84b8-f76da7701c46",
                "cus_user_id": "3b4bf7b6-088b-4931-9279-9259f5c34fe3",
                "face_auth_id": "",
                "id_card_face_id": "4f959f4a-f5de-4b50-8724-224e709d06c2",
                "live_face_id": "009161c9-9c89-4573-8119-8f513de4449c",
                "created_at": "2023-12-21T07:24:19.829484Z",
                "auth_action": "login",
                "status": "failure",
                "result": "image is not liveness",
                "face_type": "id_card"
            }
        ]
    }
}

A Flash mode authentication (selfie_type: "flash_16") carries the same verdict shape, with validate_faces_result.liveness_check scoring every faces image individually — the same shape Verify face liveness returns:

JSON
{
    "data": {
        "status": "success",
        "auth_check": {
            "score": 0.910237543284893,
            "result": "matched"
        },
        "request_id": "3f2a9b3e-1c9d-4a5e-9b7a-2f6d9a1b7e3c",
        "validate_faces_result": {
            "sanity_verdict": "good",
            "is_live": true,
            "sanity_check": {
                "portrait_sanity": {
                    "score": 1,
                    "verdict": "good"
                },
                "status": "success"
            },
            "liveness_check": {
                "images": [
                    {
                        "id": "1cb8712c-56da-4c26-a850-08618248a370",
                        "is_live": true,
                        "score": 0.910237543284893
                    },
                    {
                        "id": "b635e407-13e4-403b-8731-80821cfa4922",
                        "is_live": true,
                        "score": 0.9999598160466121
                    }
                ],
                "is_live": true,
                "score": 0.910237543284893,
                "status": "success"
            }
        },
        "auth_id": "8c8e6a2b-2f9d-4a5e-9b7a-2f6d9a1b7e3c",
        "histories": []
    }
}

Failure codes

If the data.status is "failure", the "errors" field will tell you why it failed.

Error codeDescription
image_too_blurthe input image is too blur
image_too_darkthe input image is too dark
image_too_brightthe input image is too bright (glare)
image_has_no_facesthe input image has no faces
image_non_livenessthe input image is not liveness
face_auth_user_deactivatedthe user is deactivated
face_auth_user_unregisteredthe user is unregistered from face_auth service
face_auth_user_lockedthe user is locked from face_auth service

Transport, auth and rate-limit errors are the same on every call — see Common objects & errors.

Transaction data

transaction_data carries the payment context behind an authentication. It is optional and purely additive: omitting it leaves the request, and the face-match verdict, exactly as they are today. What it adds is context for the face authentication policy rules configured for your account — those rules can read every field below, so a policy can, for example, ask for a step-up or a manual review when a large transfer goes to a beneficiary that was added minutes ago.

Two things to know about scope:

  • The object is read only when face authentication policy checks are enabled for your account. With those checks off it is accepted and ignored.
  • It is meaningful only on this endpoint. Register a face accepts the field for symmetry but does not evaluate or store it.

Every field is optional — send the ones you have. Fields marked encrypted at rest are stored encrypted by the server: send them in plaintext, and policy rules still evaluate them in plaintext.

Transaction core

FieldTypeISO 20022 pathDescription
transaction_idstringPmtId/EndToEndIdYour identifier for the transaction
transaction_timestampstringGrpHdr/CreDtTmWhen the transaction was created, as an ISO 8601 timestamp
transaction_amountnumberIntrBkSttlmAmtSettlement amount
currency_codestringIntrBkSttlmAmt/@CcyISO 4217 currency code, e.g. VND
transaction_typestringPmtTpInfCoarse transaction type, e.g. transfer
channelstringChannel the transaction was initiated from, e.g. mobile_app
transaction_statusstringpacs.002 TxStsStatus of the transaction

Parties & accounts

FieldTypeISO 20022 pathDescription
account_numberstringDbtrAcct/IdSender account number — encrypted at rest
account_holder_namestringDbtr/NmSender account holder name — encrypted at rest
bank_codestringDbtrAgt/FinInstnId/(BICFI or ClrSysMmbId)Sender bank code — encrypted at rest
beneficiary_account_numberstringCdtrAcct/IdRecipient account number — encrypted at rest
beneficiary_account_holder_namestringCdtr/NmRecipient account holder name — encrypted at rest
beneficiary_bank_codestringCdtrAgt/FinInstnId/(BICFI or ClrSysMmbId)Recipient bank code — encrypted at rest

Risk & payment context

FieldTypeISO 20022 pathDescription
is_new_beneficiaryboolWhether this is the first payment to this beneficiary
days_since_beneficiary_addedintDays since the beneficiary was added to the sender's payee list
beneficiary_known_mule_flagboolWhether the beneficiary is on your known-mule list
beneficiary_name_match_resultstringOutcome of the beneficiary name check (e.g. NAPAS name-check)
beneficiary_proxy_aliasstringCdtrAcct/Prxy/IdBeneficiary proxy alias, e.g. phone or VietQR alias — encrypted at rest
beneficiary_countrystringCdtr/PstlAdr/CtryBeneficiary country
beneficiary_bank_countrystringCdtrAgt/FinInstnId/PstlAdr/CtryBeneficiary bank country
beneficiary_account_open_datestringWhen the beneficiary account was opened
relationship_to_beneficiarystringDeclared relationship between sender and beneficiary
directionstringCdtDbtIndCredit or debit indicator (CRDT / DBIT)
purpose_codestringPurp/CdPayment purpose code
transaction_categorystringPmtTpInf/CtgyPurp/CdCategory purpose code
transaction_remarkstringRmtInf/UstrdUnstructured remittance information / transfer note
payment_railstringPmtTpInf/LclInstrm/CdPayment rail or local instrument
initiation_methodstringHow the payment was initiated
merchant_category_codestringMrchntCtgyCdMerchant category code
original_amountnumberInstdAmtInstructed amount, before any conversion or fees
napas_system_trace_refstringPmtId/ClrSysRefClearing-system reference
global_tx_uuidstringPmtId/UETREnd-to-end unique transaction reference (UETR)
is_recurringboolWhether the payment is part of a recurring series
sender_countrystringDbtr/PstlAdr/CtrySender country
sender_account_open_datestringWhen the sender account was opened
sender_date_of_birthstringDbtr/Id/PrvtId/DtAndPlcOfBirth/BirthDtSender date of birth — encrypted at rest
customer_risk_levelstringYour own risk rating for the customer
balance_beforenumberAccount balance before the transaction
balance_afternumberAccount balance after the transaction
time_since_last_inflowintSeconds since the last credit into the account
velocity_featuresjson_objectFree-form velocity features you compute for the customer
txn_velocity_windowjson_objectFree-form transaction counts/amounts over your own time windows
failed_auth_attemptsintFailed authentication attempts leading up to this transaction
days_since_credential_changeintDays since the customer last changed a credential
sim_swap_recent_flagboolWhether a recent SIM swap was detected for the customer
authentication_methodstringAuthentication method used for the payment itself
biometric_required_flagboolWhether your own policy already requires biometrics for this transaction
ip_addressstringIP address the transaction was initiated from

Field naming: canonical names and ISO 20022 paths

Each field can be keyed by either its canonical name (the Field column above) or the ISO 20022 path shown next to it; both are normalized to the canonical name on ingest. If the same logical field arrives under both keys in one request, the ISO-keyed value wins.

  • Canonical names are matched case-insensitively, so channel and Channel both bind. ISO paths must match exactly as written above.
  • Acct/OpngDt and Bal/Amt are deliberately not accepted as aliases — each maps to two canonical fields (sender vs. beneficiary open date, balance before vs. after) and cannot be told apart from the key alone. Use the canonical names for those four fields.
  • Keys that are not listed above are kept verbatim and stay readable by policy rules, so bank-specific fields can be sent alongside the standard ones.

Example

JSON
{
    "cus_user_id": "customer-001",
    "auth_type": "transfer",
    "selfie_type": "passive",
    "faces": [{ "id": "5f0e9dbe-1e0f-4bd7-9e39-5f0b8ba4c9d1" }],
    "transaction_data": {
        "transaction_id": "txn-001",
        "transaction_timestamp": "2024-01-15T10:30:00Z",
        "transaction_amount": 5000000,
        "currency_code": "VND",
        "transaction_type": "transfer",
        "channel": "mobile_app",
        "account_number": "1234567890",
        "beneficiary_account_number": "0987654321",
        "beneficiary_bank_code": "970436",
        "is_new_beneficiary": true,
        "days_since_beneficiary_added": 0,
        "direction": "DBIT",
        "balance_after": 1200000
    }
}