Disabling MFA and regenerating recovery codes
Multi-factor authenticationTwo security-sensitive self-service operations that follow once MFA is on. Both are STEP-UP actions — a live session is not enough; a fresh factor must be re-verified inside the handler.
- GET /v1/me/mfa/methods → check which factors are enrolled first
- POST /v1/me/mfa/otp/request → (password-less accounts) send a step-up OTP to your verified phone
- DELETE /v1/me/mfa → disable ALL methods (step-up); hasMfaEnabled → false
- POST /v1/me/mfa/recovery-codes → invalidate all existing codes and return 8 new ones (step-up)
/v1/me/mfa/methodsauthList enrolled MFA methodsShows the enabled methods (phone_otp / totp) and whether MFA is on — review before disabling.
Field guide — what each value means & where it comes fromno inputs
Bodyless GET — no input fields. Requires a valid bearer access token (the gateway injects x-gateway-context from it); a missing/invalid context yields 403 forbidden.
GET https://api.kerja.team/v1/me/mfa/methodsResponse guide — what comes back & what each value means4 fields
200 OK, JSON { data } envelope (not raw). No notable headers (no no-store on this read endpoint).
data.hasMfaEnabledbooleanalwaystrue— Caller has >=1 confirmed MFA method; show MFA-enabled state and offer disable/add-method actions.false— No confirmed MFA method; prompt the user to enrol a second factor.
data.methodsarray<object>alwaysdata.methods[].methodstringalwaysphone_otp— SMS one-time-passcode method is enrolled; offer phone-OTP disable/manage.totp— Authenticator-app TOTP method is enrolled; offer TOTP disable/manage.
data.methods[].confirmedAtstring (ISO 8601 date-time)always/v1/me/mfa/otp/requestauthSend step-up MFA OTPNo body. Sends a step-up OTP (purpose=step_up) to your verified phone so a PASSWORD-LESS (phone-only) account can satisfy step-up on a sensitive self-action — disabling MFA, regenerating recovery codes, or unlinking the phone. Always 202.
Field guide — what each value means & where it comes fromno inputs
No request body, no path/query params, no Idempotency-Key. It only needs the caller's authenticated session: a valid Authorization bearer access token plus the gateway-injected x-gateway-context (both supplied automatically by the tester, not typed here). The OTP is sent to the caller's already-verified phone, so there is nothing for the user to fill in.
POST https://api.kerja.team/v1/me/mfa/otp/requestResponse guide — what comes back & what each value means1 field
202 Accepted with a { data } envelope and no-store caching (Cache-Control: no-store). Always the same single shape; no token/cookie set. The OTP itself is delivered out-of-band to the caller's verified phone, not in this response.
data.statusstringalwaysaccepted— The only possible value; the OTP request was accepted and an OTP was sent to the verified phone.
/v1/me/mfaauthDisable MFA (step-up)Step-up is MANDATORY: a password-holding user supplies currentPassword, a password-less user an otpCode ({ currentPassword?, otpCode? }, ≥ 1 required). A wrong factor → 401. Turns off ALL methods. Does NOT revoke existing sessions and does NOT untrust devices: sessions created with amr [pwd, mfa] keep running, only future logins skip the MFA hop. TENANT ENFORCEMENT REFUSES THE CALL OUTRIGHT: if ANY tenant you hold an active membership in requires MFA, this answers 403 mfa_enrollment_required and nothing is disabled — enforcement is at the point of REMOVAL, not only at session issue and switch-tenant. Letting the disable succeed and rejecting you at the next login would make the policy unenforceable in the one direction that matters, and the person who paid for it would be you, locked out by an action the API had just called fine. Any one confirmed method satisfies enforcement, so a member holding two may still remove one (scenario 25 / 26).
Field guide — what each value means & where it comes from2 fields
Step-up re-auth body (.strict()) — supply AT LEAST ONE factor. Also requires a live authenticated session (Bearer access token) plus the gateway-injected x-gateway-context; this is not enough on its own, hence the body factor. Step-up is checked FIRST, then tenant enforcement: if any tenant you hold an active membership in requires MFA, the call is refused 403 mfa_enrollment_required and nothing is disabled — a member cannot opt out of a policy their admin set (scenario 48).
currentPasswordbodystringconditionalyou chooseotpCodebodystringconditionalout-of-bandDELETE https://api.kerja.team/v1/me/mfaResponse guide — what comes back & what each value means1 field
200 OK, { data } envelope with Cache-Control: no-store. Body is an empty data object; MFA is disabled (all methods cleared, hasMfaEnabled=false). Existing sessions are NOT revoked.
dataobject (empty)always/v1/me/mfa/recovery-codesauthRegenerate recovery codes (step-up)Step-up (currentPassword OR otpCode). Invalidates all existing codes and returns 8 new ones, shown ONCE — save them immediately, they are not retrievable after the response. MFA must already be enabled.
Field guide — what each value means & where it comes from2 fields
Regenerates MFA recovery codes (invalidate-and-reissue). Requires a valid access token plus gateway context, and is step-up: you must supply at least ONE of the two body factors. MFA must already be enabled.
currentPasswordbodystringconditionalyou chooseotpCodebodystringconditionalout-of-bandPOST https://api.kerja.team/v1/me/mfa/recovery-codesResponse guide — what comes back & what each value means1 field
200 OK with a { data } envelope and Cache-Control: no-store. Previous recovery codes are immediately invalidated and 8 fresh codes are returned exactly once.
data.recoveryCodesstring[]always