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
mode | Frames | When to use |
|---|---|---|
passive | exactly one image | Lowest friction. A single selfie scored by the anti-spoof models. |
burst | 3 to 5 parts named image_1 … image_5 | A short capture sequence. Adds a temporal check across frames. |
challenge | 2 to 10 parts named image_<n> plus a challenge_token | Strongest. The server picks an action the user must perform. |
Challenge flow
- Call
POST /v1/liveness/challenges. The response carries achallenge_token, theactionthe user must perform (blink,turn_leftorturn_right) andexpires_at, 120 seconds later. - Capture frames while the user performs the action.
- Send them to
/v1/liveness/checkwithmode=challengeand 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 mode | Window |
|---|---|
| live | 24 hours |
| test | 60 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_liveisscore >= threshold. The threshold is your tenant's anti-spoof threshold from the Decision thresholds page in the dashboard.reasonis set when the check could not be scored, for example no face found.face_qualityholds 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 enableliveness_show_componentsfor your tenant.lc_idis stable;GET /v1/liveness/checks/:idreturns 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.