Face match
Build with

code
POST /api/kyc/face/compare
POST /api/kyc/face/upload
GET  /api/kyc/face/checks
GET  /api/kyc/face/checks/:id

Compare 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
Build with

code
POST /api/kyc/face/compare

One 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.

FieldRequiredDescription
imageAone ofThe 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.
imageBone ofThe second photo, same forms.
mediaAIdone ofAlternative to imageA: a mediaId from POST /face/upload.
mediaBIdone ofAlternative to imageB.
consentyesMust be true: you attest the people shown have consented to the comparison.
sandboxOutcomenoSandbox only, see below. Ignored on live keys.
shell
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:

json
{
  "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):

VerdictMeaning
matchSimilarity at or above your pass mark.
reviewSimilarity between your review floor and pass mark. The score alone cannot decide; a human should compare the photos.
no_matchSimilarity below your review floor.
no_resultThe 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)
Build with

code
POST /api/kyc/face/upload

When 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.

shell
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
Build with

code
GET /api/kyc/face/checks/:id

Returns the same faceCheck object for any past check belonging to your organisation and environment.

History
Build with

code
GET /api/kyc/face/checks

Paginated 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
Build with

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
Build with

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
Build with

StatusCodeMeaning
400same_photoBoth sides are the same photo (same media id, or identical inline images).
400media_unreadableOne of the photos could not be read; send both again.
400invalid_imageAn inline image is neither an https URL, a data URI, nor valid base64.
400unsupported_media_typeThe bytes are not a JPEG, PNG or WebP photo.
400image_too_largeThe photo exceeds 25 MB.
400image_url_not_allowedThe image URL is not http(s), or resolves to a non-public address.
400image_fetch_failedThe image URL could not be fetched (unreachable, error status, empty body, or too many redirects).
402insufficient_creditsYour balance cannot cover the check.
403spot_checks_disabledFace checks are not enabled for your organisation.
403secret_key_requiredA publishable key was used; this surface is secret-key only.
429rate_limitedHourly face-check limit reached; retry later.
502no_resultThe comparison could not run; nothing charged.