Auth
Every path below is scoped to one auth collection:
/collections/{idOrName}/<action>. Superuser login is the same
auth-with-password endpoint with idOrName=_superusers — there is no
separate admin-login path. See Concepts → Authentication
for the token model these endpoints share.
| Method & path | Auth | Summary |
|---|---|---|
POST .../auth-with-password |
none | Log in with identity + password |
POST .../auth-refresh |
bearer | Refresh a session token |
GET .../auth-methods |
none | Which auth methods are enabled |
POST .../request-verification |
none | Email a verification link |
POST .../confirm-verification |
none | Consume it |
POST .../request-password-reset |
none | Email a reset link |
POST .../confirm-password-reset |
none | Consume it, set a new password |
POST .../request-email-change |
bearer | Email a confirmation to a pending new address |
POST .../confirm-email-change |
none | Consume it (acting record comes from the token) |
POST .../request-otp |
none | Email a one-time login code |
POST .../auth-with-otp |
none | Complete OTP login |
POST .../request-magic-link |
none | Email a single-use sign-in link |
POST .../auth-with-magic-link |
none | Complete magic-link login |
POST .../impersonate/{id} |
bearer (superuser) | Mint a non-refreshable token for another record |
POST .../auth-signout |
bearer | Revoke the session backing the current token |
GET .../sessions |
bearer | List the caller’s own sessions (or another record’s, as superuser) |
DELETE .../sessions/{id} |
bearer | Revoke one session by id |
POST .../sessions/revoke-others |
bearer | Revoke every session but the current one |
POST .../sessions/revoke-all |
bearer | Revoke every session, including the current one |
POST .../stop-impersonating |
cookie | Restore the impersonator’s session (cookie mode only) |
POST .../ban/{id} |
bearer (superuser) | Ban a record, revoking its sessions immediately |
DELETE .../ban/{id} |
bearer (superuser) | Lift a ban |
GET .../oauth2/{provider}/start |
none | Begin the server-driven OAuth2 redirect flow |
GET .../oauth2/{provider}/callback |
none, GET/POST |
Complete it, landing the session in a cookie |
POST .../totp/setup |
bearer | Start (or restart) TOTP setup, returns the otpauth:// URI + secret |
POST .../totp/confirm |
bearer | Confirm setup, returns 10 backup codes shown once |
POST .../totp/disable |
bearer | Disable TOTP with a code or the account password |
POST .../totp/backup-codes/regenerate |
bearer | Invalidate existing backup codes, issue 10 new ones |
POST .../auth-with-totp |
none | Complete a pending MFA challenge with a TOTP/backup code |
GET .../records/{id}/external-auths |
bearer (owner/superuser) | List a record’s linked OAuth2 providers |
DELETE .../records/{id}/external-auths/{provider} |
bearer (owner/superuser) | Unlink one |
auth-with-password
Section titled “auth-with-password”{ "identity": "alice@example.com", "password": "hunter2", "identityField": "email", "mfaId": "..." }identity is matched against passwordAuth.identityFields (email by
default); identityField pins the lookup to one field instead of trying
each in order. mfaId completes a pending MFA
challenge from a previous call. Success: 200 { "token", "record" }.
A pending MFA challenge is 401 { "mfaId": "..." } — with no
status/message/data envelope, distinct from every other error
shape on this page.
request-* endpoints always answer 204
Section titled “request-* endpoints always answer 204”request-verification, request-password-reset, request-otp, and
request-magic-link all answer 204 (or, for OTP, 200 { "otpId" };
for magic-link, 200 {}) regardless of whether the address exists —
this is deliberate account-enumeration resistance, not a bug to work
around. See Email flows,
OTP, and
Magic link.
request-magic-link / auth-with-magic-link
Section titled “request-magic-link / auth-with-magic-link”Requires authOptions.magicLink.enabled (default false) — disabled
answers 403. POST .../request-magic-link body:
{ "email": "...", "redirectUrl": "..." } (redirectUrl optional,
honoured only when same-origin as settings.meta.appURL). POST .../auth-with-magic-link body: { "token": "...", "mfaId": "..." } →
the normal 200 { "token", "record" }, or 400 for a missing, reused,
or expired token. See Magic link
and Email → Recipes for the full flow.
confirm-email-change
Section titled “confirm-email-change”{ "token": "...", "password": "current-password" }Note this one takes the record’s current password as a second
factor, not the new email — the acting record comes entirely from the
token’s claims, not from an Authorization header, which is why this
endpoint is unauthenticated at the transport level while still being
scoped to one specific record.
impersonate/{id}
Section titled “impersonate/{id}”Superuser-only. Body: { "duration": 3600 } (optional; defaults to the
collection’s own authToken.duration). Returns a normal
{ "token", "record" } pair, but the token is flagged non-refreshable —
calling auth-refresh on it returns the same token unchanged rather
than failing. See Impersonation.
auth-signout
Section titled “auth-signout”Bearer-authenticated. Revokes the _sessions row backing the current
token and, in cookie mode, clears the session cookie. Returns 204
regardless of prior state.
sessions
Section titled “sessions”{ "items": [{ "id": "...", "kind": "password", "fingerprint": "...", "ip": "...", "userAgent": "...", "current": true, "created": "...", "lastSeenAt": "...", "expiresAt": "..." }] }GET .../sessions lists the caller’s own sessions; pass ?recordId= to
look at another record’s sessions, which requires superuser. kind is
one of password, otp, oauth2, impersonation, or refresh.
Requires SESSION_TRACKING=true (the default) — with it off, this
always returns an empty list and revocation is a no-op.
DELETE .../sessions/{id} revokes one session by id: 204 whether or
not the id existed, and requires either being the session’s own owner
or a superuser (403 otherwise). POST .../sessions/revoke-others
revokes every session but the current one and returns
200 { "revoked": <count> }. POST .../sessions/revoke-all revokes
every session, including the current one — 204, clearing the session
cookie in cookie mode.
stop-impersonating
Section titled “stop-impersonating”Cookie mode only. Reads the cb_session_prev cookie stashed by
impersonate/{id}, re-verifies it still signs correctly, and restores
it as the active session cookie, clearing cb_session_prev. Returns
200 { "record" }. 400 if there’s no impersonation session to stop
(missing, unverifiable, or already-consumed cookie). Bearer-mode
impersonation restoration has no server-side counterpart — the client
just discards the impersonated token and keeps using the original.
ban/{id} / unban
Section titled “ban/{id} / unban”Superuser-only. POST .../ban/{id} body: { "reason": "...", "expiresAt": "..." }
(both optional). Upserts a _bans row, rotates the target’s signing
key, and revokes every one of its live sessions — a ban locks out live
sessions immediately, not just future logins. Returns
200 { "recordRef", "reason", "expiresAt" }. DELETE .../ban/{id}
removes the ban, if any, and returns 204. A banned record’s next
auth-with-password/auth-with-otp/auth-with-oauth2/auth-refresh
call fails with 403 "This account is banned.".
oauth2/{provider}/start / oauth2/{provider}/callback
Section titled “oauth2/{provider}/start / oauth2/{provider}/callback”Cookie mode only — the server drives the whole provider round trip.
GET .../oauth2/{provider}/start?redirect=&createData= requires
redirect to be same-origin with settings.meta.appURL or an exact
member of CORS_ALLOW_ORIGINS (a "*" entry never qualifies); an
untrusted redirect answers 400 JSON — the one failure this flow
answers directly instead of by redirecting, since redirecting to an
untrusted target is exactly the open redirect this guards against.
Every later failure instead redirects to the now-trusted redirect
with ?cb_error=<code>: cookie_sessions_disabled,
app_url_not_configured, oauth2_disabled, or provider_not_enabled.
GET .../oauth2/{provider}/callback is the provider’s own redirect
target — never called directly — and on success redirects to
redirect with the session already set in an httpOnly cookie. A
pending MFA challenge redirects with ?cb_mfa=<mfaId> instead of
setting the cookie. The exact URL to register with the provider is
{appURL}/api/collections/{collection}/oauth2/{provider}/callback.
The callback also accepts POST (Apple’s response_mode=form_post
sends code/state/user as form fields instead of query params —
see Sign in with Apple); every
other provider only ever uses GET.
totp/setup / totp/confirm / totp/disable / totp/backup-codes/regenerate
Section titled “totp/setup / totp/confirm / totp/disable / totp/backup-codes/regenerate”Bearer-authenticated, act on the caller’s own record — there’s no {id}
in any of these paths. POST .../totp/setup (no body) → 200 { "secret", "uri" }; calling it again before confirming replaces the pending
secret, and calling it once already confirmed answers 400. POST .../totp/confirm body { "code": "123456" } → 200 { "backupCodes": [...10 strings] }, shown once and never retrievable again. POST .../totp/disable body { "code": "..." } or { "password": "..." } →
204. POST .../totp/backup-codes/regenerate (no body) → 200 { "backupCodes": [...10 new strings] }, invalidating every previous one.
See TOTP 2FA.
auth-with-totp
Section titled “auth-with-totp”{ "mfaId": "...", "code": "123456" }Unauthenticated at the transport level — the pending _mfas session
mfaId names already identifies the record. code is either a live
TOTP code or one of the record’s backup codes (consumed on use, and
never accepted twice). Success: the normal 200 { "token", "record" };
a wrong/reused code is 400.
records/{id}/external-auths
Section titled “records/{id}/external-auths”[{ "id": "...", "created": "...", "updated": "...", "recordId": "...", "collectionId": "...", "provider": "google", "providerId": "..." }]GET lists every provider linked to id — a bare array, not the usual
{ "items": [...] } list envelope. Both this and DELETE .../external-auths/{provider} require being id’s own owner or a
superuser. The DELETE additionally refuses (400) when unlinking
that provider would leave the record with no password, no other linked
provider, and both OTP and magic-link login disabled — see Linked
accounts.