Enroll
const url = 'https://example.com/api/auth/mfa/enroll';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"current_password":"example","mfa_pending_token":"example","otp_code":"example"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://example.com/api/auth/mfa/enroll \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "current_password": "example", "mfa_pending_token": "example", "otp_code": "example" }'Generate a new TOTP secret + QR code. Re-enrolling overwrites any unconfirmed prior secret (idempotent) — mfa_enabled stays False until /enroll/confirm verifies a real code from it.
If the user already has mfa_enabled=True, this is a RE-enrollment (e.g. lost device) and requires the same re-auth bar as /disable (current password + a valid current-factor OTP code) — otherwise a bare session could silently swap the TOTP factor to one only an attacker knows, with no password/OTP ever demanded (the forced mid-login path never hits this branch: login() only mints an enroll-purpose pending token when mfa_enabled is already False).
Authorizations
Section titled “ Authorizations ”Request Bodyrequired
Section titled “ Request Bodyrequired ”Enroll request. Either an authenticated session (voluntary, MEMBER/ VIEWER) or an mfa_pending/purpose=enroll token (forced mid-login, OWNER/ADMIN) authorizes this — see app/api/mfa.py for the dual-context dependency.
current_password/otp_code are required ONLY when re-enrolling a user who already has mfa_enabled=True (re-auth bar, same as /disable) — a bare session must not be able to silently swap an already-active TOTP factor to one only the caller knows, which would otherwise let a hijacked session (or an XSS the CSRF header exists to resist) lock the real user out and take over their 2FA with no password/OTP ever demanded.
object
Example generated
{ "current_password": "example", "mfa_pending_token": "example", "otp_code": "example"}Responses
Section titled “ Responses ”Successful Response
QR code (base64 PNG) + manual-entry secret. Deliberately omits the otpauth:// URI as a separate field — it just re-encodes the same secret a second time in the clear for no benefit.
object
Example generated
{ "qr_code_png_base64": "example", "secret_base32": "example"}Validation Error
object
object
object
Example generated
{ "detail": [ { "ctx": {}, "input": "example", "loc": [ "example" ], "msg": "example", "type": "example" } ]}