Time-based one-time codes from an authenticator app — Google Authenticator, Authy, 1Password, anything that reads a standard otpauth:// URI. This is a step-up factor, not a way to sign up or log in from zero.
TOTP attaches to a user who is already authenticated. There's no registerWithTotp() or loginWithTotp() — enrollTotp() and verifyTotp() both require a session, same as calling any other method after login()/register() succeeds. This is deliberate, not a limitation to work around: threshold1's identity model requires an email, and TOTP has no identity of its own to sign up with — it only makes sense as something you add to an account that already exists.
await auth.login("user@example.com"); // establish a session first
const { otpauthUrl, qrCodeDataUrl, secret } = await auth.enrollTotp();
// Show qrCodeDataUrl for scanning, or secret for manual entry.
const { enrolled } = await auth.verifyTotp("123456");
// The first successful call after enrollTotp() completes enrollment.
// Every call after that is an ordinary step-up check.
Calling enrollTotp() again always replaces any existing secret immediately, even a previously-verified one — only call it when the user genuinely intends to (re)set up TOTP, not as a "get the current secret" accessor.
enrollTotp() response.Each code is usable exactly once, even within its ~90-second valid window — a code that verified successfully can't be replayed a second time before it naturally expires. This is enforced by tracking the specific time-step each code corresponds to and atomically consuming it on first use, so a captured or logged code is only ever good for one login, not for the whole window it happened to fall in.
verifyTotp() returns invalid_code for a wrong or already-used code. See the Error Reference for details.