TruvaLI API Documentation
Integration interface for fraud prevention, AML monitoring and customer onboarding (KYC) services.
Overview
All endpoints are served under the /api/ prefix and speak JSON (except document upload endpoints, which use multipart/form-data). Request bodies must be UTF-8 encoded.
Endpoint list
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/api/merchant/auth/token/ |
POST | none | Obtain access token |
/api/merchant/auth/check/ |
GET | Bearer | Token validity check |
/api/customer/upsert/ |
POST | Bearer | Create/update customer |
/api/transaction/financial/upsert/ |
POST | Bearer | Financial transaction reporting |
/api/transaction/non-financial/upsert/ |
POST | Bearer | Non-financial event reporting |
/api/document/create-session/ |
POST | Bearer | Open KYC session |
/api/document/upload_document/ |
POST | Session token | Upload document (end user) |
/api/document/take_snapshot/ |
POST | Session token | Take snapshot (end user) |
/api/document/end-kyc-session/ |
POST | Agent token | End the interview |
Server-to-server calls use a Bearer token (JWT). KYC calls made from the end user's browser use a signed session token instead. Your API key must never be sent to the end user's device.
Authentication
You obtain an access token using your API key and secret key.
Request
{
"api_key": "05b190fb-c354-4431-bf04-450cf77e0306",
"api_secret": "9d4f2a11-77c3-4b2e-9a51-1f0b8c3d7e42"
}
Response | 200
{
"status": true,
"message": "Login successful.",
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
Usage in subsequent requests
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
The allowed IP list on your credentials cannot be left empty. An empty list means "allow no IP" and login attempts are rejected with 403. Use * to allow all IPs; in production, we recommend entering your servers' static IPs.
Errors
| Status | Message | Reason |
|---|---|---|
| 400 | Request body is not valid JSON. |
Body could not be parsed |
| 400 | Request body must be a JSON object… |
Body is not an object (list/string was sent) |
| 400 | Missing required field(s): api_key… |
Missing field |
| 401 | Invalid credentials provided. |
Key or secret key is incorrect |
| 403 | This credential is not active. |
Credential has been deactivated |
| 403 | Access from this IP address is not allowed. |
IP is not in the allow list |
Token check
Tests whether the token you have is still valid. Only callable via GET; a POST request returns 403 due to CSRF protection.
{
"status": true,
"message": "Token is valid for Example Customer.",
"data": {
"client": "Example Customer",
"session_id": "8f2c1a90-3b7e-4d55-9c21-6a4b8e0d1f33"
}
}
Response format
All JSON endpoints use the same envelope:
{
"status": true | false, // whether the request as a whole succeeded
"message": "a human-readable summary",
"data": { ... } // endpoint-specific content
}
In batch endpoints, status: true only means "the request was processed". The fate of individual records is in data.results; even if a record is rejected, the top-level status stays true.
Error model
There are two different error levels, and distinguishing between them matters for integration.
1. Request level: the entire request is rejected
If the body is malformed, no record is processed and 4xx is returned. This points to a programming error on the caller's side.
| Status | When |
|---|---|
| 400 | Body is not valid JSON |
| 400 | Body is not an object or array |
| 400 | Array is empty: Payload contains no records. |
| 400 |
Array contains a non-object element: Record at index 0 must be a JSON object, got str.
|
| 401 | Bearer token is missing, invalid, or expired |
| 413 | More than 1000 records were sent |
2. Record level: only that record is skipped
If the body format is correct but a record's content is invalid, only that record is dropped, the others continue to be processed, and the request returns 200. The skipped record is flagged with action: "skipped" and error.
{
"status": true,
"message": "Processed 3 financial event record(s).",
"data": {
"results": [
{ "transaction_ref": "TX-1", "id": "…", "action": "created", "score": 0, "decision": "ACCEPT" },
{ "transaction_ref": "TX-2", "id": null, "action": "skipped",
"error": "'amount' cannot be negative (got -1.00); use io_type to express direction.",
"score": 100.0, "decision": "REJECT" },
{ "transaction_ref": "TX-3", "id": "…", "action": "created", "score": 0, "decision": "ACCEPT" }
]
}
}
3. Warnings: the record is processed but something was corrected
Correctable deviations do not drop the record; the value is normalized and reported via warnings. We recommend monitoring these warnings and fixing them at the source.
{
"transaction_ref": "TX-4",
"action": "created",
"warnings": [
"'currency' was normalised to upper case ('usd' -> 'USD').",
"'amount' was rounded to 2 decimal places (1.129 -> 1.13)."
]
}
You can send any field outside the defined ones. These are not rejected, do not produce a warning, and are stored in the record's raw field, making them available to the rule engine. This is designed so you can write rules based on your own business fields.
Batch requests
All upsert endpoints accept either a single object or an array of objects. Up to 1000 records can be sent per request.
// single record
{ "customer_ref": "C-1001", "first_name": "Jane" }
// batch
[
{ "customer_ref": "C-1001", "first_name": "Jane" },
{ "customer_ref": "C-1002", "first_name": "John" }
]
In both cases, the response returns an array inside data.results, and the order matches the order you sent.
Customer upsert
Creates or updates a customer record. The record is matched by customer_ref; a second call with the same reference performs an update. After it is saved, the customer is run through the rule engine and a risk score is calculated.
Fields
| Field | Type | Constraint | Description |
|---|---|---|---|
customer_ref required |
text | max 48 | Your unique customer identifier |
customer_type |
number | 1 or 2 | 1 = Individual (default), 2 = Corporate |
first_name |
text | max 100 | |
last_name |
text | max 100 | |
gender |
text | male, female, other, unknown | Case differences are normalized |
birth_date |
date | YYYY-MM-DD | Used in email risk scoring |
identity_number |
text | max 100 | Identity number |
passport_number |
text | max 100 | |
passport_issuing_country |
text | 3 letters | ISO 3166-1 alpha-3 |
nationality |
text | 3 letters | ISO 3166-1 alpha-3, e.g. TUR |
email |
text | max 254 | A quality score is calculated; an invalid format is not rejected, only flagged |
phone |
text | max 100 | |
country |
text | max 100 | |
occupation |
text | max 100 | Occupation |
ip_address |
text | IPv4 / IPv6 | For geolocation and IP risk score |
status |
text | max 24 | Your customer status |
registration_date |
date-time | ISO-8601 | The moment the customer registered with you |
email_verified_at |
date-time | ISO-8601 | |
phone_verified_at |
date-time | ISO-8601 | |
migration |
true/false | Historical data migration: skips external service calls (email, IP, sanctions) |
Corporate customer fields
Available in addition when you send customer_type: 2:
| Field | Constraint | Description |
|---|---|---|
company_name |
max 255 | Trade name |
legal_form |
max 50 | Legal structure (e.g. joint-stock company, limited liability company …) |
tax_number |
max 100 | Tax number |
tax_office |
max 100 | Tax office |
sector |
max 150 | Industry sector |
incorporation_date |
YYYY-MM-DD | Incorporation date |
country_of_origin |
3 letters | ISO 3166-1 alpha-3 |
registry_number |
max 100 | Trade registry number |
lei_code |
max 20 | Legal Entity Identifier |
stock_ticker_symbol |
max 20 | Stock ticker symbol |
website |
max 255 | |
founders |
array | Founders / partners |
Example request
POST /api/customer/upsert/
Authorization: Bearer <token>
Content-Type: application/json
{
"customer_ref": "C-1001",
"customer_type": 1,
"first_name": "Jane",
"last_name": "Smith",
"gender": "female",
"birth_date": "1990-04-17",
"identity_number": "10000000146",
"nationality": "TUR",
"email": "[email protected]",
"phone": "+905301234567",
"ip_address": "88.230.14.7",
"registration_date": "2026-01-15T09:30:00Z",
"loyalty_tier": "gold",
"internal_segment": "vip"
}
The last two fields are not defined in the model; they are not rejected, they are stored in the record's raw field and can be used when writing rules.
Example response | 200
{
"status": true,
"message": "Processed 1 customer record(s).",
"data": {
"results": [
{
"customer_ref": "C-1001",
"id": "3f1c8a20-9d44-4e77-b1a2-77c9e0b34512",
"action": "created",
"score": 25.0,
"decision": "ACCEPT",
"sanction_score": 0,
"sanction_topic": [],
"ip_address_score": 12.5,
"email_risk_score": 34.0,
"warnings": []
}
]
}
}
| Field | Description |
|---|---|
action |
created, updated, or skipped
|
score |
Risk score produced by the rule engine (0–100) |
decision |
ACCEPT, REVIEW, or REJECT
|
sanction_score / sanction_topic |
Result of the sanctions list screening |
email_risk_score |
Email quality/risk score (higher = riskier) |
warnings |
List of fields that were normalized |
Financial transaction upsert
Reports transactions that involve a movement of funds. The record is matched by transaction_ref; a second call with the same reference performs an update.
The customer sent via customer_ref must already exist. event_slug must be an event defined in the admin panel, active, and of type Financial. If either is not satisfied, the record is skipped.
Fields
| Field | Type | Constraint | Description |
|---|---|---|---|
customer_ref required |
text | at most 48 | Reference of an existing customer |
event_slug required |
text | Defined financial event code, e.g. deposit |
|
amount required |
decimal | > 0, at most 9999999999.99 |
Rounded to 2 decimal places. Cannot be negative; direction is expressed via io_type
|
currency required |
text | 3 letters | ISO-4217, converted to uppercase |
transaction_ref required
|
text | at most 64 | Deduplication key. Must be unique for each transaction |
io_type required |
number | 0 or 1 | 0 = Credit, 1 = Debit |
event_date required |
date-time | ISO-8601 | The moment the transaction occurred |
payment_method |
text | at most 16 | e.g. credit_card, wallet |
fee_amount |
decimal | ≥ 0 | Transaction fee |
fee_currency |
text | 3 letters | |
source_provider |
text | at most 64 | Sending institution |
source_ref |
text | at most 64 | Sender reference |
source_address |
text | at most 64 | IBAN, wallet address, etc. |
destination_provider |
text | at most 64 | Receiving institution |
destination_ref |
text | at most 64 | Recipient reference |
destination_address |
text | at most 64 | IBAN, wallet address, etc. |
status |
text | at most 16 | Defaults to initiated |
event_description |
text | at most 128 | Free-form description |
session_id |
text | at most 128 | End user's session ID |
device_id |
text | at most 128 | Device fingerprint |
ip_address |
text | IPv4 / IPv6 | End user's IP address |
channel |
number | 0–4 | See constant values |
user_agent |
text | Browser information | |
geo |
object | Location data | |
metadata |
object | Free-form additional data |
A financial transaction is not recorded without customer_ref, event_slug, amount, currency, transaction_ref, io_type and event_date; the record is returned as skipped.
Although the last three have a technical default, leaving them at their default is a silent data error: without transaction_ref, a network retry creates a second transaction record; if io_type is wrong, debit/credit totals silently break; if event_date is wrong, time-windowed rules ("so many transactions in so many hours") silently break.
Example request
POST /api/transaction/financial/upsert/
Authorization: Bearer <token>
Content-Type: application/json
{
"customer_ref": "C-1001",
"event_slug": "deposit",
"transaction_ref": "TX-2026-0007781",
"amount": "15750.00",
"currency": "TRY",
"io_type": 0,
"payment_method": "credit_card",
"event_date": "2026-08-07T11:42:13Z",
"source_address": "TR330006100519786457841326",
"ip_address": "88.230.14.7",
"channel": 1,
"metadata": { "campaign": "summer26" }
}
Example response | 200
{
"status": true,
"message": "Processed 1 financial event record(s).",
"data": {
"results": [
{
"transaction_ref": "TX-2026-0007781",
"id": "6b708204-81d2-49e2-a808-cbc9beae549b",
"action": "created",
"score": 40.0,
"decision": "REVIEW",
"rule_check": true,
"log_id": "b1e7…",
"warnings": []
}
]
}
}
Indicates whether the rule engine ran for this record. If event_date is older than 1 day, the rule engine is not run (so historical data migration doesn't generate live alerts) and this field returns false.
Non-financial transaction upsert
Reports events that do not involve a movement of funds, such as login attempts, password changes, IBAN registration, or profile updates.
The field set is the same as the financial endpoint's; there are no amount- or currency-related fields. If you send these fields anyway, they are not rejected; they are written to the record's raw field but not processed.
| Field | Constraint | Description |
|---|---|---|
customer_ref required |
at most 48 | Reference of an existing customer |
event_slug required |
Defined event code of type Non-financial or Onboarding | |
transaction_ref |
at most 64 | Deduplication key. Optional on this endpoint; if not sent, the record cannot be deduplicated and you will receive a warning in the response |
io_type |
not used |
Not present on this endpoint; if sent, it is only stored in raw
|
event_date required |
ISO-8601 | The moment the event occurred |
event_description |
at most 128 | Free-form description |
status |
at most 16 | Defaults to initiated |
session_id, device_id,
ip_address, channel,
user_agent, geo, metadata
|
Same as the financial endpoint |
Example request
POST /api/transaction/non-financial/upsert/
Authorization: Bearer <token>
Content-Type: application/json
{
"customer_ref": "C-1001",
"event_slug": "failed_login",
"transaction_ref": "EVT-2026-0004412",
"event_date": "2026-08-07T11:40:02Z",
"event_description": "Incorrect password, attempt 3",
"ip_address": "185.66.12.9",
"device_id": "a91f3c7d20e4",
"channel": 3
}
No record is created without customer_ref, event_slug and event_date. Although event_date has a technical default (the moment of the request), leaving the moment the event occurred at its default is a silent data error: in case of queue delay or bulk submission, the request time and the event time can diverge by hours, and time-windowed rules ("so many attempts in so much time") run in the wrong window.
transaction_ref, however, is optional on this endpoint; that is the difference from the financial endpoint.
You cannot send a non-financial event_slug to the financial endpoint (or vice versa). Such a record is skipped with the following error:
Event 'login' has type 0 (Onboarding Event), which this endpoint does
not accept. Accepted types: 1 (Financial Event).
Document verification (KYC) | Flow overview
The customer onboarding flow consists of five steps:
-
Create session (server-to-server). Your platform calls
create-sessionwith a Bearer token and receives a session token (endpoint_token). - Redirect to the end user. You pass the session token to the end user's browser. From this point on, document upload calls are made with this token; your API key never reaches the device.
-
Document upload. The end user uploads the documents required by the flow using
upload_document. - Video call (if the flow requires it). An agent joins the call, and a snapshot is captured if needed.
-
Resolution. The session is closed with
end-kyc-sessionand the operator makes a decision.
Flow definition (flow)
Which documents to collect, whether a video call is required, and where the result notification should be sent are all kept in the flow definition. Flows are created from the admin panel and referred to by a code (e.g. onboarding). You send this code when creating a session.
A flow determines the following:
- Checklist: which document types are required
-
Video call: if the checklist includes
recordingorsnapshot, a call is set up; otherwise none is set up at all - Automatic decision: whether the session is approved without waiting for an operator once all steps complete successfully
- Time limit: how many minutes the flow must be completed within
Create session
Opens a KYC session, or returns the already-open session for the same customer and flow.
Fields
| Field | Type | Description |
|---|---|---|
customer_ref
required
|
text | Customer reference |
flow |
text | Flow code, e.g. onboarding |
device_id |
text | The end user's device identifier |
ip_address |
text | The end user's IP address |
user_agent |
text | The end user's browser information |
geo |
object | Location data |
This endpoint is called by your server, not by the end user. As a result, the request's own IP address and browser information belong to your server and say nothing about the end user. For risk scoring to work correctly, include the end user's ip_address, device_id, and user_agent in the request body.
Example request
POST /api/document/create-session/
Authorization: Bearer <token>
Content-Type: application/json
{
"customer_ref": "C-1001",
"flow": "onboarding",
"device_id": "a91f3c7d20e4",
"ip_address": "88.230.14.7",
"user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) …",
"geo": { "lat": 41.0082, "lon": 28.9784 }
}
Example response | 200
{
"status": true,
"message": "Processed verification session record.",
"data": {
"session": {
"session_id": "1aa3d05c-fde7-410c-b0e1-090487760c47",
"flow": "onboarding",
"required_artifact_types": ["doc_front", "doc_mrz", "selfie"],
"pending_artifact_types": ["doc_mrz", "selfie"],
"succeeded_artifact_types": ["doc_front"],
"requires_video_call": false,
"attempt_no": 1,
"livekit_token": "",
"endpoint_token": "eyJjdXN0b21lcl9yZWYiOiJDLTEwMDEi…",
"mrz_data": null
}
}
}
| Field | Description |
|---|---|
session_id |
The session's unique identifier |
endpoint_token |
The signed token the end user will use when uploading documents. Valid for 24 hours. |
required_artifact_types |
The document types required by the flow |
succeeded_artifact_types |
Types that have been uploaded and successfully verified |
pending_artifact_types |
Types that are still missing; build your interface around this |
requires_video_call |
Whether the flow requires a video call |
attempt_no |
The attempt number. Increments when returning to an open session |
livekit_token |
The video call token. Empty if the flow doesn't require a call |
mrz_data |
The fields extracted if the MRZ was read, otherwise null
|
If you call again with the same customer_ref and flow, no new session is opened; the already-open session is returned and attempt_no increments by one. A different flow opens a separate session. This lets you build flows that resume where the user left off.
Errors
| Status | Description |
|---|---|
| 400 |
Unknown or inactive verification flow: <code>: the flow was not found or is inactive. No session is opened
|
| 401 | Invalid Bearer token |
Upload document
Called from the end user's browser. Authentication is established with the endpoint_token you received from create-session.
Form fields
| Field | Type | Description |
|---|---|---|
token
required
|
text | The endpoint_token received from create-session |
artifact_type
required
|
text | See document types |
snapshot
required
|
file |
Up to 25 MB, image/* or video/*
|
extra_data |
JSON text | Structured data accompanying the document (e.g. fields read from an NFC chip). Silently ignored if it isn't valid JSON |
Example request
const formData = new FormData();
formData.append('token', endpointToken);
formData.append('artifact_type', 'doc_front');
formData.append('snapshot', file);
await fetch('/api/document/upload_document/', {
method: 'POST',
body: formData // DO NOT manually add the Content-Type header
});
Response | 200
{
"status": "success",
"path": "snapshots/2026-08-07/1aa3d05c…__985545f0….jpg",
"artifact_id": "744a654d-3350-4f13-8de7-2f88d888e739",
"artifact_type": "doc_front"
}
Errors
| Status | reason | Description |
|---|---|---|
| 401 | token_missing |
Token not sent |
| 401 | token_invalid |
Token is malformed or the signature is invalid |
| 401 | token_expired |
Token is older than 24 hours; create a new session |
| 403 | token_wrong_audience |
Agent tokens cannot be used on this endpoint |
| 404 | session_not_found |
The session referenced by the token does not exist |
| 409 | session_closed |
The session has been resolved; documents cannot be added to a decided case |
| 400 |
artifact_type_missing /
artifact_type_invalid
|
Document type is missing or undefined |
| 400 | file_missing / file_empty |
File is missing or empty |
| 413 | file_too_large |
The 25 MB limit was exceeded |
| 415 | file_type_not_allowed |
Only images and videos are accepted |
The error body uses the standard envelope:
{
"status": false,
"message": "This verification session is closed (decided).",
"data": { "reason": "session_closed" }
}
Take snapshot
Saves the frame captured from the end user's camera during the video call. Requires the same token as upload_document; the document type is automatically assigned as snapshot.
| Field | Description |
|---|---|
token required |
endpoint_token |
snapshot required |
Image file, 25 MB maximum |
{
"status": "success",
"path": "snapshots/2026-08-07/1aa3d05c…__985545f0….jpg",
"artifact_id": "744a654d-3350-4f13-8de7-2f88d888e739"
}
Error codes are the same as upload_document.
End session
Ends the call and writes the checklist marked by the agent to the audit record.
This endpoint cannot be called with a customer token; it returns 403. The token to use is a separate one generated when the agent joins the call. This way only an agent who actually joined the room can end the call.
Request
| Field | Description |
|---|---|
token required |
Agent session token |
status required |
completed, ended or reported
|
checklist |
Checklist answers, {"101": true, "102": false}
|
| Submitted status | Session status |
|---|---|
completed |
Completed |
reported |
Reported |
ended |
Failed |
Response | 200
{
"status": true,
"session_id": "1aa3d05c-fde7-410c-b0e1-090487760c47",
"session_status": "completed"
}
An undefined status value is rejected with 400; it is never silently treated as "failed". Closing an already-closed session again is not an error (page reload, double click).
How it works
Document verification runs asynchronously. The upload_document call only tells you "file received"; operations such as OCR, MRZ reading and face matching run in the background and can take a few seconds.
You don't need to keep polling to wait for the result: as soon as each verification finishes, a websocket message is sent to the relevant session.
document uploaded → background verification → websocket message → your UI
Every message is routed to a single verification session. When you register for a session you only receive that session's results; other customers' results never reach you.
Connection and registration
After opening the websocket connection, you need to send a registration message indicating which session you are listening to. The endpoint_token you received from create-session is used as identity, the same token as the document upload endpoints.
const ws = new WebSocket("wss://<websocket-host>/document/");
ws.onopen = () => {
ws.send(JSON.stringify({
event: "register",
token: endpointToken // endpoint_token from create-session
}));
};
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.event !== "broadcast") return;
const { type, result } = msg.data;
if (result.code === "success") {
// mark the step as completed
} else {
// ask the user to retry: result.message
}
};
endpoint_token is valid for 24 hours. You cannot register with an expired token; you need to open a new session.
Result messages
Message envelope
Every incoming message has the same structure:
{
"event": "broadcast",
"client_id": "...",
"timestamp": 1786111190365, // epoch, milliseconds
"correlation_id": "9f3c1a20-…", // to track a single notification
"data": {
"to": "1aa3d05c-fde7-410c-b0e1-090487760c47", // session id
"delivery": "transporter",
"type": "mrz_verification_result", // which verification
"result": {
"code": "success", // success | fail
"data": { … }, // extra data, null if none
"message": "MRZ text has been successfully read."
}
}
}
result.code is only ever success or fail; no other value is sent. result.data is present in every message; if there is nothing to carry it is null, the field is never omitted. result.message is a description that can be shown to the user. Because these three are fixed, a single handler is enough.
Message types
type |
When it arrives | result.data |
|---|---|---|
docfront_verification_result |
When the ID document front (doc_front) is processed |
Check details (was OCR read, was a face found, mismatched fields) |
mrz_verification_result |
When the MRZ image (doc_mrz) is processed |
Fields read from the MRZ (first name, last name, document number, date of birth and expiry …) |
nfc_raw_result |
When the NFC chip data (nfc_raw) is processed |
null |
face_verification_result |
When the selfie (selfie) is compared against the photo on the document
|
null |
liveness_result |
When the liveness video (liveness_video) is processed |
null |
recording_result |
When the call recording (recording) is processed |
null |
Liveness detection does not work at the moment: the video is captured and stored, but since its content is not analyzed this message always returns success. Do not base your liveness decision on it. When detection is enabled the message format will not change, only code will start reflecting the actual result.
Example: successful MRZ read
{
"event": "broadcast",
"timestamp": 1786111190365,
"correlation_id": "9f3c1a20-4b77-4e51-8a03-2d5e91c4f8a1",
"data": {
"to": "1aa3d05c-fde7-410c-b0e1-090487760c47",
"delivery": "transporter",
"type": "mrz_verification_result",
"result": {
"code": "success",
"data": {
"first_name": "AYSE",
"last_name": "YILMAZ",
"document_number": "A12B34567",
"birth_date": "1990-04-17",
"expiry_date": "2031-06-30",
"nationality": "TUR"
},
"message": "MRZ text has been successfully read."
}
}
}
Example: failed face match
{
"event": "broadcast",
"timestamp": 1786111203881,
"correlation_id": "c07be413-1f9a-42d0-9c62-b8ad5e3a7710",
"data": {
"to": "1aa3d05c-fde7-410c-b0e1-090487760c47",
"delivery": "transporter",
"type": "face_verification_result",
"result": {
"code": "fail",
"data": null,
"message": "Yüzler eşleşmedi, lütfen tekrar deneyin."
}
}
}
Handling recommendations
-
Don't rely on ordering. Verifications finish in different processes and at different speeds; messages can arrive in a different order than the upload order. Handle every message based on its
typefield. -
Ignore
typevalues you don't recognize. New verification steps may be added in the future; an unknown type should never break your UI. -
Don't treat the websocket as the single source of truth. The connection can drop. You can always get the definitive state by calling
create-sessionagain: thesucceeded_artifact_typesandpending_artifact_typesin the response reflect the truth at that moment. -
The same message can arrive more than once. If the user re-uploads a document, a new result message is produced for that type. You can tell duplicates apart with
correlation_id.
Constants
Document types (artifact_type)
| Value | Description |
|---|---|
doc_front |
Front of ID document |
doc_back |
Back of ID document |
doc_mrz |
MRZ zone only (faster) |
nfc_raw |
NFC chip data and biometric photo |
passport_mrz_page |
Passport MRZ page |
selfie |
Selfie |
liveness_video |
Liveness video |
snapshot |
Frame captured during the call |
recording |
Call recording |
Customer type (customer_type)
| Value | Meaning |
|---|---|
1 |
Individual (default) |
2 |
Corporate |
Gender (gender)
| Value | Meaning |
|---|---|
male |
Male |
female |
Female |
other |
Other |
unknown |
Unknown |
Transaction direction (io_type)
| Value | Meaning |
|---|---|
0 |
Credit (default) |
1 |
Debit |
Channel (channel)
| Value | Meaning |
|---|---|
0 |
Unknown (default) |
1 |
Web |
2 |
Mobile browser |
3 |
Mobile / Android |
4 |
Mobile / iOS |
Decision (decision)
| Value | Meaning |
|---|---|
ACCEPT |
Accepted |
REVIEW |
Under review |
REJECT |
Rejected |
Event type (event_type)
Selected when defining an event in the admin panel; determines which endpoint it is sent to.
| Value | Meaning | Endpoint |
|---|---|---|
0 |
Onboarding event | non-financial |
1 |
Financial event | financial |
2 |
Non-financial event | non-financial |
Error codes
KYC endpoints include a data.reason field in their error bodies. These codes are stable and can be used safely in code; the message text may change.
| reason | Status | What to do |
|---|---|---|
token_missing |
401 | Add the token to the request |
token_invalid |
401 | Token is corrupted; start a new session |
token_expired |
401 | 24 hours have passed; start a new session |
token_wrong_audience |
403 | Wrong token type used |
session_not_found |
404 | Session deleted or ID incorrect |
session_closed |
409 | Session already finalized; start a new session |
artifact_type_missing |
400 | Send the document type |
artifact_type_invalid |
400 | Use one of the defined types |
file_missing |
400 | File field is empty |
file_empty |
400 | File is 0 bytes |
file_too_large |
413 | Compress the image (limit 25 MB) |
file_type_not_allowed |
415 | Only send images and videos |
status_invalid |
400 | Use one of the defined closing statuses |