---
title: "Complete a two-factor challenge."
method: POST
path: "/api/v1/sanctum/token/two-factor"
tags: ["Endpoints"]
---

# Complete a two-factor challenge.

`POST /api/v1/sanctum/token/two-factor`

## Request

- Base URL: `https://hidemail.app`
- URL: `https://hidemail.app/api/v1/sanctum/token/two-factor`
- Auth: none required

Exchanges the `challenge` returned by a `409` from `POST /api/v1/sanctum/token`
for a permanent token. Send either a six-digit `code` from the authenticator
app or one `recovery_code`. A challenge is single-use, expires after five
minutes, and is burned after five wrong codes.

The `two_factor_too_many_attempts` 429 below burns the challenge, so the
client must sign in with the password again to get a new one. This is
distinct from the route's own rate limiter, whose 429 has a plain
`{"message", "status": 429}` body (see `App\Exceptions\Handler::handleApiException`)
and does not burn the challenge.

## Request body

- object
  - `challenge` string, required: The challenge from the 409 response.
  - `device_name` string, required: Device Name. This is validated but not used to name the token: the device name recorded on the token is the one sent with the original `POST /api/v1/sanctum/token` request that issued this challenge.
  - `code` string, nullable: Six-digit authenticator code. Required unless recovery_code is present.
  - `recovery_code` string, nullable: A recovery code. Required unless code is present.

## Response `200`

Verified

- object
  - `token` string

## Other responses

- `410`: Challenge unknown, expired or burned
- `422`: Wrong code
- `429`

---

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