API reference
POST /v1/match
Sign a user in by matching face descriptors against the app's enrolled users.
POST /v1/match
Compares 1 to 3 descriptors with the templates enrolled in your app. On a match, the owner of the matched template is signed in and receives a user token.
Credential: publishable key, from an allowed Origin.
Request body
{
"challenge_id": "ch_...",
"descriptors": [[0.012, -0.044, "... 128 floats in total"]],
"liveness": { "score": 0.93, "checks": ["blink", "motion"] }
}| Field | Type | Rules |
|---|---|---|
challenge_id | string | A fresh challenge. |
descriptors | array | 1 to 3 arrays, each exactly 128 finite floats. |
liveness | object | Advisory browser result. |
curl -X POST https://api.facecard.dev/v1/match \
-H "X-FaceCard-Key: fc_pk_live_xxx" \
-H "Origin: https://app.example.com" \
-H "Content-Type: application/json" \
-d @match.jsonResponse
200 OK
{
"user": {
"id": "usr_...",
"email": "ada@example.com",
"name": "Ada",
"created_at": "2026-10-14T12:00:00.000Z"
},
"token": "<jwt>",
"expires_at": "2026-10-14T13:00:00.000Z",
"confidence": 0.91
}confidence is between 0.0 and 1.0. Matching rules (threshold and margin) are explained in Matching.
No match
401 Unauthorized
{ "error": { "code": "no_match", "message": "No matching face was found." } }The message is generic on purpose. It never includes an email or any candidate data.
Errors
| Status | Code | When |
|---|---|---|
| 400 | invalid_request | Malformed body, wrong descriptor count or length. |
| 400 | challenge_invalid | The challenge is missing, expired, already used or from another app. |
| 401 | unauthorized | Missing or invalid key. |
| 401 | no_match | No candidate passed the threshold and margin. |
| 403 | origin_not_allowed | The Origin is not on the allow list. |
| 429 | rate_limited | More than 30 requests a minute from this IP. |
| 429 | plan_limit_reached | Over the monthly auth limit. A match that reaches face matching counts as one auth, success or failure. |