---
title: "Complete login when user has two-factor authentication enabled."
method: POST
path: "/api/v1/tokens/two-factor"
tags: ["Public"]
---

# Complete login when user has two-factor authentication enabled.

`POST /api/v1/tokens/two-factor`

## Request

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

Call this after `POST /api/v1/tokens` returns `two_factor_required`.
Post the challenge token from that response together with the user's
one-time code (the emailed code, or the current code from their
authenticator app):
            
    { "challenge_token": "eyJ...", "code": "123456" }
            
On success the response is `200` with the final access token, in the
same shape as `POST /api/v1/tokens`:
            
    { "access_token": "eyJ...", "refresh_token": "...", "token_type": "Bearer", "expires_in": 31536000 }
            
An incorrect code returns `400` (`invalid_grant`). A code has at most
five attempts before the challenge locks. An expired challenge returns
`410` (`challenge_expired`) — restart at `POST /api/v1/tokens`. The
endpoint is rate limited to 10 requests per minute (`429`).

## Request body

- TwoFactorVerifyRequest: Request body for POST /api/v1/tokens/two-factor: the challenge token from the login response plus the user's one-time code. Property names are snake_case to match the wire format of the token endpoints. The action binds the body as a loose JObject at runtime; this type exists so the API docs describe the body instead of an opaque data parameter (see [OpenApiRequestBody] on the action).
  - `challenge_token` string, nullable: The challenge_token returned by POST /api/v1/tokens when two-factor authentication is required. Echo it back verbatim.
  - `code` string, nullable: The user's one-time code: the emailed code for an Email challenge, or the current code from their authenticator app for a Totp challenge.

## Response `200`

The code was accepted; the access token (JWT) is returned.

- TokenPayload
  - `access_token` string, nullable
  - `authentication_callback_url` string, nullable
  - `expires_in` integer
  - `id_token` string, nullable
  - `refresh_token` string, nullable
  - `token_type` string, nullable

## Other responses

- `400`: The code was incorrect or missing (`invalid_grant`).
- `410`: The challenge expired (`challenge_expired`); restart the login.

---

[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)
