API reference
43 operations in auth. Raw spec: openapi.json.
/api/v1/auth/edge/sourcesGET /api/auth/edge/sources — admin listing of edge-assertion sources.
Responses
| Status | Description |
|---|---|
200 | |
403 |
Security: bearer
/api/v1/auth/edge/sources/{name}PUT /api/auth/edge/sources/{name} — create or replace an edge source. Pins are stored verbatim and enforced byte-for-byte at verification time; every write is audited.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes |
Request body
UpsertEdgeSourceRequest
Responses
| Status | Description |
|---|---|
200 | |
403 | |
409 | this name is already claimed by another workspace |
Security: bearer
/api/v1/auth/edge/sources/{name}DELETE /api/auth/edge/sources/{name}.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes |
Responses
| Status | Description |
|---|---|
204 | |
403 | |
404 |
Security: bearer
/api/v1/auth/edge/sources/{name}/linksGET /api/auth/edge/sources/{name}/links — admin link listing.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes |
Responses
| Status | Description |
|---|---|
200 | |
403 | |
404 |
Security: bearer
/api/v1/auth/edge/sources/{name}/linksPOST /api/auth/edge/sources/{name}/links — create an explicit link. Audited as `auth.federated_account_linked`.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes |
Request body
CreateEdgeLinkRequest
Responses
| Status | Description |
|---|---|
200 | |
403 | |
404 |
Security: bearer
/api/v1/auth/edge/sources/{name}/links/{link_id}DELETE /api/auth/edge/sources/{name}/links/{link_id}. Audited as `auth.federated_account_unlinked`.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes | |
link_id | path | string | Yes |
Responses
| Status | Description |
|---|---|
204 | |
403 | |
404 |
Security: bearer
/api/v1/auth/edge/{name}/exchangePOST /api/auth/edge/{name}/exchange — swaps a valid `Cf-Access-Jwt-Assertion` (header) for Marula tokens. The assertion MUST verify against the source's pinned team JWKS with issuer AND audience pinned; failures are a uniform typed 401.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes |
Responses
| Status | Description |
|---|---|
200 | |
401 |
/api/v1/auth/email-verification/confirmPOST /api/auth/email-verification/confirm — marks the email verified (single-use, audited).
Request body
TokenRequest
Responses
| Status | Description |
|---|---|
200 | |
401 | invalid or expired token |
429 |
/api/v1/auth/email-verification/requestPOST /api/auth/email-verification/request — resend (public, THROTTLED; admin override rides POST /api/org/users/{id}/email-verified).
Request body
EmailVerifyRequest
Responses
| Status | Description |
|---|---|
200 | |
429 |
/api/v1/auth/loginPOST /api/auth/login — resolves the org by email (SECURITY DEFINER bridge), verifies the argon2 hash under RLS scope. When any second factor is enrolled, returns a short-lived `mfa_pending` bridge instead of tokens; POST /api/auth/mfa/verify completes it.
Answers 503 with `Retry-After` when the process's password-hash slots are saturated (Wave 56 lane `p56-auth-bounds`) — backpressure under a burst, never a 500. Left out of the `utoipa` response list for the reason given on [`register`].
Request body
LoginRequest
Responses
| Status | Description |
|---|---|
200 | |
401 | bad credentials |
/api/v1/auth/logoutPOST /api/auth/logout — revokes the refresh session and audits the event.
Request body
RefreshRequest
Responses
| Status | Description |
|---|---|
200 |
/api/v1/auth/magic-link/confirmPOST /api/auth/magic-link/confirm — consumes the link and issues a full session through the SAME machinery as password login. STAFF family only (the portal is NOT included — see the threat model). An account with an enrolled second factor answers a typed `mfa_required` refusal — the link proves the mailbox, not the factor.
Request body
MagicLinkConfirm
Responses
| Status | Description |
|---|---|
200 | |
401 | invalid or expired token |
429 |
/api/v1/auth/magic-link/requestPOST /api/auth/magic-link/request — deny-by-default per org (`orgs.magic_link_sign_in`, 0088): unknown emails AND disabled orgs collapse into the SAME uniform answer.
Request body
EmailVerifyRequest
Responses
| Status | Description |
|---|---|
200 | |
429 |
/api/v1/auth/mfa/recovery-codes/regeneratePOST /api/auth/mfa/recovery-codes/regenerate — invalidates all previous codes, issues a new batch (revealed once).
Request body
RegenerateRecoveryRequest
Responses
| Status | Description |
|---|---|
200 | |
401 |
Security: bearer
/api/v1/auth/mfa/step-upPOST /api/auth/mfa/step-up — proves password plus a live factor and records it against this sign-in lineage.
This is the door the destructive factor changes point at. It is deliberately separate from `mfa/verify`: that one completes a login, this one raises what an already-signed-in session may do, and the record it leaves expires. A passkey user does not call this — their step-up is the passkey login, which stamps the same record on the session it creates.
Request body
MfaStepUpRequest
Responses
| Status | Description |
|---|---|
200 | |
401 | |
428 |
Security: bearer
/api/v1/auth/mfa/totp/disablePOST /api/auth/mfa/totp/disable — requires password AND a live factor; wipes TOTP enrollment and all recovery codes.
This is the explicit "turn MFA off" action the last-factor rule points at. A successful call also stamps a step-up, because the caller just proved a factor on this session; the next change within the window does not re-prompt.
Request body
TotpDisableRequest
Responses
| Status | Description |
|---|---|
204 | |
401 |
Security: bearer
/api/v1/auth/mfa/totp/enroll-beginPOST /api/auth/mfa/totp/enroll-begin — generates a secret (returned ONCE) and stores it sealed+unconfirmed.
A FIRST enrolment (no confirmed factor) simply restarts the pending window. Replacing a CONFIRMED factor is a destructive act and needs a fresh step-up proved by a method OTHER than TOTP: a live code cannot authorize replacing the secret it belongs to, so the caller uses a recovery code or a passkey. The account keeps its recovery codes through the swap, which is what stops it holding zero working factors while the new secret is pending. After an admin reset (`users.mfa_reenroll_required`) enrolment is the sanctioned way out and the audited reset is its authorization: there is no factor left to step up with, which is exactly why the reset travels in this lane.
Responses
| Status | Description |
|---|---|
200 |
Security: bearer
/api/v1/auth/mfa/totp/enroll-verifyPOST /api/auth/mfa/totp/enroll-verify — confirms the enrollment with the first valid code and issues the one-time recovery-code batch.
Request body
TotpEnrollVerifyRequest
Responses
| Status | Description |
|---|---|
200 | |
401 |
Security: bearer
/api/v1/auth/mfa/verifyPOST /api/auth/mfa/verify — completes an MFA-gated login with a TOTP or recovery code. Tries TOTP first (advancing the replay watermark), then falls back to single-use recovery codes.
Request body
MfaVerifyRequest
Responses
| Status | Description |
|---|---|
200 | |
401 |
/api/v1/auth/mfa/webauthn/beginPOST /api/auth/mfa/webauthn/begin — assertion request for the pending MFA login. Requires a valid mfa_pending token.
Request body
MfaTokenRequest
Responses
| Status | Description |
|---|---|
200 | |
401 |
/api/v1/auth/mfa/webauthn/credentialsGET /api/auth/mfa/webauthn/credentials — list registered passkeys.
Responses
| Status | Description |
|---|---|
200 |
Security: bearer
/api/v1/auth/mfa/webauthn/credentials/{id}/removePOST /api/auth/mfa/webauthn/credentials/{id}/remove — removes one passkey.
The password is not the authorization. A phished password must not be able to downgrade a passkey-only account to password-only sign-in (DECISIONS record 2), so removal needs a FRESH step-up on this sign-in lineage, proved by a factor that is not the credential being removed. The last factor of any kind cannot leave this way: that is the explicit "turn MFA off" action (`POST /api/auth/mfa/totp/disable`), and the refusal says so.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes |
Request body
WebauthnRemoveRequest
Responses
| Status | Description |
|---|---|
204 |
Security: bearer
/api/v1/auth/mfa/webauthn/finishPOST /api/auth/mfa/webauthn/finish — verifies the assertion and issues full tokens. Ceremony state is single-use; replay of a finish call fails.
Request body
WebauthnLoginFinishRequest
Responses
| Status | Description |
|---|---|
200 | |
401 |
/api/v1/auth/mfa/webauthn/register/beginPOST /api/auth/mfa/webauthn/register/begin — starts a passkey registration for the authenticated user.
Responses
| Status | Description |
|---|---|
200 |
Security: bearer
/api/v1/auth/mfa/webauthn/register/finishPOST /api/auth/mfa/webauthn/register/finish — verifies the attestation and stores the passkey. The public key is stored; no secret exists.
Request body
WebauthnRegisterFinishRequest
Responses
| Status | Description |
|---|---|
204 |
Security: bearer
/api/v1/auth/oidc/presetsGET /api/auth/oidc/presets — the declarative preset catalog that drives the admin "add provider" UI. Pure data; no configuration.
Responses
| Status | Description |
|---|---|
200 |
Security: bearer
/api/v1/auth/oidc/providersGET /api/auth/oidc/providers — admin listing. Secrets excluded by type.
Responses
| Status | Description |
|---|---|
200 | |
403 |
Security: bearer
/api/v1/auth/oidc/providers/publicGET /api/auth/oidc/providers/public — drives config-driven SSO buttons on the login screen. No configuration detail beyond names.
Responses
| Status | Description |
|---|---|
200 |
/api/v1/auth/oidc/providers/{name}PUT /api/auth/oidc/providers/{name} — create or replace a provider. The client secret is required on every write (rotate-friendly).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes |
Request body
UpsertProviderRequest
Responses
| Status | Description |
|---|---|
200 | |
403 | |
409 | this name is already claimed by another workspace |
Security: bearer
/api/v1/auth/oidc/providers/{name}DELETE /api/auth/oidc/providers/{name}.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes |
Responses
| Status | Description |
|---|---|
204 | |
403 | |
404 |
Security: bearer
/api/v1/auth/oidc/{name}/callbackGET /api/auth/oidc/{name}/callback — validates state signature/expiry/ provider binding and nonce echo, runs the conservative decision, then hands tokens (or link confirmation) to the web app via a single-use code.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes |
Responses
| Status | Description |
|---|---|
303 | redirect to web app |
/api/v1/auth/oidc/{name}/linkPOST /api/auth/oidc/{name}/link — begins the EXPLICIT account-link flow for the current user. Returns the authorize URL to navigate to.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes |
Responses
| Status | Description |
|---|---|
200 | |
404 |
Security: bearer
/api/v1/auth/oidc/{name}/loginGET /api/auth/oidc/{name}/login — 303 to the IdP with signed state, nonce, and PKCE S256 bound together.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes |
Responses
| Status | Description |
|---|---|
303 | redirect to provider |
404 |
/api/v1/auth/password-reset/confirmPOST /api/auth/password-reset/confirm — consumes the token, sets the new password, and revokes ALL of the user's live sessions (the reset's security payload: a stolen session cannot outlive it).
Request body
PasswordResetConfirm
Responses
| Status | Description |
|---|---|
200 | |
401 | invalid or expired token |
422 | validation error |
429 |
/api/v1/auth/password-reset/requestPOST /api/auth/password-reset/request — issues a single-use token + mail when the account exists; ALWAYS the uniform answer.
Request body
PasswordResetRequest
Responses
| Status | Description |
|---|---|
200 | |
429 | flood throttle |
/api/v1/auth/refreshPOST /api/auth/refresh — rotates the refresh secret as a NEW chain node. Presenting an already-consumed (rotated/revoked/expired) token proves replay: the WHOLE chain is revoked and the event is audited.
Request body
RefreshRequest
Responses
| Status | Description |
|---|---|
200 |
/api/v1/auth/registerPOST /api/auth/register — creates the org, its default roles/board/SLA policy, and the first user as Owner. One transaction: a half-created org can never exist.
Answers 503 with `Retry-After` when the process's password-hash slots are saturated (Wave 56 lane `p56-auth-bounds`). The 503 is deliberately NOT in the `utoipa` response list: every route can answer it from the middleware layer (draining, database unreachable), the pinned OpenAPI document is regenerated only by `scripts/regen-openapi-pins.sh` against a live API, and adding one description here would leave both committed pins stale for a status the whole surface already shares.
Request body
RegisterRequest
Responses
| Status | Description |
|---|---|
200 | |
409 | slug or email already exists |
422 | validation error |
/api/v1/auth/sessionsGET /api/auth/sessions — the caller's live refresh sessions (security settings page).
Responses
| Status | Description |
|---|---|
200 |
Security: bearer
/api/v1/auth/sessions/{id}/revokePOST /api/auth/sessions/{id}/revoke — kills one of the caller's own sessions (sign-out-everywhere-else primitive).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes |
Responses
| Status | Description |
|---|---|
204 |
Security: bearer
/api/v1/auth/users/{id}/mfa-resetPOST /api/auth/users/{id}/mfa-reset — clears another user's factors.
This exists because the strict rule needs an exit: a user who lost their only factor cannot step up, and without a reset path the step-up requirement locks them out of their own account changes (the ruling of 2026-10-25 puts the two in one lane for exactly that reason). What makes it safe rather than a takeover tool: - `manage_users` is required, and an administrator cannot reset their own factors — a stolen admin session must not be able to turn its owner's MFA off with one call. - An Owner's or Admin's factors can only be reset by an Owner, so a lesser administrator cannot strip a peer's second factor. - The account is left owing a factor (`users.mfa_reenroll_required`), not merely factor-free: enrolment is the only factor change it may make until one is confirmed, and the flag is audited with the counts. - Every live session is revoked, so the user re-authenticates with their password and learns the factor is gone. - The audit row names the administrator, the target, what was removed and how many sessions died.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes |
Responses
| Status | Description |
|---|---|
200 | |
403 | |
404 |
Security: bearer
/api/v1/meGET /api/me — the authenticated principal with resolved permissions.
Responses
| Status | Description |
|---|---|
200 |
Security: bearer
/api/v1/sso/exchangePOST /api/auth/sso/exchange — swaps the single-use callback code for tokens (or a link confirmation). Codes die on first use or after 60s.
Request body
SsoExchangeRequest
Responses
| Status | Description |
|---|---|
200 | |
401 |
/api/v1/users/meWave 26 onboarding leg (b): the per-user first-run "check your profile" step needs a self-editable name/timezone. Self-scoped: PATCH /api/v1/users/me changes ONLY the caller's own display name and timezone (email is immutable here by design), with an audit row in the same transaction.
Request body
PatchMeRequest
Responses
| Status | Description |
|---|---|
200 | |
422 |
Security: bearer