Reusable Face Authentication
Verify a user's identity once, then re-authenticate them later with just a selfie — no ID document, no re-onboarding. This is the basis for biometric step-up (e.g. before a high-value action) and, eventually, passwordless login.
A template is a 512-dim face embedding, encrypted at rest (AES-256-GCM). The raw selfie is never stored for this flow.
1. Enroll a template
POST /face/templates
curl -X POST https://api.dev.valydar.com/face/templates \
-H "Authorization: Bearer YOUR_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"external_id": "user-123",
"selfie": "<base64-encoded-jpeg>",
"consent": true
}'
| Field | Type | Description |
|---|---|---|
external_id | string | Your identifier for the returning user |
selfie | string | Base64 JPEG/PNG of the user's face |
consent | boolean | Required — explicit consent to store a biometric template |
Response 201:
{
"template_id": "ftm_...",
"external_id": "user-123",
"model_version": "arcface-512-v1",
"created_at": "2026-09-26T12:00:00Z"
}
Re-enrolling the same external_id replaces the template and invalidates any
outstanding challenges. The selfie must pass passive liveness, otherwise the
request is rejected with 400.
2. Issue an anti-replay challenge
POST /face/templates/{external_id}/challenges
curl -X POST https://api.dev.valydar.com/face/templates/user-123/challenges \
-H "Authorization: Bearer YOUR_API_KEY"
Response:
{
"challenge_id": "fch_...",
"nonce": "8f3c…",
"expires_at": "2026-09-26T12:05:00Z",
"ttl_secs": 300
}
The challenge is single-use and time-boxed (default 300 s).
3. Authenticate
POST /face/templates/{external_id}/authenticate
curl -X POST https://api.dev.valydar.com/face/templates/user-123/authenticate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"selfie": "<base64-encoded-jpeg>",
"challenge_id": "fch_...",
"nonce": "8f3c…"
}'
Response 200:
{
"authenticated": true,
"similarity": 0.93,
"liveness_score": 0.72,
"threshold": 0.45,
"reason": "match above threshold",
"template_id": "ftm_...",
"external_id": "user-123"
}
| Field | Type | Description |
|---|---|---|
authenticated | boolean | Whether the selfie matches the enrolled template |
similarity | float / null | Cosine similarity (null if no usable face was found) |
liveness_score | float / null | Passive-liveness score of the submitted selfie |
threshold | float | Match threshold (default 0.45) |
reason | string | Human-readable outcome |
A below-threshold match, a failed liveness check, or an unusable selfie all
return 200 with authenticated: false. The challenge is consumed atomically
and bound to the template, its version, and the nonce — a replay, a
mismatched nonce, or a challenge issued before a re-enrollment is rejected with
400.
Manage templates
GET /face/templates/{external_id} # metadata only (never the embedding)
DELETE /face/templates/{external_id} # erase (GDPR); 204 on success
Webhooks
When a webhook is configured, every authentication outcome fires a
face.authentication event with the same body (including external_id and
template_id). See Webhooks.
Presentation-attack protection
Passive liveness is enforced on both enrollment and authentication, so a printed photo or screen replay of the enrolled face never authenticates. Stronger anti-replay (binding the capture itself to a fresh challenge) requires an active-liveness capture — use the Active Liveness step before calling authenticate when you need that guarantee.
Errors
| Status | Meaning |
|---|---|
400 | Missing consent, no usable face, failed liveness, or invalid/reused challenge |
404 | Template not found (or deleted) |
503 | Face recognition model unavailable |