Mechanism
Bearer tokens — not cookie sessions. The web and mobile clients obtain a token by logging in; third-party scripts and integrations use a user-bound API key instead (see API Access), which is the same bearer mechanism with a read or read & write scope. There is no OAuth client-credentials flow.
Login endpoint
POST /api/v1/auth/login — email/password. Two outcomes:
- Success (no 2FA): returns a a bearer token immediately.
- 2FA enrolled: returns a challenge response instead; the client must then re-submit with a
totp_code(or recovery code) to complete login and receive the token.
Google SSO is an alternate path to the same result — see Integration — Google SSO: the client ends up calling POST /auth/google/exchange to trade a short-lived exchange code for the same kind of bearer token (also TOTP-gated if enrolled).
Using the token
Every authenticated request must send:
Authorization: Bearer <token>
and, for tenant-scoped endpoints:
X-Tenant: <tenant-uuid>
The X-Tenant value is only trusted if it matches one of the caller's own active memberships — a client cannot access another tenant's data by simply changing this header.
Token expiration
Default token lifetime is 7 days, after which the token stops working even without explicit revocation.
Token refresh
There is no refresh-token endpoint — the client re-authenticates (login or SSO) to obtain a new token when the old one expires or is revoked.
Logout / session management
POST /auth/logout— revokes the current token.GET /profile/sessions— lists the caller's active tokens.DELETE /profile/sessions/{tokenId}— revokes a specific token (e.g. remote sign-out of another device).
MFA (TOTP)
POST /auth/2fa/enroll→POST /auth/2fa/confirm— two-step enrollment; a newly generated secret is staged as "pending" and only activated onconfirm, so a bareenrollcall can never silently disable existing 2FA.POST /auth/2fa/disable— requires re-verification (current password, or an emailed OTP for SSO-only accounts with no password).POST /auth/2fa/recovery-codes/regenerate— invalidates old recovery codes and issues new ones.- Independent of login-time MFA, some sensitive actions (approval, posting, reversal, bank reconciliation completion) can require a fresh TOTP code again at the point of action, per tenant policy flags (
require_totp_for_review/_approval/_posting, all defaulttrue). See Authentication & Authorization.
Permission enforcement errors
- No
X-Tenantheader, or it doesn't match an active membership →409. - Valid tenant but missing the route's required
permission:<key>→403with the required permission key in the response body. - No active subscription (tenant billing lapsed) →
402on write requests (reads and*/billing/*remain available). - Plan limit reached →
402 plan_limit_reached(user seats) or402 usage_limit_reached(monthly transaction cap, returned by the approve/post step). - Tenant's country doesn't match a
country:<ISO2>gate →404(not a 403 — the route is presented as not existing for that tenant).
See API Error Reference for the full status-code catalogue.
Rate limiting on auth endpoints
Login 20/min, register 10/min, password-forgot 6/min, password-reset 10/min, email-verification-related 6/min, Google OAuth redirect/callback 20/min, 2FA enroll/confirm/disable/recovery-regenerate 10/min each. TOTP verification is additionally capped at 5 attempts/minute independent of the route throttle (tracked in totp_attempts). See Security.