Matching: threshold and margin
How FaceCard compares a face with enrolled templates and when it answers no_match.
Descriptors
The browser SDK uses @vladmandic/face-api to turn a face into a 128-float descriptor. The API checks that each descriptor has exactly 128 finite numbers, L2-normalizes it, and stores it in a Postgres vector(128) column with an HNSW cosine index. Searches are always filtered by app_id, so a match can only be a user of your app.
Threshold
A candidate counts as a match only if its cosine distance to the probe is below the match threshold. The default is 0.35. A lower number is stricter: fewer false accepts, more false rejects.
The threshold is a server setting (MATCH_MAX_DISTANCE), not something a client can change. Apps cannot set it.
Margin
Being close is not enough. The best candidate must also beat the second best different user by a margin, which defaults to 0.04. If two users look alike to the model and neither clearly wins, the result is no_match instead of a guess.
What you get back
- A match returns
200withuser,token,expires_atand aconfidencevalue between 0.0 and 1.0. The user in the response is the owner of the matched template, and that is the user who is signed in. - No match returns
401 no_matchwith a generic message. The response never contains an email, a name or any candidate data.
Duplicate faces at enrollment
During enrollment the API runs the same comparison against existing users in your app. If the new face matches an existing user above the threshold, enrollment fails with 409 face_already_enrolled. The response does not say who the other user is.
Templates over time
A user can have up to 10 templates. After a fresh successful match, the user can add up to 3 more descriptors with POST /v1/me/templates, which helps with lighting and appearance changes. When the cap is reached the oldest learned template is dropped first.
Tuning
Matching quality depends on the capture. Good light, a face centered in the frame, no heavy glare and a few seconds of video give the best descriptors. If real users are rejected often, ask them to retry in better light before you look at anything else.