API reference
POST /v1/enroll
Create a user with their email, name, face descriptors and consent.
POST /v1/enroll
Creates a user in your app and stores their face templates. Returns the user and a user token, so the user is signed in right after enrolling.
Credential: publishable key, from an allowed Origin. Body limit: 256 KB.
Request body
{
"challenge_id": "ch_...",
"email": "ada@example.com",
"name": "Ada",
"descriptors": [[0.012, -0.044, "... 128 floats in total"]],
"liveness": { "score": 0.93, "checks": ["blink", "motion"] },
"consent": { "policy_version": "2026-10-14", "accepted": true }
}| Field | Type | Rules |
|---|---|---|
challenge_id | string | A fresh challenge from POST /v1/challenges. |
email | string | Must be unique in this app. |
name | string | Display name. |
descriptors | array | 1 to 5 arrays, each exactly 128 finite floats. |
liveness | object | Browser liveness result. Advisory, see Liveness. |
consent | object | accepted must be true. FaceCard stores the policy version, time, IP and user agent. |
Keep the real request body in a file because descriptors are long:
curl -X POST https://api.facecard.dev/v1/enroll \
-H "X-FaceCard-Key: fc_pk_live_xxx" \
-H "Origin: https://app.example.com" \
-H "Content-Type: application/json" \
-d @enroll.jsonResponse
201 Created
{
"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"
}Errors
| Status | Code | When |
|---|---|---|
| 400 | invalid_request | Malformed body, wrong descriptor count or length. |
| 400 | consent_required | consent.accepted is not true. |
| 400 | challenge_invalid | The challenge is missing, expired, already used or from another app. |
| 401 | unauthorized | Missing or invalid key. |
| 403 | origin_not_allowed | The Origin is not on the allow list. |
| 409 | email_taken | That email already exists in this app. |
| 409 | face_already_enrolled | The face matches an existing user in this app. The response never names the other user. |
| 413 | payload_too_large | Body over 256 KB. |
| 429 | rate_limited | More than 30 requests a minute from this IP. |
| 429 | plan_limit_reached | Over the monthly auth limit. Each enroll that reaches face matching counts as one auth. |