---
title: "Complete two-phase login with a TOTP or recovery code"
method: POST
path: "/auth/login/totp"
---

# Complete two-phase login with a TOTP or recovery code

`POST /auth/login/totp`

## Request

- Base URL: `https://app.yoping.me/api/v1`
- URL: `https://app.yoping.me/api/v1/auth/login/totp`
- Auth: none required

Phase 2 of login for accounts with TOTP enrolled: consumes the `totp_token` returned by POST /auth/login's `totp_required` response, verifies a 6-digit TOTP code (or a recovery code), and on success rotates and sets the `yp_session` cookie, returning the same bootstrap payload as GET /me. The token is single-use and expires 5 minutes after phase 1; a wrong code does not burn it, but each code (TOTP step or recovery code) can only ever be consumed once. Limited to 10 login attempts per IP per minute plus 5 code attempts per user per minute.

## Request body

- LoginTotpRequest
  - `totp_token` string, required: The token returned as `totp_token` by POST /auth/login's phase-1 `totp_required` response.
  - `code` string, required: A 6-digit TOTP code from the enrolled authenticator, or an unused recovery code.

## Response `200`

Logged in; the session cookie is set and the /me bootstrap payload is returned.

- object: Uniform success envelope: the payload is always under `data`.
  - `data` Me, required: The SPA bootstrap payload, returned by GET /me and POST /auth/login.
    - `user` object, required
      - `id` string, required
      - `email` string, email, required
      - `email_verified` boolean, required
      - `totp_enabled` boolean, required: Whether two-factor authentication (TOTP) is enrolled and confirmed for this account.
      - `created_at` string, date-time, required
    - `account` object, required
      - `id` string, required
      - `name` string, required
      - `plan` 'free' | 'pro' | 'team', required
      - `status` string, required
      - `deleted_at` string, date-time, nullable, required: Set while the account is in its deletion grace window.
    - `limits` object, required: The plan's enforced caps.
      - `max_monitors` integer, required
      - `min_interval_seconds` integer, required
      - `max_regions` integer, required: Regions per monitor; the effective cap is min(max_regions, active fleet size).
      - `allowed_channel_types` string[], required
      - `result_retention_days` integer, required
      - `mcp_history_days` integer, required: MCP history window in days; 0 means unlimited.
      - `mcp_queries_per_day` integer, required
      - `max_alert_after_seconds` integer, required: Per-monitor DOWN-alert delay cap in seconds; 0 means the knob is not available on this plan.
      - `max_region_alert_after_seconds` integer, required: Per-monitor region-degradation alert delay cap in seconds; 0 means the knob is not available on this plan.
    - `usage` object, required
      - `monitors` integer, required: Current monitor count.
    - `regions` string[], required: Active region codes; resolve display names via GET /regions.
    - `billing` object, required: Billing summary. When `configured` is false (billing disabled on this deployment) every other field is its zero value and the dashboard hides billing UI. Subscription fields are null until the account has a subscription.
      - `configured` boolean, required
      - `has_subscription` boolean, required
      - `status` string, nullable, required: Paddle subscription status verbatim (e.g. trialing, active, past_due, canceled).
      - `plan` 'pro' | 'team' | 'null', nullable, required
      - `interval` 'monthly' | 'annual' | 'null', nullable, required
      - `current_period_end` string, date-time, nullable, required
      - `trial_end` string, date-time, nullable, required
      - `cancel_at_period_end` boolean, required
      - `trial_available` boolean, required: True when the account has never had a subscription; the first checkout gets a 14-day trial.
      - `can_manage` boolean, required: True when a billing customer exists, so POST /billing/portal will succeed.
    - `recovery_codes_remaining` integer: Count of unused two-factor recovery codes. Present only when totp_enabled is true.

## Other responses

- `400`: Invalid input (code `validation_failed`): malformed JSON, a missing or out-of-range field, bad pagination parameters, or a rejected token.
- `401`: Not authenticated (code `unauthorized`): missing, invalid, or expired session; invalid login credentials; or a Bearer token, which this surface rejects.
- `403`: Forbidden. Code `forbidden` for a cross-origin (CSRF) request blocked by origin validation or for API-key management attempted without a browser session; `account_suspended` when a suspended account attempts a mutation; `account_deleted` when an account in its deletion grace window calls a non-exempt route.
- `429`: Too many requests (code `rate_limited`). A global per-IP budget of 300 requests/minute (burst 60) applies to every endpoint; login, signup, and test alerts have tighter dedicated limits.

---

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