Verification
Standalone tools
One-shot utilities — AML, face search, signature verify/search/enroll, document extract — with full request/response JSON and no session attached.
The /v3/tools/* endpoints mirror the console's Manual Tools. They are stateless: none of them create or mutate a verification session. Each is tenant-scoped by your API key (x-api-key), and image arguments are base64 strings — raw or a data: URI.
Warning
A tool result is a lookup, not a verification. It carries no liveness, no face match, and no audit trail. Don't use document-extract or aml-check as a substitute for a real module in a compliance-bearing decision — use the tools for enrichment, triage and operator tooling.
Conventions
- Auth:
x-api-key: <tenant_api_key>(orX-API-Key). See Authentication. - Images: base64 (optionally a
data:URI), JPEG or PNG only, max 8 MB. Other formats (HEIC, WEBP, GIF, …) return415 unsupported_image_type; a missing/undecodable image returns400 <field>_required/400 invalid_<field>. - Rate limit: the tools share a per-tenant bucket of 120 requests/minute (
kyc_v3_tools). Over-limit requests get429. - Entitlement + billing: each chargeable tool is gated by your plan. A disabled feature returns
403 feature_not_enabled(with afeaturesarray); an empty balance returns402 insufficient_credits. A successful call debits one use. - Errors:
/v3tool errors return{ "detail": "<code>" }with the HTTP status carrying the machine signal. Branch on status + the stable code, not the message.
| Endpoint | Purpose | Key body fields | Chargeable |
|---|---|---|---|
POST /v3/tools/aml-check/ | Sanctions/PEP/adverse-media screening | full_name | first_name+last_name, date_of_birth?, nationality?, country?, document_number?, lists?, match_threshold? | ✅ |
POST /v3/tools/face-search/ | 1:N face search over enrolled identities | image_base64, threshold?, top_k? | ✅ |
POST /v3/tools/signature-verify/ | 1:1 signature comparison | reference_base64, probe_base64, threshold? | ✅ |
POST /v3/tools/signature-search/ | 1:N signature search (beta) | image_base64, threshold?, top_k? | ✅ |
POST /v3/tools/signature-enroll/ | Add a signature specimen to an identity | individual_id | external_user_id, image_base64, label? | — |
POST /v3/tools/document-extract/ | Identity-document OCR | image_base64, document_type? | — |
AML check
Ad-hoc watchlist screening for a name — the same engine as the AML_SCREENING module. A name is the minimum useful selector; every other field narrows the candidate set.
curl -X POST "$TF_BASE_URL/v3/tools/aml-check/" \
-H "x-api-key: $TF_API_KEY" -H "Content-Type: application/json" \
-d '{
"full_name": "Aarav Sharma",
"date_of_birth": "1990-04-12",
"nationality": "NP",
"lists": ["sanctions", "pep"],
"match_threshold": 0.7
}'const res = await fetch(`${process.env.TF_BASE_URL}/v3/tools/aml-check/`, {
method: "POST",
headers: {
"x-api-key": process.env.TF_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
full_name: "Aarav Sharma",
date_of_birth: "1990-04-12",
nationality: "NP",
lists: ["sanctions", "pep"],
match_threshold: 0.7,
}),
});
const screen = await res.json();
console.log(screen.risk_level, screen.matches.length);import requests
res = requests.post(
f"{BASE_URL}/v3/tools/aml-check/",
headers={"x-api-key": API_KEY},
json={
"full_name": "Aarav Sharma",
"date_of_birth": "1990-04-12",
"nationality": "NP",
"lists": ["sanctions", "pep"],
"match_threshold": 0.7,
},
timeout=30,
)
screen = res.json()
print(screen["risk_level"], len(screen["matches"])){
"risk_level": "medium",
"matches": [],
"possible_matches": [
{
"entity_id": "NK-9x2",
"name": "Aarav Sharma",
"schema": "Person",
"score": 0.63,
"match": false,
"lists": ["pep"],
"topics": ["role.pep"],
"datasets": ["everypolitician"],
"birth_date": "",
"countries": ["np"],
"aliases": [],
"nationalities": ["np"],
"positions": ["Member of Parliament"],
"gender": "male",
"addresses": [],
"id_numbers": [],
"url": ""
}
],
"informational_matches": [],
"lists_checked": ["pep", "sanctions"],
"provider": "watchlist",
"dataset": "default",
"algorithm": "logic-v1",
"threshold": 0.7
}risk_level is high / medium / low / clear. A query with no name returns 400 name_required; an unreachable engine returns 503 screening_unavailable. Full field reference, list topics, scoring tiers and the corroboration rule: AML screening.
Face search
Ranks your tenant's enrolled identities whose selfie matches a probe photo — duplicate-account detection. Read-only (nothing is flagged); it mirrors the automatic duplicate check.
curl -X POST "$TF_BASE_URL/v3/tools/face-search/" \
-H "x-api-key: $TF_API_KEY" -H "Content-Type: application/json" \
-d '{"image_base64":"<jpg/png base64>","threshold":0.35,"top_k":12}'const res = await fetch(`${process.env.TF_BASE_URL}/v3/tools/face-search/`, {
method: "POST",
headers: {
"x-api-key": process.env.TF_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({ image_base64: imageB64, threshold: 0.35, top_k: 12 }),
});
const { matches } = await res.json();
console.log(matches.map((m: any) => [m.external_user_id, m.score]));import requests
res = requests.post(
f"{BASE_URL}/v3/tools/face-search/",
headers={"x-api-key": API_KEY},
json={"image_base64": image_b64, "threshold": 0.35, "top_k": 12},
timeout=30,
)
for m in res.json()["matches"]:
print(m["external_user_id"], m["score"])image_base64stringbodyrequiredthresholdnumberbodytop_knumberbody{
"matches": [
{
"individual_id": "9f3a…",
"name": "Aarav Sharma",
"external_user_id": "user-123",
"status": "active",
"kyc_status": "clear",
"score": 0.71,
"thumb": "https://…/face.jpg"
}
],
"threshold": 0.35
}422 no_face_detected if there's no usable face in the probe.
Signature verify
Compares a probe signature to a reference — structural similarity of stroke geometry (not pen dynamics). Both inputs are cropped to just the signature ink first, so a full document photo or a video frame is matched on its ink, not the surroundings.
curl -X POST "$TF_BASE_URL/v3/tools/signature-verify/" \
-H "x-api-key: $TF_API_KEY" -H "Content-Type: application/json" \
-d '{"reference_base64":"<base64>","probe_base64":"<base64>","threshold":0.62}'const res = await fetch(`${process.env.TF_BASE_URL}/v3/tools/signature-verify/`, {
method: "POST",
headers: {
"x-api-key": process.env.TF_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
reference_base64: referenceB64,
probe_base64: probeB64,
threshold: 0.62,
}),
});
const out = await res.json();
console.log(out.verdict, out.score);import requests
res = requests.post(
f"{BASE_URL}/v3/tools/signature-verify/",
headers={"x-api-key": API_KEY},
json={"reference_base64": reference_b64, "probe_base64": probe_b64, "threshold": 0.62},
timeout=30,
)
out = res.json()
print(out["verdict"], out["score"])reference_base64stringbodyrequiredprobe_base64stringbodyrequiredthresholdnumberbody{
"score": 0.68,
"verdict": "match",
"matched": true,
"threshold": 0.62
}The verdict is derived against the requested threshold:
verdict | Condition |
|---|---|
match | score ≥ threshold |
review | score ≥ 0.75 × threshold |
mismatch | below the review floor |
Note
If an input can't be read as a signature you get a 422 — no_signature_detected when no stroke-shaped ink is found, or not_a_signature when a human face is detected (a photo of a person is not a signature). The tool never returns a falsely-confident match.
Signature search (beta)
Ranks your tenant's enrolled signature specimens against a probe — a recall aid for operators, not a verdict. The probe is cropped to the signature ink first, the same treatment enrollment applies, so both sides of the search are built from the same kind of crop.
curl -X POST "$TF_BASE_URL/v3/tools/signature-search/" \
-H "x-api-key: $TF_API_KEY" -H "Content-Type: application/json" \
-d '{"image_base64":"<png base64>","threshold":0.6,"top_k":12}'const res = await fetch(`${process.env.TF_BASE_URL}/v3/tools/signature-search/`, {
method: "POST",
headers: {
"x-api-key": process.env.TF_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({ image_base64: imageB64, threshold: 0.6, top_k: 12 }),
});
const { matches, beta } = await res.json();
console.log(beta, matches.length);import requests
res = requests.post(
f"{BASE_URL}/v3/tools/signature-search/",
headers={"x-api-key": API_KEY},
json={"image_base64": image_b64, "threshold": 0.6, "top_k": 12},
timeout=30,
)
data = res.json()
print(data["beta"], len(data["matches"]))image_base64stringbodyrequiredthresholdnumberbodytop_knumberbody{
"matches": [
{
"individual_id": "9f3a…",
"name": "Aarav Sharma",
"external_user_id": "user-123",
"status": "active",
"score": 0.64,
"thumb": "https://…/signature.png"
}
],
"threshold": 0.6,
"beta": true
}422 no_signature_detected if the probe has no readable signature. Confirm any hit with the 1:1 signature verify.
Signature enroll
Attaches a reference signature to an identity so it becomes searchable by signature search. The input is cropped to the signature ink first, so a full video frame / document photo enrolls a clean specimen.
curl -X POST "$TF_BASE_URL/v3/tools/signature-enroll/" \
-H "x-api-key: $TF_API_KEY" -H "Content-Type: application/json" \
-d '{"external_user_id":"user-123","image_base64":"<png base64>","label":"account-opening"}'const res = await fetch(`${process.env.TF_BASE_URL}/v3/tools/signature-enroll/`, {
method: "POST",
headers: {
"x-api-key": process.env.TF_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
external_user_id: "user-123",
image_base64: imageB64,
label: "account-opening",
}),
});
const out = await res.json(); // 201
console.log(out.sample_id, out.individual_id);import requests
res = requests.post(
f"{BASE_URL}/v3/tools/signature-enroll/",
headers={"x-api-key": API_KEY},
json={
"external_user_id": "user-123",
"image_base64": image_b64,
"label": "account-opening",
},
timeout=30,
)
out = res.json() # 201
print(out["sample_id"], out["individual_id"])individual_idstringbodyexternal_user_id).external_user_idstringbodyimage_base64stringbodyrequiredlabelstringbodyaccount-opening), truncated to 120 chars.{
"sample_id": "c2b8…",
"individual_id": "9f3a…"
}On success returns 201. An unknown person returns 404 individual_not_found; an image with no readable signature returns 422 no_signature_detected.
Document extract
Identity-document OCR — the same read as ID_VERIFICATION, run standalone. It uses the platform document-OCR service, so it works on any account regardless of the tenant's Lens config.
curl -X POST "$TF_BASE_URL/v3/tools/document-extract/" \
-H "x-api-key: $TF_API_KEY" -H "Content-Type: application/json" \
-d '{"image_base64":"<base64>","document_type":"passport"}'const res = await fetch(`${process.env.TF_BASE_URL}/v3/tools/document-extract/`, {
method: "POST",
headers: {
"x-api-key": process.env.TF_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({ image_base64: imageB64, document_type: "passport" }),
});
const doc = await res.json();
console.log(doc.detected_type, doc.fields);import requests
res = requests.post(
f"{BASE_URL}/v3/tools/document-extract/",
headers={"x-api-key": API_KEY},
json={"image_base64": image_b64, "document_type": "passport"},
timeout=30,
)
doc = res.json()
print(doc["detected_type"], doc["fields"])image_base64stringbodyrequireddocument_typestringbodyOptional type hint to recover documents auto-detect misses: passport, drivers_license, citizenship, pan, disability_id. Auto-detect covers most cases.
fieldsobjecttranslated_fieldsobjectdetected_typestringconfidencenumberis_document_validbooleanhas_portraitboolean{
"fields": {
"full_name": "AARAV SHARMA",
"document_number": "PA1234567",
"date_of_birth": "1990-04-12",
"expiry_date": "2032-01-10",
"nationality": "NP"
},
"translated_fields": {},
"detected_type": "passport",
"confidence": 0.97,
"is_document_valid": true,
"has_portrait": true
}Template-less documents (e.g. disability cards) run a generic extraction — auto-detect fields plus best-effort local OCR text, flagged with generic_extraction: true and a full_text string. Aadhaar cards, which the classifiers can only call "national id", are relabelled to aadhaar from OCR-text signals (display-level only). 503 ocr_not_configured if the OCR service isn't available; 422 ocr_no_fields if nothing readable was found.
Error codes
| Status | detail code | Meaning |
|---|---|---|
| 400 | <field>_required / invalid_<field> | Missing or undecodable image / name. |
| 402 | insufficient_credits | Tenant balance below the tool's rate. |
| 403 | feature_not_enabled | The tool isn't on your plan (see features). |
| 404 | individual_not_found | Signature enroll target doesn't exist. |
| 415 | unsupported_image_type | Not JPEG/PNG (HEIC, WEBP, GIF, …). |
| 422 | no_face_detected / no_signature_detected / not_a_signature / ocr_no_fields | Nothing usable in the input. |
| 429 | — | Over the 120 req/min tools bucket. |
| 503 | screening_unavailable / ocr_not_configured | The backing engine isn't reachable/configured. |