switching clears the token & responses
No token
27

Disabling MFA and regenerating recovery codes

Multi-factor authentication

Two 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.

  1. GET /v1/me/mfa/methods → check which factors are enrolled first
  2. POST /v1/me/mfa/otp/request → (password-less accounts) send a step-up OTP to your verified phone
  3. DELETE /v1/me/mfa → disable ALL methods (step-up); hasMfaEnabled → false
  4. POST /v1/me/mfa/recovery-codes → invalidate all existing codes and return 8 new ones (step-up)
GET/v1/me/mfa/methodsauthList enrolled MFA methods

Shows 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.

Bearer token
GET https://api.kerja.team/v1/me/mfa/methods
↩︎Response 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.hasMfaEnabledbooleanalways
True when the caller has at least one confirmed MFA method (phone_otp and/or totp). The client uses it to show whether MFA is active overall and to gate enrol/disable affordances.
  • trueCaller has >=1 confirmed MFA method; show MFA-enabled state and offer disable/add-method actions.
  • falseNo confirmed MFA method; prompt the user to enrol a second factor.
data.methodsarray<object>always
List of the caller's enrolled (confirmed) MFA methods. Empty array when none are enrolled. The client iterates it to render active factors and offer the correct enrol/disable actions per method.
data.methods[].methodstringalways
The kind of second factor for this entry. Identifies which enrol/disable flow applies to the row.
  • phone_otpSMS one-time-passcode method is enrolled; offer phone-OTP disable/manage.
  • totpAuthenticator-app TOTP method is enrolled; offer TOTP disable/manage.
data.methods[].confirmedAtstring (ISO 8601 date-time)always
Timestamp when this method was confirmed/activated. The client can display when the factor was enabled.
POST/v1/me/mfa/otp/requestauthSend step-up MFA OTP

No 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.

Bearer token
POST https://api.kerja.team/v1/me/mfa/otp/request
↩︎Response 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.statusstringalways
Fixed acknowledgement that the step-up OTP send was accepted and dispatched to the caller's verified phone (purpose=step_up). The client treats it as confirmation to prompt the user for the numeric phone OTP, which is then supplied to DELETE /v1/me/mfa, POST /v1/me/mfa/recovery-codes, or DELETE /v1/me/phone. The call is re-callable as a throttled resend.
  • acceptedThe only possible value; the OTP request was accepted and an OTP was sent to the verified phone.
DELETE/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).

⛓ Needs an out-of-band value?
Request body (JSON)
ℹ︎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 choose
The user's current account password (1–1024 chars), freely typed by the user. Required for password-holding accounts as the step-up factor to prove a live re-auth. Conditional: at least one of currentPassword or otpCode must be supplied.
otpCodebodystringconditionalout-of-band
Numeric phone OTP the user retrieves from the SMS sent by POST /v1/me/mfa/otp/request (§3.26) and pastes here. Used by password-less (phone-only) accounts as the step-up factor. Conditional: at least one of currentPassword or otpCode must be supplied. Implemented (route-level e2e still pending).
Bearer token
DELETE https://api.kerja.team/v1/me/mfa
↩︎Response 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
Empty object {} signalling success — MFA has been disabled. No payload fields are returned; the client confirms success from the 200 status and empty data envelope.
POST/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.

⛓ Needs an out-of-band value?
Request body (JSON)
ℹ︎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 choose
The user freely types their account password (1-1024 chars). Required for password-holding accounts; supply this OR otpCode (at least one factor is mandatory). In the currently verified implementation, only currentPassword is accepted.
otpCodebodystringconditionalout-of-band
Numeric phone OTP that the user obtains out-of-band (the SMS code delivered after calling POST /v1/me/mfa/otp/request, §3.26) and pastes in. Used as the step-up factor for password-less accounts; supply this OR currentPassword. Implemented (route-level e2e still pending).
Bearer token
POST https://api.kerja.team/v1/me/mfa/recovery-codes
↩︎Response 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
Array of 8 newly generated single-use recovery codes, shown only this once. The client must surface them to the user to save securely; the previously issued codes are now invalid.