Four flows end with a SECURITY_SETUP_REQUIRED challenge instead of tokens: confirming a sign-up, an email-verified password login, changing a temporary password, and finishing a password reset.
The account exists, but the user has no tokens yet. They need a second factor first: a passkey (recommended) or 2FA. The session_token from the challenge is what you send on every call below. None of them take a bearer token, because there isn’t one yet.

The flow

Pick one path, then finish. Three calls for a passkey, three for 2FA.
  1. Set up a passkey (start): send the session_token. You get back publicKey creation options.
  2. Create the passkey on the device. Pass those options to navigator.credentials.create() on the web, or the platform credential API on iOS and Android. The OS asks the user for Face ID, Touch ID, or their screen lock and returns an attestation.
  3. Set up a passkey (finish): send the session_token and the attestation. The passkey is now stored.

2FA path

  1. Set up 2FA (start): send the session_token. You get back a secret and an otpauth:// qr_uri. Render the QR code so the user can scan it into their authenticator app.
  2. Set up 2FA (confirm): send the session_token and the first 6-digit code the app shows. 2FA is now on.

Finish and get tokens

Whichever path you took, call Finish security setup with the session_token. It returns the full set of tokens and the user is signed in.
Calling finish before a passkey or 2FA is set up returns 403 SECURITY_SETUP_NOT_COMPLETE.
The session_token is short-lived. If it expires you get 410 SESSION_EXPIRED. Send the user back through the flow that issued it (log in again, for example) to get a fresh one.