Skip to main content
This document describes all 2FA API endpoints. Base URL: https://console.vast.ai.

Authentication

The 2FA endpoints accept different authentication shapes depending on whether the caller is the UI (logged-out or logged-in) or the CLI. Several endpoints can be called without a Bearer token — they accept a tfa_secret from a prior login or send-code response instead.

UI flow (logged out — login path)

When a user is logged out and entering credentials in the UI, the regular login endpoint returns a tfa_secret. The client then calls 2FA endpoints (tfa/, tfa/sms/, tfa/email/, tfa/sms/resend/) using only that tfa_secret — no Bearer token is required. The login endpoint completes the 2FA challenge and returns the user object; for this UI flow, the session is established via cookie/header (the remember() mechanism on the backend) — session_key is not returned in the response body. See the per-endpoint Login section below for the CLI-vs-UI distinction on session_key placement.

UI flow (logged in — adding/removing methods)

When the user is already logged in via the UI, calls to 2FA endpoints carry a Bearer token (Authorization: Bearer <api_key> header set by the frontend client) — and the browser’s session cookie is sent in parallel as a separate auth mechanism. Calls may also carry a tfa_secret from a recent send-code response. For some endpoints (e.g., authorize-new-method, remove method), both are accepted; the docs per endpoint specify the exact requirement.

CLI flow

CLI callers authenticate with their normal API Key as a Bearer token. The 2FA endpoints called from the CLI typically don’t need a tfa_secret because the API key already authenticates the user — except for paths where a code-flow is invoked, in which case the secret returned by the send-code endpoint is passed back to verify the code.

Per-endpoint summary

Footnote on bearer + secret cells:
  • For DELETE /api/v0/tfa/ and PUT /api/v0/tfa/regen-backup-codes/, secret is only required when the verification method is SMS or email (challenge-based). For TOTP and backup_code verification, the secret field is not needed — the TOTP secret lives in the DB and backup codes are self-validating.
  • For POST /api/v0/tfa/confirm-new/, secret is always required — but its meaning depends on the method being added: for SMS, it’s the tfa_secret from the SMS challenge; for TOTP, it’s the base32 TOTP secret returned by /tfa/totp-setup/. Same JSON field, different value depending on tfa_method.

Email verification prerequisite

Email verification (email_verified_at non-null on the user record) is required before any 2FA endpoint that sends codes (SMS or email). Because first-time 2FA setup uses the email-send flow as its authorize step, email verification is also a prerequisite for the first-method setup. Adding subsequent methods may or may not require it, depending on which existing method the user authorizes with — TOTP-only or backup-code authorization paths don’t trigger code sending and therefore don’t require email verification. Admin users (is_admin=True) are exempt from the email-verified prerequisite on the SMS send endpoints (POST /api/v0/tfa/sms/, POST /api/v0/tfa/sms/resend/) — useful for support and testing flows. The exemption does not apply to other endpoints.

Status

Get 2FA status

Returns the current 2FA configuration for the authenticated user. Response
Fields:
  • success — request status flag
  • tfa_enabled — boolean reflecting the users.tfa_enabled DB column. This is a boolean only — it does NOT distinguish enabled from legacy. To get that enum, use the tfa_status field on the user object returned by show user / GET /api/v0/users/current/.
  • methods[] — list of configured 2FA methods. Each entry includes:
    • id — method ID (use as tfa_method_id when disambiguating multiple methods of the same type)
    • user_id — owning user
    • method"sms" or "totp"
    • label — user-set display label
    • phone_number — masked (last 4 digits shown) for SMS; TOTP secrets are never returned
    • is_primary — whether this is the user’s primary method
    • fail_count — current failed-attempt count for the method
    • locked_until — Unix timestamp when this method’s lockout expires, or null if not locked
    • created_at / last_used — timestamps
  • backup_codes_remaining — count of unused backup codes
  • new_method_authorized — whether the user has completed the authorization step to add a new method (short-lived, 30-minute window)
This endpoint does not return the enabled|disabled|legacy enum — the tfa_enabled boolean above is a binary flag. To distinguish enabled from legacy, read the tfa_status field on the user object returned by show user / GET /api/v0/users/current/.

Login

Complete 2FA login

Verifies a 2FA code to complete login. Called after passing username/password authentication. Request body
Response Returns the authenticated user object (the same fields as the show user endpoint), plus a session_key field on CLI-flow responses and the conditional response fields documented below.
session_key placement depends on caller flow:
  • CLI flow (request authenticated via API key as Bearer): the response body includes a session_key field. CLI callers automatically use this value as Authorization: Bearer <session_key> for subsequent authenticated requests.
  • UI logged-out flow (request authenticated via username/password + tfa_secret): the session is set as a cookie/header via the remember() mechanism and is NOT included in the response body. The browser automatically uses cookie auth for subsequent requests; no client-side session_key handling is needed.
