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
POST /api/v1/auth/login
Content-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
POST /api/v1/auth/logout
Authorization: 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
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
For factors that require a server-side initiation (SMS sends a code, WebAuthn returns a challenge):
POST /api/v1/auth/mfa/begin
Content-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
POST /api/v1/auth/mfa/complete
Content-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)
POST /api/v1/auth/webauthn/begin-authentication
Content-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-authentication
Content-Type: application/json
{
"assertion": { ... }
}
Success returns a session and tokens.
Step-up
POST /api/v1/auth/step-up/initiate
Authorization: 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
Error | When |
|---|---|
| Email or password wrong |
| The user record is disabled |
| The user hasn't verified their email and tenant policy requires verification |
| Too many failed attempts; cool-down active |
| The MFA flow_id is older than the policy window |
| The flow_id doesn't exist or doesn't belong to this caller |
Every error response has request_id. Log it.