SECURITY_SETUP_REQUIRED challenge instead of tokens:
session_token from the challenge through every step below; nothing here needs a bearer
token, because the user doesn’t have one yet.
The flow
Pick one of the two paths, then call Finish security setup to exchange thesession_token for tokens.
Passkey path (recommended)
- Start —
POST /v1/auth/security-setup/passkey/register/startwith thesession_token. You get backpublicKeycreation options. - On the device — pass those options to the browser’s
navigator.credentials.create()(or the platform credential API on native) so the user creates the passkey with fingerprint, face, or screen lock. - Finish —
POST /v1/auth/security-setup/passkey/register/completewith the attestation the device produced.
2FA path
- Set up —
POST /v1/auth/security-setup/mfa/setupwith thesession_token. You get back asecretand anotpauth://qr_uri; render the QR code for the user to scan into their authenticator app. - Confirm —
POST /v1/auth/security-setup/mfa/confirmwith the first 6-digit code the app shows.
Then: finish and get tokens
Once a passkey or 2FA is set up, call Finish security setup (POST /v1/auth/security-setup/complete) with the
session_token. It returns the full set of tokens and
ends the SECURITY_SETUP_REQUIRED state.
The
session_token is short-lived. If it expires you’ll get 410 SESSION_EXPIRED — send
the user back through the flow that issued it (for example, log in again) to get a fresh
one.