Conditional fields on the login response:
  • backup_codes_remaining — included only when the caller authenticated with a backup_code AND has at least one remaining unused code. The value is the count of remaining unused backup codes after this consumption. Note: the field is omitted when the count is 0 — a user consuming their last backup code will not see this field in the response. Treat its absence as either “didn’t use a backup code” or “used the last one”; check tfa_status and methods[] from GET /api/v0/tfa/status/ for definitive state.
  • legacy_tfa_user: true — included only for legacy 2FA accounts (the field is omitted otherwise — there is no explicit false value sent). Signals the frontend to show a migration banner.

Remove a 2FA method

Removes a configured 2FA method after verifying the request. Request body
Response
If the last method is removed: {"msg": "2FA Successfully Disabled"}tfa_enabled is set to false and all backup codes are soft-deleted.
The same "2FA Successfully Disabled" message is also returned when a legacy 2FA account calls DELETE /api/v0/tfa/. In that case, no backup codes are soft-deleted because legacy users do not have any backup codes stored.

Sending Codes

Send email verification code

Sends a 6-digit 2FA code to the user’s verified email address. Used for email login verification and for the authorize-new-method flow. Request body
Response
The returned secret (tfa_secret from this response) must then be passed back as the secret field when the user submits the email code, on whichever 2FA endpoint completes the verification — login (POST /api/v0/tfa/), authorize-new-method confirm (PUT /api/v0/tfa/authorize-new-method/), method removal (DELETE /api/v0/tfa/), regen backup codes (PUT /api/v0/tfa/regen-backup-codes/), or any other endpoint that accepts email as a verification method. When called with a Bearer token and no secret in the request body, this endpoint generates a fresh tfa_secret for a new challenge and returns it — useful for CLI callers and for UI clients starting (or restarting) the email-verification flow. This is not a “recovery” of a prior secret — every bearer-authenticated call without a secret produces a new challenge. (The actual challenge-recovery behavior — looking up an existing challenge by phone number and rotating its secret — lives in POST /api/v0/tfa/sms/resend/.)

Send SMS verification code

Sends a 6-digit 2FA code via SMS (Twilio). Used both for login and for adding an SMS method. Request body
Response
Returns tfa_challenge_exists (HTTP 400) if a recent code was already sent; call the resend endpoint instead.

Resend SMS verification code

Resends the SMS code for an existing challenge. Required if the original code was not received. Request body
Response

Set up TOTP (Authenticator app)

Generates a TOTP secret and provisioning URI for an authenticator app (Google Authenticator, Authy, etc.). This endpoint does not check whether the user is authorized to add a new method — it only generates and returns the secret. The actual authorization-and-method-add check happens in POST /api/v0/tfa/confirm-new/ (see below). Until confirm-new is called successfully with a valid TOTP code, no method is added to the account. Response
The provisioning_uri is a string that encodes the TOTP secret in otpauth:// format. The UI and CLI each have client-side tools to convert this string into a QR code for display; the backend does not produce a QR image. After the user scans the QR or enters the secret manually into their authenticator app, call POST /api/v0/tfa/confirm-new/ with tfa_method: "totp" and the 6-digit code from the app.

Adding Methods

Adding a new 2FA method requires completing a two-step authorization flow first.

Step 1 — Request authorization

Starts the authorization flow to add a new method. For users with no existing methods, forces tfa_method = "email" automatically. Request body
Response (code flow)
Response (backup code flow)

Step 2 — Confirm authorization

Verifies the code sent in Step 1 and grants a 30-minute window to add a new method. Request body
Response
After this call succeeds, GET /api/v0/tfa/status/ returns new_method_authorized: true.

Confirm and activate new method

Verifies the code for a newly configured method and activates it. Request body
Response
For TOTP confirm-new, the msg reads "TOTP 2FA method added successfully." instead of the SMS variant. The rest of the response shape is identical. If this is the first 2FA method, the response also includes:
Backup codes are returned once, in plaintext. They cannot be retrieved again.

Update a method

Updates the label or primary status of a 2FA method. Does not apply to email (email is not stored as a method). Request body
Response

Backup Codes

Regenerate backup codes

Replaces all existing backup codes with a fresh set of 10. Requires verification with an active 2FA method or an existing backup code. This endpoint also handles the legacy account migration case: if the user has tfa_enabled=true but no user_tfa_methods rows of any kind (active or soft-deleted), it backfills an SMS method using the phone number on file before generating codes — this is the legacy-account migration path. Request body
Response
Returns 10 new backup codes in plaintext. They are returned once only.

Error Codes

Note: the same missing_auth_value error code is also returned with HTTP 401 by verify_user_via_secret_or_auth when both tfa_secret and Bearer token are missing — the request has no usable authentication context.

untracked_2fa_found detail

This error is returned only when a legacy TFA user calls the deprecated /api/v0/tfa/test-submit/ endpoint. Users accessing the current UI call /api/v0/tfa/confirm-new/ and are not affected. Legacy users are identified by a “migrate your 2FA” banner in Account Settings and should call PUT /api/v0/tfa/regen-backup-codes/ to complete migration before adding a new method.

Deprecated Endpoints

These paths remain active for backwards compatibility but should not be used in new integrations: