Skip to main content
This page covers the public tenant auth surface (/auth/*) used by workspace users, tenant admins, and direct API integrations.

The token model

A successful login returns a TokenPair:
accessToken
string
required
A short-lived JWT (JWT_ACCESS_TTL, default 15 minutes). Send it as Authorization: Bearer <accessToken> on every authenticated request. It embeds tenantId, roleCode, and subjectScope as claims — there is no separate tenant header to set.
refreshToken
string
required
A longer-lived JWT (JWT_REFRESH_TTL, default 30 days) used to obtain a new access token. Browser clients get this automatically as an httpOnly cookie scoped to /api/v1/auth; if you’re integrating server-to-server, read it from the response body instead and store it yourself.
expiresIn
string
required
Access token lifetime, e.g. "15m".
Endpoints marked Public below don’t require a bearer token (they’re how you get one, or they predate having one). Everything else requires Authorization: Bearer <accessToken>.

Register a tenant

tenantName
string
required
2–80 characters.
tenantSlug
string
required
Lowercase, ^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$. This becomes your login slug.
adminName
string
required
2–80 characters.
nameProfile
object
Optional structured name (givenName, familyName, nameOrder, phonetic fields for CJK names, preferredDisplayName, nameLocaleRegion) — see the request example.
email
string
required
Lowercased automatically.
password
string
required
8+ characters.
subjectName
string
Initial subject company name.
planCode
string
Defaults to free. Public registration only ever accepts free.
turnstileToken
string
Required when Turnstile is enabled.
curl -X POST https://api.alforse.com/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "tenantName": "Acme Leasing",
    "tenantSlug": "acme-leasing",
    "adminName": "Jordan Lee",
    "email": "[email protected]",
    "password": "correct-horse-battery",
    "turnstileToken": "..."
  }'
Returns an AuthLoginResult (see me below for the user shape). Disabled in production (PUBLIC_REGISTRATION_ENABLED=false) — use Redeem a code instead. Public.

Redeem a code

Same response shape as register, plus a code field instead of planCode — the plan comes from the code itself.
code
string
required
8–80 characters.
tenantName
string
required
tenantSlug
string
required
adminName
string
required
nameProfile
object
email
string
required
password
string
required
subjectName
string
turnstileToken
string
POST /auth/redeem · Public

Log in

tenantSlug
string
required
email
string
required
password
string
required
mfaCode
string
6-digit TOTP code, if MFA is enrolled.
emailCode
string
6-digit backup code from /auth/login/mfa/email, used instead of mfaCode when the authenticator is unavailable.
mfaSetupSecret
string
Used during first-time MFA enrollment.
turnstileToken
string
curl -X POST https://api.alforse.com/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"tenantSlug":"acme-leasing","email":"[email protected]","password":"correct-horse-battery"}'
Two possible response shapes:
(success)
AuthLoginResult
{ accessToken, refreshToken, expiresIn, tenant: { id, slug, name }, user } — see session for user.
(MFA required)
MfaLoginRequired
{ mfaRequired: true, enrollmentRequired, message, secret?, otpauthUrl?, emailFallbackAvailable? }. If enrollmentRequired is true, this is the account’s first login and it must enroll TOTP before proceeding — see MFA.
POST /auth/login · Public Related:
  • POST /auth/login/resolve — given just an email, a convenience lookup (no password) used by UIs to figure out which tenant(s) an email belongs to before showing the login form.
  • POST /auth/login/mfa/email — re-verifies tenantSlug + email + password, then emails a 6-digit backup code to use as emailCode on /auth/login.
Both are Public.

Refresh and logout

refreshToken
string
Optional — falls back to the alforse_tenant_refresh httpOnly cookie if omitted.
POST /auth/refresh (Public) returns a new TokenPair. POST /auth/logout (requires bearer token) revokes the refresh token and clears the cookie.

Session

user
AuthUserSummary
{ id, email, name, nameProfile, status, roleCode, subjectScope }
tenant
object
{ id, slug, name, edition }
permissions
object
Module → permission level map.
featureFlags
object
Resolved plan feature flags.
subjects
array
Subject companies visible to this user.
settings
object
{ timezone, reminderLeadDays, reminderHour, reminderEscalationDays, ownerScopeEnabled, preferences }
GET /auth/me is the single call to hydrate a client session after login.
  • PATCH /auth/preferences — language, locale, timezone, currency, calendar/measurement system, first day of week, date/number format. All fields optional.
  • PATCH /auth/profilename and/or a structured nameProfile.

MFA

EndpointBodyNotes
GET /auth/mfa/statusWhether TOTP is enrolled.
POST /auth/mfa/enroll/startReturns a new TOTP secret + otpauth URL to scan.
POST /auth/mfa/enroll/confirm{ secret, code }Confirms enrollment with a 6-digit code.
POST /auth/mfa/disable{ code }Requires a current valid 6-digit code.
Tenant admins cannot disable the MFA_REQUIRED_FOR_ADMINS policy for themselves in production — see Configuration.

Passkeys (WebAuthn)

EndpointBodyNotes
GET /auth/passkeysList your registered passkeys.
POST /auth/passkeys/register/optionsGet WebAuthn registration options.
POST /auth/passkeys/register/verify{ response, label? }response is the browser’s RegistrationResponseJSON.
DELETE /auth/passkeys/:idRemove a passkey.
POST /auth/passkeys/login/options{ email, tenantSlug, turnstileToken? }Public. Start passkey login.
POST /auth/passkeys/login/verify{ flowId, response }Public. response is the browser’s AuthenticationResponseJSON. Returns a TokenPair like login.

SSO

EndpointNotes
GET /auth/sso/providersPublic. Lists which of google / linkedin / dingtalk / lark / slack are configured.
GET /auth/sso/:provider/start?tenantSlug=&next=&client=Public. Redirects into the provider’s OAuth flow.
GET /auth/sso/:provider/callbackPublic. Provider redirects back here; Alforse redirects onward with a short-lived exchange code.
POST /auth/sso/exchangePublic. { code } → a TokenPair (code must be 16+ characters, single use, ~2 minute TTL).

Invitations and password reset

EndpointBodyNotes
POST /auth/invitations/accept{ token, tenantSlug?, name, nameProfile?, password, turnstileToken? }Public. Accepts a member invitation and sets the account’s password.
POST /auth/password-reset/request{ tenantSlug, email, turnstileToken? }Public. Always returns { ok: true, deliveryStatus } — including when the tenant or email doesn’t exist (deliveryStatus: "not_sent") — to avoid account enumeration.
POST /auth/password-reset/confirm{ token, tenantSlug, password, turnstileToken? }Public. token is 16+ characters, single use, expires 2 hours after it was requested.