FaceCard Docs
API reference

Email codes

POST /v1/email-codes and POST /v1/email-codes/verify: request a 6 digit code by email and sign in with it.

The fallback and recovery path when a face does not match. The user asks for a code, FaceCard emails it, and the user sends it back to sign in. The idea and the rules are explained in Email code sign in.

Credential: publishable key, from an allowed Origin, for both routes. Neither route needs a challenge.

Request a code

POST /v1/email-codes

{ "email": "ada@example.com" }
FieldTypeRules
emailstringA valid email address. Matched against your app's users without regard to case.
curl -X POST https://api.facecard.dev/v1/email-codes \
  -H "X-FaceCard-Key: fc_pk_live_xxx" \
  -H "Origin: https://app.example.com" \
  -H "Content-Type: application/json" \
  -d '{"email":"ada@example.com"}'

Response

202 Accepted

{ "expires_at": "2026-10-14T12:10:00.000Z" }

The answer is always 202, whether or not a user with that email exists in your app. This stops the route from being used to find out who is enrolled. If a user exists, FaceCard sends a 6 digit code to the address and the email subject names your app. If no user exists, nothing is sent and nothing is stored. Do not word your screen so that it reveals which case happened.

The code is valid for 10 minutes and works once. Asking for a new code retires the older pending code for that user.

Errors

StatusCodeWhen
400invalid_requestThe body is malformed or email is missing or not an address.
401unauthorizedMissing or invalid key.
403origin_not_allowedThe Origin is not on the allow list.
429rate_limitedOver a limit (see below). A Retry-After header gives the seconds to wait.
503email_unavailable or email_send_failedA user exists and the email could not be sent. Try again later.

Verify a code

POST /v1/email-codes/verify

{ "email": "ada@example.com", "code": "123456" }
FieldTypeRules
emailstringThe address the code was sent to.
codestringThe 6 digit code from the email.
curl -X POST https://api.facecard.dev/v1/email-codes/verify \
  -H "X-FaceCard-Key: fc_pk_live_xxx" \
  -H "Origin: https://app.example.com" \
  -H "Content-Type: application/json" \
  -d '{"email":"ada@example.com","code":"123456"}'

Response

200 OK

{
  "user": {
    "id": "usr_...",
    "email": "ada@example.com",
    "name": "Ada",
    "email_verified": true,
    "created_at": "2026-10-14T12:00:00.000Z"
  },
  "token": "<jwt>",
  "expires_at": "2026-10-14T13:00:00.000Z",
  "method": "email_code"
}
  • The token is a normal user token with amr: ["otp"] and email_verified: true.
  • If the user's email was not verified before, this call verifies it.
  • A successful verify counts as one authentication toward your plan.

Errors

StatusCodeWhen
400invalid_requestThe body is malformed.
400invalid_codeThe code is wrong, expired, already used, or there is no code for that email. One generic message for all of these, so the response never says which.
401unauthorizedMissing or invalid key.
403origin_not_allowedThe Origin is not on the allow list.
429rate_limitedOver a limit, or the user is locked out after too many failures.
429plan_limit_reachedThe team used all its auths for the month.

Five wrong attempts kill the code, and the user must request a new one. Wrong codes also count toward the per user lockout (5 failures in 15 minutes, the same as failed face matches). While locked out, verify answers 429 rate_limited even for a correct code.

Limits

All answer 429 rate_limited with Retry-After.

RouteLimit
Request a code10 per 15 minutes per IP, and 5 per hour per email address per app (counted even when no user exists)
Verify a code20 per 15 minutes per IP
Request a code, per appShares the per minute request budget with match, plus a daily cap of 5% of the plan's monthly auths (at least 50)

See Plans and limits for the rest.

In the SDK

SDK 4.2.0 or later: requestEmailCode(email) and signInWithEmailCode(email, code). See Browser client.

On this page