Passkey login
Org-member (dashboard) passkey endpoints live under /api/passkey. On register/login a web origin gets an HttpOnly cookie (SDK callers keep the token from the response body).
Your app’s end users have their own set under /api/public/apps/{clientKey}/auth/passkey: POST /register-options, /register, /login-options and /login. Adding a passkey to an existing end-user account requires that user’s Authorization: Bearer token on /register-options; without it the call answers 409 LOGIN_REQUIRED_TO_ADD_PASSKEY.
One credential per account (stable user handle)
Section titled “One credential per account (stable user handle)”Every enrollment endpoint sets the WebAuthn user.id (userHandle) to a deterministic value derived from the normalized email, not a random or account-row id. Platform authenticators (Windows Hello, Touch ID) de-duplicate resident credentials by (rpId, userHandle), so re-registering the same email — including after a database reset — replaces the existing passkey instead of piling up a new one. This keeps the authenticator’s credential set at one per account, which is what keeps the sign-in dialog fast (it has a single credential to enumerate, not dozens).
The userHandle is not used for identity: login verification always resolves the account from the returned credentialId against the database, never from the handle.
Register (new account)
Section titled “Register (new account)”| Method · Route | Purpose |
|---|---|
POST /register/options | Attestation options (Username = email, optional SecurityMode, turnstileToken when bot protection is on) |
POST /register | Complete registration → auto-login (cookie issued) |
Add device (rotation)
Section titled “Add device (rotation)”| Method · Route | Purpose |
|---|---|
POST /add/options | Enrollment options (authenticated). The user’s existing passkeys are listed in excludeCredentials, so an authenticator that already holds one refuses to create a duplicate |
POST /add | Add a passkey to the current user (optional orgSlug) |
| Method · Route | Purpose |
|---|---|
POST /login/options | Assertion options (optional Username = email) |
POST /login | Complete assertion → cookie / token |
Helpers
Section titled “Helpers”GET /exists?email= (does the user exist / has passkey / login provider; limited to 60 requests per minute per IP) and GET /browser-match?fingerprint= (authenticated) round out the set.