Skip to main content

Liveness checks

POST /v1/liveness/check decides whether the frames you send show a live person in front of the camera rather than a photo, a screen or a replayed clip. The verdict is always the server's; on-device face detection in the SDKs only helps the user frame the shot.

Modes

modeFramesWhen to use
passiveexactly one imageLowest friction. A single selfie scored by the anti-spoof models.
burst3 to 5 parts named image_1image_5A short capture sequence. Adds a temporal check across frames.
challenge2 to 10 parts named image_<n> plus a challenge_tokenStrongest. The server picks an action the user must perform.

Challenge flow

  1. Call POST /v1/liveness/challenges. The response carries a challenge_token, the action the user must perform (blink, turn_left or turn_right) and expires_at, 120 seconds later.
  2. Capture frames while the user performs the action.
  3. Send them to /v1/liveness/check with mode=challenge and the token. The action is bound to the token server side; a caller cannot choose it, which is what stops one pre-recorded clip from being replayed against the same action every time.

Frames are single use

Every frame is hashed before inference. Frames that were already scored for your tenant are refused with a 400 frame_replayed error, and so is a request that contains the same frame twice. The window is:

Key modeWindow
live24 hours
test60 seconds

A photo that has passed through a liveness check once cannot pass again in that window, whatever the outcome of the first check was. Re-encoding or resizing changes the bytes and therefore the hash, so this guard is a replay check, not a face deduplication. For repeated integration tests with the same image, use a test key and wait a minute between runs, or capture fresh frames.

Result

{
"lc_id": "lc_01a0799179d576da…",
"is_live": true,
"score": 0.91,
"threshold": 0.4,
"reason": null,
"mode": "passive",
"face_bbox": { "x": 312, "y": 402, "w": 540, "h": 540 },
"face_quality": { "…": "…" },
"model_version": "…",
"request_id": "…",
"correlation_id": null
}
  • is_live is score >= threshold. The threshold is your tenant's anti-spoof threshold from the Decision thresholds page in the dashboard.
  • reason is set when the check could not be scored, for example no face found.
  • face_quality holds informational metrics about the detected face; its keys can change between model versions, so do not branch on them.
  • components (the per-model scores behind the verdict) is present in test mode, and in live mode only when ops enable liveness_show_components for your tenant.
  • lc_id is stable; GET /v1/liveness/checks/:id returns the result later for audit.

Inside a verification session

When the request is made with a session token that is bound to a verification, the check becomes that verification's liveness step. The verification's policy then applies: it can require a particular mode (validation_error if you send another) and it caps attempts (verification_closed once they are exhausted).

Webhook

A scored check publishes liveness.check.completed with the result body. See Webhooks.