The auth API is the imperative side of IntelliAuth's sign-in surface — the endpoints that take credentials in, validate them, and either return a session or kick off MFA. Most apps drive these via the React SDK and never touch them directly; this topic is the reference for non-SDK consumers.
Sign in with email + password
Section titled “Sign in with email + password”POST /api/v1/auth/loginContent-Type: application/json
{ "email": "user@cymmetri.com", "password": "..."}Three possible response shapes:
Success without MFA:
{ "data": { "state": "success", "session": { "id": "ses_01HZX...", "user_id": "usr_01HZX..." }, "access_token": "...", "refresh_token": "...", "expires_in": 3600 }}MFA required:
{ "data": { "state": "mfa_required", "flow_id": "flow_01HZX...", "available_factors": ["webauthn", "totp", "sms"] }}Failed:
{ "error": "invalid_credentials", "message": "Email or password is incorrect.", "request_id": "req_01HZX..."}The success-without-MFA shape includes tokens directly; this endpoint is the password-only entry point. For OAuth flows (PKCE, etc.), users do not call this — they go through /oauth2/authorize.
Sign out
Section titled “Sign out”POST /api/v1/auth/logoutAuthorization: Bearer <access-token>Returns 204 No Content on success. Revokes the current session. To revoke other sessions too, hit the per-session revoke endpoint instead (see Me API).
MFA factor list
Section titled “MFA factor list”GET /api/v1/auth/mfa/factors?flow_id=flow_01HZX...When a flow returns mfa_required, fetch the factors available to this specific user:
{ "data": [ { "id": "factor_01HZX...", "kind": "webauthn", "label": "MacBook Pro" }, { "id": "factor_01HZY...", "kind": "totp", "label": "1Password" } ]}Show these to the user. They pick one.
Begin MFA step
Section titled “Begin MFA step”For factors that require a server-side initiation (SMS sends a code, WebAuthn returns a challenge):
POST /api/v1/auth/mfa/beginContent-Type: application/json
{ "flow_id": "flow_01HZX...", "factor_id": "factor_01HZX..."}Response varies by factor kind:
{ "data": { "kind": "totp" } }{ "data": { "kind": "webauthn", "challenge": "<base64url-bytes>", "allow_credentials": [ ... ] }}{ "data": { "kind": "sms", "sent_to_last4": "0100" } }Complete MFA step
Section titled “Complete MFA step”POST /api/v1/auth/mfa/completeContent-Type: application/json
{ "flow_id": "flow_01HZX...", "factor_id": "factor_01HZX...", "code": "123456"}For WebAuthn, the body carries the assertion instead of code:
{ "flow_id": "flow_01HZX...", "factor_id": "factor_01HZX...", "assertion": { "id": "...", "rawId": "...", "response": { ... }, "type": "public-key" }}Success response carries tokens, identical to a non-MFA sign-in.
WebAuthn primary authentication (passwordless)
Section titled “WebAuthn primary authentication (passwordless)”POST /api/v1/auth/webauthn/begin-authenticationContent-Type: application/json
{}Response:
{ "data": { "challenge": "<base64url-bytes>", "rp_id": "banking-cymmetri.intelliauth.local", "user_verification": "required" }}The browser calls navigator.credentials.get() with the challenge, then:
POST /api/v1/auth/webauthn/finish-authenticationContent-Type: application/json
{ "assertion": { ... }}Success returns a session and tokens.
Step-up
Section titled “Step-up”POST /api/v1/auth/step-up/initiateAuthorization: Bearer <current-access-token>Content-Type: application/json
{ "required_acr": "aal2"}Response indicates which factor to complete:
{ "data": { "flow_id": "step_01HZX...", "factor": { "kind": "webauthn", "challenge": "..." } }}Complete via the MFA-complete endpoint with the returned flow_id. The response carries a new access token whose acr claim reflects the new AAL.
Common errors
Section titled “Common errors”| Error | When |
|---|---|
invalid_credentials | Email or password wrong |
account_disabled | The user record is disabled |
email_unverified | The user hasn't verified their email and tenant policy requires verification |
mfa_factor_locked | Too many failed attempts; cool-down active |
flow_expired | The MFA flow_id is older than the policy window |
flow_invalid | The flow_id doesn't exist or doesn't belong to this caller |
Every error response has request_id. Log it.