HTTP API reference¶
Unless noted, paths are relative to your app root. Defaults: auth_path="/auth", users_path="/users".
Placeholders:
{auth}— value ofauth_pathwithout trailing slash ambiguity (routes are registered underauth_path).{users}—users_path.{provider}— OAuth provider name fromOAuthConfig.oauth_providersor a manual controller factory.
Generated OpenAPI publishes the built-in request/response payload names from litestar_auth.payloads. login_identifier only changes how LoginCredentials.identifier is resolved during login; it does not rename the email/token fields used by the built-in registration, verification, reset-password, refresh, or TOTP routes.
For plugin-mounted protected routes, LitestarAuth publishes per-operation OpenAPI security
requirements derived from the configured auth transports, so Swagger and other OpenAPI UIs can
authorize requests with the standard mechanism. Disable that metadata with
include_openapi_security=False.
For app-owned protected routes, reuse config.resolve_openapi_security_requirements(). If you
mount protected controllers manually, pass security= to the relevant controller factory.
Auth core¶
| Method | Path | Request body | Enabled when | Description |
|---|---|---|---|---|
| POST | {auth}/login |
LoginCredentials (identifier, password) |
Always (auth controller) | Credentials → tokens / session. |
| POST | {auth}/logout |
None | Always | Authenticated; clears session per strategy. |
| POST | {auth}/refresh |
RefreshTokenRequest (refresh_token) |
enable_refresh=True |
New access token from refresh token / cookie. |
| GET | {auth}/sessions |
None | include_session_devices=True |
Authenticated; list the current user's active DB-backed refresh sessions. CookieTransport clients can be marked current from the refresh cookie. |
| POST | {auth}/sessions |
RefreshTokenRequest (refresh_token) |
include_session_devices=True |
Authenticated; list active refresh sessions while identifying the current bearer refresh session. |
| DELETE | {auth}/sessions/{session_id} |
None | include_session_devices=True |
Authenticated; revoke one of the current user's refresh sessions by public session id. |
| POST | {auth}/sessions/revoke-others |
Optional RefreshTokenRequest (refresh_token) for bearer clients |
include_session_devices=True |
Authenticated; revoke the current user's other refresh sessions. |
| POST | {auth}/switch-organization |
SwitchOrganizationRequest (organization_slug) |
organization_config.enabled=True, organization_config.include_switch_organization=True, and a JWT-capable non-API-key backend |
Authenticated; verifies target organization membership and returns an organization-bound JWT through the configured transport. |
| POST | {auth}/organization-invitations/accept |
OrganizationInvitationTokenRequest (token) |
organization_config.enabled=True, organization_config.include_organization_invitations=True |
Authenticated; validates a single-use invitation token, requires the authenticated user's normalized email to match the invitation, creates membership roles from the invitation, and consumes the invitation. |
| POST | {auth}/organization-invitations/decline |
OrganizationInvitationTokenRequest (token) |
organization_config.enabled=True, organization_config.include_organization_invitations=True |
Authenticated; validates the token and authenticated email, then revokes the pending invitation without creating membership. |
Session/device response contracts¶
Session/device management routes use response DTOs from litestar_auth.payloads. Bearer clients use
POST {auth}/sessions with the existing RefreshTokenRequest body when they want the server to
identify the current refresh session in a list response, and may include the same body on
POST {auth}/sessions/revoke-others. Cookie clients do not submit that body; the
controller reads the configured CookieTransport refresh cookie instead.
RefreshSessionRead serializes one active DB-backed refresh session:
session_id— stable public session identifier. It is not the raw refresh token, access token, token digest, or keyed token digest.created_at— original refresh-session creation timestamp.last_used_at— timestamp of the last successful refresh rotation, ornullwhen the session has not been used after login.is_current—true/falsewhen the current refresh credential can be matched to the row, ornullwhen the route cannot compute that marker.client_metadata— optional bounded safe client hints. The built-in DB strategy stores only normalizeduser_agentmetadata capped at 255 characters; exact IP addresses and token material are not part of this contract.
List responses use RefreshSessionListResponse:
{
"sessions": [
{
"session_id": "a4ff5e6a-60f8-4a8e-9684-7239150fd91b",
"created_at": "2026-05-09T01:20:00Z",
"last_used_at": "2026-05-09T01:25:00Z",
"is_current": true,
"client_metadata": {
"user_agent": "Example Browser/1.0"
}
}
]
}
Plugin-owned session/device routes are opt-in with include_session_devices=True and are mounted
under auth_path. They are authenticated routes and always scope strategy calls to request.user.
The first controller slice supports strategies implementing the refresh-session management protocol,
including the built-in DB token strategy. JWT and Redis token strategies do not provide a session
dashboard in this slice.
| Status | Error code | Applies to | Meaning |
|---|---|---|---|
| 400 | SESSION_MANAGEMENT_UNSUPPORTED |
All session/device routes | The configured strategy does not implement refresh-session management. |
| 401 | None | All session/device routes | Authentication credentials are absent or invalid. |
| 404 | REFRESH_SESSION_NOT_FOUND |
DELETE {auth}/sessions/{session_id} |
The public session id is missing or does not belong to the authenticated user. |
Current-session detection is available when the configured strategy can identify a public session id
from the current raw refresh credential. The built-in DB token strategy supports that lookup by
hashing the supplied refresh token and comparing it to stored digests; it does not store or expose raw
refresh tokens. When the credential is available and matches one of the current user's active refresh
sessions, cookie clients can use GET {auth}/sessions and bearer clients can use
POST {auth}/sessions to mark exactly that item with is_current: true and the other active items
with false; POST {auth}/sessions/revoke-others preserves that session.
When no current refresh credential is present or resolvable (for example invalid, expired, owned by
another user, or unsupported by the current strategy), the current session is unresolved.
In that fallback, list responses keep is_current: null, and revoke-others passes an unknown
current-session marker to the strategy.
For the built-in DB strategy, this revokes all active refresh sessions for the current user.
Organization activation¶
POST {auth}/switch-organization is an opt-in organization route mounted under the backend's auth
path. The primary backend uses {auth}. Additional capable backends use {auth}/{backend_name},
matching the rest of the backend-specific controller layout.
Request body:
The route requires an authenticated user and is mounted only when
OrganizationConfig.include_switch_organization=True on an enabled organization config. The backend
must use a strategy that implements write_token_for_organization(user, organization), such as
JWTStrategy; API-key transports are skipped. Database-token, Redis-token, and API-key strategies do
not receive signed active-organization semantics and continue to use configured tenant resolvers such
as headers or subdomains.
On success, the controller returns the same login-token shape as the configured transport. A bearer
JWT backend returns the new bearer token response. A cookie JWT backend sets the configured login
cookie. The token carries a signed org claim normalized from organization_slug.
The route verifies membership before minting the token: it normalizes the slug, fetches the
organization by slug, fetches the authenticated user's membership for that organization id, then
calls JWTStrategy.write_token_for_organization(). Failures are fail closed and do not distinguish
unknown organizations from non-membership.
| Status | Error code | Meaning |
|---|---|---|
| 200 | None | Organization-bound token issued through the backend transport. |
| 400 / 422 | REQUEST_BODY_INVALID |
Body failed decoding or schema validation. |
| 401 | None | Authentication credentials are absent or invalid. |
| 403 | ORGANIZATION_SWITCH_DENIED |
Slug invalid, organization missing, user or organization id unavailable, or membership cannot be verified. |
After a successful switch, later requests authenticated by the new JWT expose
JWTContext.organization from the fully verified signed claim. With the default resolver selected by
include_switch_organization=True, ClaimTenantResolver reads that trusted context before the
middleware performs the ordinary organization-store lookup and membership verification required to
publish litestar_auth_current_organization.
Organization administration (opt-in)¶
When OrganizationConfig(enabled=True, include_organization_admin=True, store_factory=...) is set,
the plugin mounts the bundled organization-admin controller under /organizations. The route surface
is opt-in and defaults to guards=[is_superuser]. Manual mounting uses
litestar_auth.contrib.organization_admin.create_organization_admin_controller(...); keep the
default guard posture unless the application supplies an equivalent operator-only guard. Passing
guards=[] is rejected during controller assembly so this admin surface cannot be mounted without
an explicit guard.
The controller depends on litestar_auth_organization_store, parses path and query identifiers
through the configured id_parser, and delegates all writes through the shared
SQLAlchemyOrganizationAdmin operations layer. That layer normalizes slugs and membership roles,
fails closed on slug collisions and unknown targets, and protects the final privileged organization
member from removal or demotion. The default privileged role names are owner and admin.
Payload contracts live in litestar_auth.contrib.organization_admin._schemas:
OrganizationCreate, OrganizationUpdate, OrganizationRead, MembershipCreate,
MembershipRolesUpdate, MembershipRead, OrganizationInvitationCreate, and
OrganizationInvitationRead. Organization create/update payloads reject unknown fields and enforce
the same 1..128 slug bound used by the switch-organization route; organization names are also
bounded to 1..128 characters. Organization-admin role payloads accept at most 64 role names, with
each role name bounded to 1..255 characters before normalization and persistence. Paginated list
routes return {"items": [...], "total": int, "limit": int, "offset": int}.
| Method | Path | Request body | Success | Other documented statuses | Error code(s) |
|---|---|---|---|---|---|
GET |
/organizations?user_id={user_id} |
None | 200 paginated OrganizationRead page |
401, 403, 404, 422 |
ORGANIZATION_MEMBERSHIP_NOT_FOUND for malformed user_id |
POST |
/organizations |
OrganizationCreate (slug, name) |
201 OrganizationRead |
401, 403, 409, 422 |
ORGANIZATION_ALREADY_EXISTS, REQUEST_BODY_INVALID |
GET |
/organizations/{organization_id} |
None | 200 OrganizationRead |
401, 403, 404, 422 |
ORGANIZATION_NOT_FOUND |
PATCH |
/organizations/{organization_id} |
OrganizationUpdate (slug, name) |
200 OrganizationRead |
401, 403, 404, 409, 422 |
ORGANIZATION_NOT_FOUND, ORGANIZATION_ALREADY_EXISTS, REQUEST_BODY_INVALID |
DELETE |
/organizations/{organization_id} |
None | 204 empty body |
401, 403, 404, 422 |
ORGANIZATION_NOT_FOUND |
POST |
/organizations/{organization_id}/members/{user_id} |
MembershipCreate (roles) |
201 MembershipRead |
401, 403, 404, 409, 422 |
ORGANIZATION_NOT_FOUND, ORGANIZATION_MEMBERSHIP_ALREADY_EXISTS, ORGANIZATION_MEMBERSHIP_NOT_FOUND, REQUEST_BODY_INVALID |
POST |
/organizations/{organization_id}/invitations |
OrganizationInvitationCreate (invited_email, roles) |
201 OrganizationInvitationRead |
401, 403, 404, 422 |
ORGANIZATION_NOT_FOUND, REQUEST_BODY_INVALID |
GET |
/organizations/{organization_id}/invitations |
None | 200 paginated OrganizationInvitationRead page |
401, 403, 404, 422 |
ORGANIZATION_NOT_FOUND |
DELETE |
/organizations/invitations/{invitation_id} |
None | 204 empty body |
401, 403, 400, 422 |
ORGANIZATION_INVITATION_INVALID |
GET |
/organizations/{organization_id}/members |
None | 200 paginated MembershipRead page |
401, 403, 404, 422 |
ORGANIZATION_NOT_FOUND |
PATCH |
/organizations/{organization_id}/members/{user_id}/roles |
MembershipRolesUpdate (roles) |
200 MembershipRead |
401, 403, 404, 409, 422 |
ORGANIZATION_MEMBERSHIP_NOT_FOUND, ORGANIZATION_LAST_PRIVILEGED_MEMBER, REQUEST_BODY_INVALID |
DELETE |
/organizations/{organization_id}/members/{user_id} |
None | 204 empty body |
401, 403, 404, 409, 422 |
ORGANIZATION_MEMBERSHIP_NOT_FOUND, ORGANIZATION_LAST_PRIVILEGED_MEMBER |
Malformed organization ids collapse to ORGANIZATION_NOT_FOUND; malformed user ids collapse to
ORGANIZATION_MEMBERSHIP_NOT_FOUND. Malformed invitation ids on the revoke route collapse to
ORGANIZATION_INVITATION_INVALID.
Invitation admin routes create, list, and revoke pending invitations through the same
SQLAlchemyOrganizationAdmin operations layer used by the CLI. POST /organizations/{id}/invitations
normalizes the invited email and roles, revokes any existing unexpired pending invitation for the
same normalized email in that organization, stores only a token hash, and dispatches
BaseUserManager.on_after_organization_invitation(invitation, token) for delivery. The raw token is
not included in OrganizationInvitationRead and is never returned by the HTTP API.
The operator controller is separate from the authenticated invitee-facing invitation accept/decline routes and does not scope application-owned tables. Tenant foreign keys and query filtering for application data remain the application's responsibility.
Organization invitations (invitee)¶
Invitee-facing invitation routes are mounted under the backend auth path when
OrganizationConfig.include_organization_invitations=True and organizations are enabled. They are
authenticated routes for active, verified accounts and use OrganizationInvitationTokenRequest with
one token field. The token field uses the same non-empty 1..2048 character bound as other
long-lived account-token payloads before JWT parsing.
| Method | Path | Request body | Success | Other documented statuses | Error code(s) |
|---|---|---|---|---|---|
POST |
{auth}/organization-invitations/accept |
OrganizationInvitationTokenRequest (token) |
200 MembershipRead |
400, 401, 403, 422 |
ORGANIZATION_INVITATION_INVALID, ORGANIZATION_INVITATION_EXPIRED, ORGANIZATION_INVITATION_EMAIL_MISMATCH, REQUEST_BODY_INVALID |
POST |
{auth}/organization-invitations/decline |
OrganizationInvitationTokenRequest (token) |
204 empty body |
400, 401, 403, 422 |
ORGANIZATION_INVITATION_INVALID, ORGANIZATION_INVITATION_EXPIRED, ORGANIZATION_INVITATION_EMAIL_MISMATCH, REQUEST_BODY_INVALID |
A valid signed token is not sufficient to accept or decline an invitation. The route validates the
JWT signature, organization-invitation audience, expiry, stored token hash, pending row state, and
the authenticated user's active, verified account state and normalized email. Inactive or unverified
authenticated users receive the standard permission-denied response before the invitation row is
mutated. If the authenticated email does not match
OrganizationInvitationRead.invited_email, the route fails with
ORGANIZATION_INVITATION_EMAIL_MISMATCH. Accept consumes the invitation row before creating
membership with the stored roles; decline revokes the row without creating membership.
Registration and email¶
| Method | Path | Request body | Enabled when | Description |
|---|---|---|---|---|
| POST | {auth}/register |
UserCreate (email, password) |
include_register=True |
Create user; triggers hooks (e.g. send verification email). |
| POST | {auth}/verify |
VerifyToken (token) |
include_verify=True |
Confirm email with token. |
| POST | {auth}/request-verify-token |
RequestVerifyToken (email) |
include_verify=True |
Re-issue verification token. |
When you replace the built-in UserCreate request body with user_create_schema, reuse
litestar_auth.schemas.UserEmailField and litestar_auth.schemas.UserPasswordField for email / password when
you want the documented built-in validation metadata. Existing UserPasswordField imports remain valid; add
UserEmailField when you also want the built-in email regex and max length. The default runtime validator still
enforces password length through require_password_length. See
Configuration for the full schema-helper,
password-validator, and shared-helper contract.
Built-in user-returning responses from POST {auth}/register, POST {auth}/verify, and
POST {auth}/reset-password use UserRead, which serializes id, email, is_active,
is_verified, and normalized roles.
That response contract is role-centric after the superuser migration.
The HTTP API exposes one flat roles array, not a superuser boolean, raw role / user_role
rows, permission matrices, or role-catalog/user-assignment endpoints on core plugin-owned auth
routes.
For operational catalog and user-role administration, use the plugin-owned
litestar roles CLI surface or mount the opt-in contrib controller from
HTTP role administration.
Password reset¶
| Method | Path | Request body | Enabled when | Description |
|---|---|---|---|---|
| POST | {auth}/forgot-password |
ForgotPassword (email) |
include_reset_password=True |
Always returns 202; enumeration-safe. |
| POST | {auth}/reset-password |
ResetPassword (token, password) |
include_reset_password=True |
Apply new password with reset token. |
TOTP (2FA)¶
Mounted under {auth}/2fa/... when totp_config is set.
| Method | Path | Request body | Notes |
|---|---|---|---|
| POST | {auth}/2fa/enable |
TotpEnableRequest (password) by default; no body when totp_enable_requires_password=False |
Authenticated; starts enrollment. |
| POST | {auth}/2fa/enable/confirm |
TotpConfirmEnableRequest (enrollment_token, code) |
Authenticated; confirms enrollment and returns one-time recovery codes. |
| POST | {auth}/2fa/verify |
TotpVerifyRequest (pending_token, code) |
Completes login when TOTP is enabled; code accepts either a current TOTP code or an unused recovery code. Successful app-code verification records the server-side recent-TOTP marker used by downstream step-up checks; recovery-code verification only does so when totp_stepup_allow_recovery=True. |
| POST | {auth}/2fa/disable |
TotpDisableRequest (code) |
Authenticated; disables TOTP. code accepts either a current TOTP code or an unused recovery code. A recovery code remains accepted for lockout recovery. |
| POST | {auth}/2fa/recovery-codes/regenerate |
TotpRegenerateRecoveryCodesRequest (optional current_password, optional totp_code) |
Authenticated; replaces the stored recovery-code set and returns the new plaintext codes once. current_password is required when totp_enable_requires_password=True; totp_code can satisfy the TOTP step-up gate. |
TotpConfirmEnableResponse and TotpRecoveryCodesResponse carry recovery_codes; those plaintext
values are returned once and only their hashes are stored. Generated recovery codes are 28 lowercase
hex characters (112 bits). TotpEnableResponse necessarily carries the plaintext TOTP secret and
otpauth URI for QR-code rendering, so production deployments must serve {auth}/2fa/enable only
over HTTPS. Pending-login JWTs are client-bound by default with cip / uaf fingerprints, so a
/2fa/verify request from a different client receives the same TOTP_PENDING_BAD_TOKEN response as
an invalid pending token.
The built-in TOTP flow remains email-oriented internally: the otpauth URI and default password step-up for POST {auth}/2fa/enable use request.user.email, not login_identifier.
Sensitive operations use LitestarAuthConfig.totp_stepup_policy and default to required_when_enrolled
for users.update_self, api_keys.create, api_keys.update, api_keys.revoke, oauth.associate,
totp.disable, and totp.regenerate_recovery_codes. Enrolled users must present either a recent
server-side TOTP marker from /2fa/verify or a valid inline totp_code field where the request body
supports it. Missing proof returns HTTP 403 with TOTP_STEPUP_REQUIRED; users without TOTP are not
blocked unless the endpoint policy is always_required. The full configuration and per-endpoint
default table lives in
TOTP step-up for sensitive operations.
OAuth2 login¶
When oauth_config.oauth_providers is configured with oauth_redirect_base_url, the plugin auto-mounts login routes under {auth}/oauth/{provider}.
| Method | Path pattern | Description |
|---|---|---|
| GET | {auth}/oauth/{provider}/authorize |
Redirect to provider. |
| GET | {auth}/oauth/{provider}/callback |
Provider redirect; completes the plugin-owned OAuth login flow. |
If you mount create_provider_oauth_controller() or create_oauth_controller() directly for a custom route table, the prefix may differ from {auth}/oauth/{provider}.
OAuth account linking (associate)¶
When oauth_config.include_oauth_associate=True, the plugin auto-mounts associate routes under {auth}/associate/{provider} for the same oauth_providers inventory.
| Method | Path pattern | Description |
|---|---|---|
| POST | {auth}/associate/{provider}/authorize |
Authenticated user starts linking. CSRF-protected: cookie-transport deployments must mirror the plugin-managed CSRF cookie into the configured csrf_header_name (defaults to X-CSRF-Token); bearer-only deployments rely on the cross-origin attachment of Authorization to be impossible. The route is POST (not GET) so Litestar's CSRF middleware can enforce that token check before the body runs and forced-association attacks fail closed. |
| GET | {auth}/associate/{provider}/callback |
Completes linking for request.user. Stays GET because OAuth providers redirect there with GET. |
Users CRUD¶
When include_users=True, routes are under {users}.
| Method | Path | Guard |
|---|---|---|
| GET | {users}/me |
Authenticated |
| PATCH | {users}/me |
Authenticated |
| POST | {users}/me/change-password |
Authenticated |
| GET | {users}/{id} |
Superuser |
| PATCH | {users}/{id} |
Superuser |
| DELETE | {users}/{id} |
Superuser |
| GET | {users} |
Superuser (list) |
The built-in users surface also serializes UserRead, so all /users reads include normalized
roles. PATCH {users}/me strips roles and the other privileged fields from self-service
payloads even when a custom user_update_schema includes them. Email changes use UserUpdate
(email, optional current_password, optional totp_code) and require current_password; non-email self-service
updates from custom schemas do not. PATCH {users}/me does not rotate passwords.
Authenticated password rotation uses POST {users}/me/change-password with ChangePasswordRequest
(current_password, new_password); the controller re-verifies the current password before
delegating the replacement password through the manager update lifecycle. Wrong or missing
current-password submissions on email changes and password rotation return 400 with
LOGIN_BAD_CREDENTIALS, invalid replacement passwords return 400 with
UPDATE_USER_INVALID_PASSWORD, malformed request payloads use REQUEST_BODY_INVALID,
unauthenticated requests return 401, and configured rate limits return 429 with Retry-After.
Superuser PATCH {users}/{id} uses AdminUserUpdate, can persist validated roles, and remains
the admin-initiated password rotation path.
Relational role tables do not get separate CRUD endpoints in the core plugin. Built-in users routes
continue to manage only the normalized flat roles contract on the user boundary.
Operator-driven catalog and assignment administration lives on the
litestar roles CLI surface, while applications that need an HTTP admin
surface can opt into HTTP role administration.
Contrib role administration (opt-in)¶
If you mount litestar_auth.contrib.role_admin.create_role_admin_controller(...), the library
adds an admin-only HTTP role-management surface under its configured prefix (default /roles).
The factory defaults to guards=[is_superuser]; see
HTTP role administration for mounting and override guidance.
In the route table below, {roles} means the configured contrib role-admin prefix (default /roles).
Payload contracts live in litestar_auth.contrib.role_admin._schemas:
RoleCreate, RoleUpdate, RoleRead, and UserBrief. Paginated list routes return
{"items": [...], "total": int, "limit": int, "offset": int}. The controller also reserves
these machine-readable ErrorCode values for role-catalog and assignment failures:
ROLE_ALREADY_EXISTS, ROLE_NOT_FOUND, ROLE_STILL_ASSIGNED,
ROLE_ASSIGNMENT_USER_NOT_FOUND, and ROLE_NAME_INVALID.
| Method | Path | Request body | Success | Other documented statuses | Error code(s) |
|---|---|---|---|---|---|
GET |
{roles} |
None | 200 paginated RoleRead page |
403, 422 |
None |
POST |
{roles} |
RoleCreate |
201 RoleRead |
403, 409, 422 |
ROLE_ALREADY_EXISTS, ROLE_NAME_INVALID |
GET |
{roles}/{role_name} |
None | 200 RoleRead |
403, 404, 422 |
ROLE_NOT_FOUND, ROLE_NAME_INVALID |
PATCH |
{roles}/{role_name} |
RoleUpdate |
200 RoleRead |
403, 404, 422 |
ROLE_NOT_FOUND, ROLE_NAME_INVALID |
DELETE |
{roles}/{role_name} |
None | 204 empty body |
403, 404, 409, 422 |
ROLE_NOT_FOUND, ROLE_STILL_ASSIGNED, ROLE_NAME_INVALID |
POST |
{roles}/{role_name}/users/{user_id} |
None | 200 RoleRead |
403, 404, 422 |
ROLE_NOT_FOUND, ROLE_ASSIGNMENT_USER_NOT_FOUND, ROLE_NAME_INVALID |
DELETE |
{roles}/{role_name}/users/{user_id} |
None | 204 empty body |
403, 404, 422 |
ROLE_ASSIGNMENT_USER_NOT_FOUND, ROLE_NAME_INVALID |
GET |
{roles}/{role_name}/users |
None | 200 paginated UserBrief page |
403, 404, 422 |
ROLE_NOT_FOUND, ROLE_NAME_INVALID |
Assignment writes are idempotent and run through the manager lifecycle instead of mutating
association rows behind BaseUserManager.
The user_id path parameter is parsed UUID-first, then falls back to the configured model's
primary-key shape, so the same controller works with bundled UUID users and integer-key custom
models.
API keys¶
When api_keys.enabled=True, the plugin mounts self-service routes under /api-keys and
superuser inventory routes under {users} (users_path, default /users).
All API-key inventory routes require requires_password_session, so callers authenticated with an
API key cannot list, create, update, or revoke API keys. Sensitive mutations also use the TOTP
step-up policy for api_keys.create, api_keys.update, and api_keys.revoke.
| Method | Path | Request body | Success | Other documented statuses | Error code(s) |
|---|---|---|---|---|---|
POST |
/api-keys |
ApiKeyCreateRequest |
201 ApiKeyCreateResponse |
400, 401, 403, 422, 429 |
LOGIN_BAD_CREDENTIALS, API_KEY_SCOPE_DENIED, API_KEY_LIMIT_REACHED, REQUEST_BODY_INVALID, TOTP_STEPUP_REQUIRED |
GET |
/api-keys |
None | 200 ApiKeyListResponse |
401, 403 |
AUTHORIZATION_DENIED |
GET |
/api-keys/{key_id} |
None | 200 ApiKeyRead |
401, 403, 404 |
AUTHORIZATION_DENIED, API_KEY_INVALID |
PATCH |
/api-keys/{key_id} |
ApiKeyUpdateRequest |
200 ApiKeyRead |
400, 401, 403, 404, 422, 429 |
LOGIN_BAD_CREDENTIALS, API_KEY_SCOPE_DENIED, AUTHORIZATION_DENIED, API_KEY_INVALID, REQUEST_BODY_INVALID, TOTP_STEPUP_REQUIRED |
DELETE |
/api-keys/{key_id} |
None | 200 ApiKeyRead |
401, 403, 404 |
AUTHORIZATION_DENIED, API_KEY_INVALID, TOTP_STEPUP_REQUIRED |
POST |
{users}/{user_id}/api-keys |
ApiKeyAdminCreateRequest |
201 ApiKeyCreateResponse |
400, 401, 403, 404 |
API_KEY_SCOPE_DENIED, API_KEY_LIMIT_REACHED, AUTHORIZATION_DENIED, TOTP_STEPUP_REQUIRED |
GET |
{users}/{user_id}/api-keys |
None | 200 ApiKeyListResponse |
401, 403, 404 |
AUTHORIZATION_DENIED |
DELETE |
{users}/{user_id}/api-keys/{key_id} |
None | 200 ApiKeyRead |
401, 403, 404 |
AUTHORIZATION_DENIED, API_KEY_INVALID, TOTP_STEPUP_REQUIRED |
ApiKeyCreateResponse returns the raw api_key only once at create time plus safe metadata in
key. Subsequent list/get/update/revoke responses expose only ApiKeyRead metadata. Credential
formats and optional request-signing headers are documented in API keys.
Multiple backends¶
If more than one backend is configured, the first uses {auth} as above. Additional backends use {auth}/{backend-name}/... for their auth routes (see Backends).
Rate limiting¶
When rate_limit_config is set, selected endpoints may return 429 with Retry-After. See the Rate limiting guide and the Python API.