FaceCard Docs
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 }
}
FieldTypeRules
challenge_idstringA fresh challenge from POST /v1/challenges.
emailstringMust be unique in this app.
namestringDisplay name.
descriptorsarray1 to 5 arrays, each exactly 128 finite floats.
livenessobjectBrowser liveness result. Advisory, see Liveness.
consentobjectaccepted 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.json

Response

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

StatusCodeWhen
400invalid_requestMalformed body, wrong descriptor count or length.
400consent_requiredconsent.accepted is not true.
400challenge_invalidThe challenge is missing, expired, already used or from another app.
401unauthorizedMissing or invalid key.
403origin_not_allowedThe Origin is not on the allow list.
409email_takenThat email already exists in this app.
409face_already_enrolledThe face matches an existing user in this app. The response never names the other user.
413payload_too_largeBody over 256 KB.
429rate_limitedMore than 30 requests a minute from this IP.
429plan_limit_reachedOver the monthly auth limit. Each enroll that reaches face matching counts as one auth.

On this page