Login
Exchange an email (or username) and password for a JWT. The identifier is email-first: pass either your email address or your username in the username field — the server matches both and prefers an exact email match. The JWT is then sent in the Authorization: Bearer <token> header for subsequent requests; alternatively, mint an API key for long-running automation.
If the user has MFA enabled, this endpoint returns {"mfa_required": true, "mfa_challenge_token": "…"} with HTTP 200 instead of a JWT — no token is issued. Your client then calls POST /v1/auth/mfa with that mfa_challenge_token plus the TOTP code (the challenge is short-lived — see mfa_expires_in). Login is rate-limited per IP and per username; repeated failures return 429 with a lockout window.
Email verification is enforced — accounts with email_verified: false get 403 with code: "EMAIL_NOT_VERIFIED". If the user's active account enforces SSO for their email domain (via a domain-verified, enforced SSO config), password login is refused before the password is checked with 403 code: "SSO_REQUIRED" — the body carries sso_required, sso_provider, email_domain, and init_url so the client can redirect the user to their identity provider.
MFA still applies after SSO. If a user signs in through their identity provider but also has MFA enabled, they must complete the TOTP second factor before a session is minted — the SSO callback issues its own mfa_challenge_token for POST /v1/auth/mfa.
Request body
username for historical reasons but accepts either value.)Response — success (200 OK)
Authorization: Bearer <token> on subsequent requests. Default expiry is in the JWT's exp claim — usually 24h. The token also carries an active_account_id claim identifying the active account it is scoped to; to act in a different account, mint a new token via POST /v1/user/switch-account.id, username, email, plan, email_verified, plus per-plan limits (max_tunnels, max_endpoints, etc.) and timestamps.Response — MFA required (200 OK)
mfa_challenge_token expires (currently 300 = 5 minutes). After that the client must log in again.id, username, email, role) for UI hints — do not trust as authenticated until MFA succeeds.Errors
username or password, or any unknown field (e.g. email or mfa_code — pass an email address in the username field, and send the TOTP code to /auth/mfa instead; the handler uses strict decoding).code: "EMAIL_NOT_VERIFIED") — direct user to /auth/resend-verification. Or the user's active account enforces SSO for their email domain (code: "SSO_REQUIRED", with sso_required, sso_provider, email_domain, and init_url set) — send the user to init_url to start SSO.Retry-After seconds before retrying.code: "SSO_CHECK_UNAVAILABLE"). Transient — retry shortly.