Authentication API
All endpoints below operate on a collection of type: "auth". Calls against
non-auth collections return 422.
For the conceptual overview see Authentication.
Admin auth
Section titled “Admin auth”POST /api/admin/setup{ "email": "...", "password": "..." }One-time setup — creates the first admin. After that, returns 400.
POST /api/admin/auth/login{ "email": "...", "password": "..." }→ { "data": { "token": "<jwt>", "admin": { "id": "...", "email": "..." } } }
GET /api/admin/auth/me→ { "data": <jwt payload> }User register / login
Section titled “User register / login”POST /api/auth/<col>/register{ "email": "...", "password": "...", ...extra fields go to `data` blob }
POST /api/auth/<col>/login{ "email": "...", "password": "..." }/login returns one of:
{ "data": { "token": "<jwt>", "record": { "id": "...", "email": "..." } } }
{ "data": { "mfa_required": true, "mfa_token": "..." } }POST /api/auth/<col>/login/mfa{ "mfa_token": "...", "code": "<6 digits>" }
# OR — single-use recovery code instead of TOTP codePOST /api/auth/<col>/login/mfa{ "mfa_token": "...", "recovery_code": "a1b2c3d4" }GET /api/auth/me ← user JWTPOST /api/auth/refresh ← user OR admin JWT, re-issues 7d tokenEmail verification
Section titled “Email verification”POST /api/auth/<col>/request-verify ← auth required, no bodyPOST /api/auth/<col>/verify-email { "token": "..." }Verification emails are sent via the verify template (Settings → Email
templates). The token is the long random hex from the email link.
Password reset
Section titled “Password reset”POST /api/auth/<col>/request-password-reset { "email": "..." } → always 200 (no enumeration), even if email isn't registeredPOST /api/auth/<col>/confirm-password-reset { "token": "...", "password": "..." }password must be ≥ 8 characters. reset template, 1-hour token TTL.
OTP / magic link
Section titled “OTP / magic link”Gated by Settings → Auth features → OTP / magic link.
POST /api/auth/<col>/otp/request { "email": "..." } ← always 200POST /api/auth/<col>/otp/auth { "token": "..." }POST /api/auth/<col>/otp/auth { "email": "...", "code": "<6 digits>" }Either token (from the link) or code (from the email) authenticates.
10-minute expiry. SMTP must be configured.
Gated by Settings → Auth features → MFA / TOTP.
POST /api/auth/<col>/totp/setup ← user JWT → { "data": { "secret": "<base32>", "otpauth_url": "otpauth://..." } }
POST /api/auth/<col>/totp/confirm { "code": "<6 digits>" } → enables MFA on this user
POST /api/auth/<col>/totp/disable { "code": "<6 digits>" } → clears MFA on this userdisable is intentionally not gated by the feature flag — once a user has
MFA enabled, disabling it shouldn’t break when an admin turns the feature off
globally. Same for /login/mfa. /disable also wipes any stored recovery
codes for the user.
Recovery codes
Section titled “Recovery codes”POST /api/auth/<col>/totp/recovery/regenerate ← user JWT → { "data": { "codes": ["a1b2c3d4", "e5f6g7h8", ...] } } // 10 codes, plaintext, ONCE
GET /api/auth/<col>/totp/recovery/status ← user JWT → { "data": { "total": 10, "remaining": 7 } }10 single-use 8-character codes per user, bcrypt-hashed at rest. Plaintext is returned once at generation time — there’s no way to retrieve them again. Regenerating invalidates the previous batch.
OAuth2
Section titled “OAuth2”GET /api/auth/<col>/oauth2/providers → { "data": [ { "name": "google", "displayName": "Google" }, ... ] }
GET /api/auth/<col>/oauth2/authorize ?provider=google&redirectUri=https://app.example.com/callback&state=<csrf> → { "data": { "authorize_url": "https://accounts.google.com/o/oauth2/v2/auth?..." } }
POST /api/auth/<col>/oauth2/exchange{ "provider": "google", "code": "...", "redirectUri": "..." } → { "data": { "token": "<jwt>", "record": { "id": "...", "email": "..." } } }Configure providers at Settings → OAuth2 (13 supported out of the box).
Anonymous
Section titled “Anonymous”POST /api/auth/<col>/anonymous → { "data": { "token": "<jwt>", "record": { "id": "...", "email": "anon_xxx@anonymous.invalid", "anonymous": true } } }30-day JWT carrying anonymous: true claim.
Promote an anonymous account
Section titled “Promote an anonymous account”POST /api/auth/<col>/promote ← anonymous user JWT{ "email": "alice@x.com", "password": "secret123" } → { "data": { "token": "<jwt>", "record": { ... "anonymous": false } } }Upgrades an existing anonymous record in place — keeps the user id, sets
the email + password, clears is_anonymous, and re-issues a fresh
non-anonymous JWT. Returns 403 if the caller’s token isn’t anonymous,
409 if the email is already taken in this collection.
Admin impersonation
Section titled “Admin impersonation”POST /api/admin/impersonate/<col>/<userId> ← admin auth required → { "data": { "token": "<jwt>", "record": { ... }, "impersonated_by": "<admin_id>" } }1-hour user JWT carrying impersonated_by: <admin_id> for audit.
Admin user management
Section titled “Admin user management”GET /api/admin/users/<col>?page=1&perPage=30PATCH /api/admin/users/<col>/<id> { email?, verified?, mfa_enabled?: false, data? }DELETE /api/admin/users/<col>/<id>mfa_enabled: true is rejected — admins can only disable MFA (account
recovery); enabling requires the user’s own enrollment via /totp/setup +
/totp/confirm.
Token shape
Section titled “Token shape”User JWT (audience "user"):
{ "iat": 1730000000, "exp": 1730604800, "aud": "user", "id": "<user_id>", "email": "...", "collection": "<col_name>", "anonymous": true, // optional "impersonated_by": "<admin_id>" // optional}Admin JWT (audience "admin"):
{ "iat": 1730000000, "exp": 1730604800, "aud": "admin", "id": "<admin_id>", "email": "..."}