Face match
POST /api/kyc/face/compare
POST /api/kyc/face/upload
GET /api/kyc/face/checks
GET /api/kyc/face/checks/:idCompare two photos of a face and learn, synchronously, whether they show the same person. This is the server-to-server counterpart of the dashboard's Face match tab: same engine, same verdicts, same history (API checks appear there labelled "API").
Authentication: Authorization: Bearer sk_… (secret keys only, for the whole surface). The inputs are biometric data and the verdict is a compliance record, so this never belongs in client-side code.
Production note: sk_live_ keys require an approved business.
Compare
POST /api/kyc/face/compareOne call: send both photos inline and get the verdict back. Each photo is exactly one of an image field (URL, data URI, or base64) or a mediaId from the optional upload endpoint below.
| Field | Required | Description |
|---|---|---|
imageA | one of | The first photo, inline: an https:// URL we fetch, a data: URI, or base64-encoded bytes. JPEG, PNG or WebP; the format is detected from the bytes, never from a label. |
imageB | one of | The second photo, same forms. |
mediaAId | one of | Alternative to imageA: a mediaId from POST /face/upload. |
mediaBId | one of | Alternative to imageB. |
consent | yes | Must be true: you attest the people shown have consented to the comparison. |
sandboxOutcome | no | Sandbox only, see below. Ignored on live keys. |
curl -X POST https://trust.myaza.app/api/kyc/face/compare \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"imageA": "https://cdn.example.com/enrolment/selfie-42.jpg",
"imageB": "data:image/jpeg;base64,/9j/4AAQ…",
"consent": true
}'Size limits. URLs and uploads accept photos up to 25 MB. Base64 travels inside the JSON body, which is capped at 10 MB, so base64 photos top out around 7 MB of image; send anything bigger by URL or upload.
URL fetching. Image URLs must be http(s) and resolve to a public address; anything that points into a private network is refused with image_url_not_allowed. Redirects are followed (up to 3), each hop checked the same way.
The response:
{
"faceCheck": {
"id": "fc_…",
"verdict": "match",
"match": true,
"confidence": 91,
"threshold": 80,
"reviewThreshold": 60,
"chargedAmount": "0.0300",
"createdAt": "2026-08-13T12:00:00.000Z"
}
}The verdict
verdict is three-way, judged against your organisation's thresholds (the pass mark and review floor, managed on the dashboard's Face match tab):
| Verdict | Meaning |
|---|---|
match | Similarity at or above your pass mark. |
review | Similarity between your review floor and pass mark. The score alone cannot decide; a human should compare the photos. |
no_match | Similarity below your review floor. |
no_result | The comparison could not run (for example, no detectable face). Returned with HTTP 502 and nothing is charged. |
There is deliberately no per-request threshold: the lines that judge a verdict are your organisation's recorded policy, and every check records the lines that judged it. If part of your flow needs a stricter bar, branch on confidence in your own code; the recorded verdict stays consistent.
Uploading first (optional)
POST /api/kyc/face/uploadWhen the bytes are already on your side and you would rather not inline them, or you want to reuse one photo across several checks, upload it once and pass the mediaId. Multipart form with a single file field (jpeg, png or webp, up to 25 MB); each call returns one mediaId.
curl -X POST https://trust.myaza.app/api/kyc/face/upload \
-H "Authorization: Bearer sk_live_…" \
-F "file=@photo-a.jpg"
# → { "mediaId": "med_…" }Re-read a check
GET /api/kyc/face/checks/:idReturns the same faceCheck object for any past check belonging to your organisation and environment.
History
GET /api/kyc/face/checksPaginated face-check history for your organisation and the key's environment, newest first. Query parameters: verdict (match / review / no_match / no_result), from / to (ISO dates), page, pageSize (max 100). Rows carry the same fields as the compare response plus initiatedVia (api or dashboard).
Sandbox behaviour
Sandbox keys (sk_test_) always return simulated verdicts; the real comparison engine runs in production only. With no sandboxOutcome the verdict is a simulated match; pass review, no_match or no_result to see the others. Simulated scores are placed inside your organisation's own thresholds, so they behave exactly as a live score would. Sandbox checks are free, and sandbox photos are replaced with a placeholder after 3 days.
Billing
Production checks are billed per comparison at your facial comparison price (see your pricing). A no_match still charges, because the check ran and answered. A no_result never charges.
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | same_photo | Both sides are the same photo (same media id, or identical inline images). |
| 400 | media_unreadable | One of the photos could not be read; send both again. |
| 400 | invalid_image | An inline image is neither an https URL, a data URI, nor valid base64. |
| 400 | unsupported_media_type | The bytes are not a JPEG, PNG or WebP photo. |
| 400 | image_too_large | The photo exceeds 25 MB. |
| 400 | image_url_not_allowed | The image URL is not http(s), or resolves to a non-public address. |
| 400 | image_fetch_failed | The image URL could not be fetched (unreachable, error status, empty body, or too many redirects). |
| 402 | insufficient_credits | Your balance cannot cover the check. |
| 403 | spot_checks_disabled | Face checks are not enabled for your organisation. |
| 403 | secret_key_required | A publishable key was used; this surface is secret-key only. |
| 429 | rate_limited | Hourly face-check limit reached; retry later. |
| 502 | no_result | The comparison could not run; nothing charged. |