---
title: "Begin email enrollment during a forced-2FA login."
method: POST
path: "/api/v1/tokens/two-factor/enroll/email/begin"
tags: ["Public"]
---

# Begin email enrollment during a forced-2FA login.

`POST /api/v1/tokens/two-factor/enroll/email/begin`

## Request

- Base URL: `https://go.outseta.com`
- URL: `https://go.outseta.com/api/v1/tokens/two-factor/enroll/email/begin`
- Auth: none declared

Call this when `POST /api/v1/tokens` returned
`two_factor_enrollment_required` and the user chooses email. Post the
enrollment token from that response:
            
    { "enrollment_token": "eyJ..." }
            
A verification code is emailed to the user and a challenge is returned:
            
    {
      "challenge_token": "eyJ...",
      "mechanism": "Email",
      "masked_destination": "b***@outseta.com",
      "expires_in": 600
    }
            
Confirm the code via `POST /api/v1/tokens/two-factor/enroll/email/confirm`.
Returns `401` if the enrollment token is invalid/expired and `403` if
forced enrollment does not apply to this user. Rate limited to 5
requests per minute (`429`).

## Query parameters

- `data` unknown, required

## Response `200`

A code was emailed; the enrollment-test challenge is returned.

- TwoFactorChallengePayload: Returned when a login cannot complete with a password alone because the user has two-factor authentication enabled. Carries the opaque challenge token that must be echoed back to POST /api/v1/tokens/two-factor (or the recovery / resend / switch-mechanism endpoints) along with enough metadata for a client to render the verification step without a second round-trip. The property names are snake_case to match the wire format of the other token endpoints (see TokenPayload).
  - `two_factor_required` boolean, nullable: Present and true on the 202 response from POST /api/v1/tokens when the user has at least one verified 2FA method. Absent on the resend / switch-mechanism responses, which only ever follow an already-issued login challenge.
  - `two_factor_enrollment_required` boolean, nullable: Present and true instead of two_factor_required when the tenant forces 2FA but the user has not yet enrolled any method. In that case only challenge_token and expires_in are populated and the client must route the user through the mid-login enrollment endpoints (/api/v1/tokens/two-factor/enroll/...) before a token can be issued.
  - `challenge_token` string, nullable: Short-lived signed JWT (audience outseta:2fa-challenge) that identifies this challenge. Echo it back verbatim to complete the login.
  - `mechanism` string, nullable: The mechanism this challenge was issued against: Email or Totp (authenticator app). For Email a code has already been sent to the user; for Totp the user reads the current code from their authenticator app and nothing is sent.
  - `masked_destination` string, nullable: A masked view of where an emailed code was sent (e.g. b***@outseta.com), suitable for display. Empty when mechanism is Totp.
  - `expires_in` integer: Seconds until the challenge expires (600). After this the challenge_token can no longer be verified and the login must restart.
  - `available_mechanisms` string[], nullable: Every verified mechanism enrolled for this user (excluding recovery codes), e.g. ["Totp", "Email"]. A client can offer a "use a different method" option for any value other than the current mechanism via POST /api/v1/tokens/two-factor/switch-mechanism.
  - `recovery_codes_available` boolean, nullable: true when the user has a batch of recovery codes on file, in which case POST /api/v1/tokens/two-factor/recovery can be used as a fallback if they cannot produce a primary code.

## Other responses

- `401`: The enrollment token is missing, invalid or expired.
- `403`: Forced 2FA enrollment does not apply to this user.

---

[API](https://skmtc.dev/outseta/apis/outseta-api.md) · [All operations](https://skmtc.dev/outseta/apis/outseta-api/llms.txt) · [OpenAPI document](https://skmtc.dev/outseta/apis/outseta-api/revisions/c362823af82d?raw)
