Run a full KYC verification via API key
POST /api/v1/kyc/verify
POST https://api.trustverify.co.uk/api/v1/kyc/verify
Accepts base64-encoded document and selfie images. Runs face matching, optional document corroboration and AML screening, and returns a synchronous result. Liveness is covered by the guided selfie step; the liveness signal reports what was returned for this check.
Request body#
| Field | Type | Description |
|---|---|---|
| documentImage | string | Base64 document front (single-image mode) |
| documentImageFront | string | Base64 document front (named mode — either this or documentImage is required) |
| documentImageBack | string | Base64 document back (optional) |
| selfieImagerequired | string | Base64-encoded JPEG/PNG selfie |
| documentType | string: "passport" | "national_id" | "driving_licence" | |
| checks | object | |
| checks.amlScreening | boolean | |
| generateReport | boolean | Include a reportUrl (RPT-001 PDF) in the response |
| metadata | object | Echoed back on the result. metadata.webhookUrl is not called: per-request callbacks are paused, and the response carries callback "not_sent". |
Example request#
curl -sS -X POST 'https://api.trustverify.co.uk/api/v1/kyc/verify' \
-H "Authorization: Bearer $TRUSTVERIFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"documentType": "passport",
"documentImage": "<base64 JPEG of the ID document>",
"selfieImage": "<base64 JPEG of the selfie>",
"metadata": {
"firstName": "Test",
"lastName": "Person",
"dateOfBirth": "1990-01-01",
"country": "GB"
}
}'Responses#
200 KYC verification result
| Field | Type | Description |
|---|---|---|
| checkIdrequired | string | |
| statusrequired | string: "passed" | "failed" | "review" | |
| scorerequired | number,null | Deterministic confidence, bounded by the weakest piece of evidence actually returned — reproducible from the same provider outputs. null when the providers returned verdicts but no numeric scores: the decision stands on the boolean signals, and no confidence was measured. Do not read null as zero. |
| riskLevelrequired | string: "low" | "medium" | "high" | |
| documentTyperequired | string | |
| completedAtrequired | string | |
| reportUrl | string | Present when generateReport was true |
| signalsrequired | object | A signal is null when no provider returned a result for it. null is never treated as a pass: `passed` requires an explicit true from the face match, plus an AML clear when screening was requested. The liveness signal is null when no liveness result was returned for the check, and null never changes the status. It can still FAIL a check: an explicit liveness false returns `failed`. |
| signals.biometricsrequired | object | |
| signals.biometrics.faceMatchrequired | boolean,null | |
| signals.biometrics.livenessrequired | boolean,null | |
| signals.amlrequired | object | |
| signals.aml.clearrequired | boolean,null | |
| signals.aml.riskLevelrequired | string,null | |
| providersrequired | object | |
| metadatarequired | object | |
| callback | string: "not_sent" | Present when the request carried a webhookUrl: per-request callbacks are paused and nothing was sent. The result is in this response. |
| callbackReason | string |
400 Validation error — including checks.amlScreening enabled without a name in metadata
401 Invalid API key
429 API rate limit exceeded
503 A required provider did not return a result, so no verdict is issued. The response carries unavailableProviders, the ful
A required provider did not return a result, so no verdict is issued. The response carries unavailableProviders, the full providers map and the partial signals. This is never a failed verification — it means the check did not run. Safe to retry.
| Field | Type | Description |
|---|---|---|
| errorrequired | string: "verification_unavailable" | |
| messagerequired | string | |
| unavailableProvidersrequired | array of string | |
| providersrequired | object | |
| signalsrequired | object | |
| requestIdrequired | string |