Authentication Endpoints
POST /auth/login
Authenticate with email + password.
Request:
{
"email": "user@example.com",
"password": "their_password",
"app_id": "uuid",
"device_id": "optional-device-identifier"
}
Success Response (200):
{
"access_token": "eyJhbG...",
"refresh_token": "a1b2c3...",
"token_type": "Bearer",
"expires_in": 900
}
TOTP Required (200):
{
"status": "totp_required",
"totp_session": "short-lived-jwt"
}
Errors:
| Status | Detail | Meaning |
|--------|--------|---------|
| 400 | Invalid app_id | App not found or inactive |
| 400 | Password login not enabled | App doesn't allow password auth |
| 401 | Invalid email or password | Bad credentials |
| 403 | You do not have access | Not enrolled, invite required |
| 403 | Account suspended | User suspended for this app |
| 403 | {"error": "2fa_required", ...} | App requires 2FA, user hasn't set it up |
| 429 | rate_limited | Too many attempts (5/15min per email, 20/15min per IP) |
POST /auth/totp/verify
Complete TOTP challenge after password login.
Request:
{
"totp_session": "jwt-from-login-response",
"code": "123456"
}
Success Response (200): Same as login success (access + refresh tokens).
Errors: | Status | Detail | |--------|--------| | 400 | Invalid or expired TOTP session | | 401 | Invalid TOTP code | | 429 | Too many TOTP attempts |
POST /auth/signup
Create a new account and enroll (open/approval apps only).
Request:
{
"email": "user@example.com",
"password": "their_password",
"display_name": "Jane Doe",
"app_id": "uuid"
}
Success Response (200): Access + refresh tokens.
Errors: | Status | Detail | |--------|--------| | 400 | This app requires an invite code | | 400 | Account already exists | | 400 | Password must be at least N characters |
POST /auth/accept-invite
Accept an invite code. Handles both existing users and new registrations.
Request:
{
"app_id": "uuid",
"invite_code": "ABCD1234",
"email": "user@example.com",
"display_name": "Jane Doe",
"password": "optional_for_new_users"
}
For existing users (invite was sent to their email): only app_id and invite_code are required.
Success Response (200): Access + refresh tokens.
POST /auth/forgot-password
Request a password reset email.
Request:
{
"email": "user@example.com",
"app_id": "uuid"
}
Response (200): Always succeeds (doesn't reveal whether account exists).
{
"detail": "If an account exists with that email, a reset link has been sent."
}
POST /auth/reset-password
Reset password using a token from the reset email.
Request:
{
"token": "reset-token-from-email",
"password": "new_secure_password"
}
Success Response (200):
{ "status": "ok" }
Revokes all SSO sessions and unused reset tokens for the user.
POST /auth/magic-link/request
Request a magic link email for passwordless login.
Request:
{
"email": "user@example.com",
"app_id": "uuid"
}
Response (200):
{ "status": "sent" }
GET /auth/magic-link/verify
Verify a magic link token (user clicks the link in their email).
Query Params: ?token={magic_link_token}
Success: Redirects to the app's redirect_uri with access + refresh tokens.
Failure: Redirects to login with error=invalid_magic_link.
OAuth Provider Endpoints
GET /oauth/{provider}/start
Initiate OAuth flow. Redirects to provider consent screen.
Providers: google, github, microsoft, apple
Query Params:
- app_id (required) — UUID of the app
- redirect_uri (optional) — Must match a registered redirect URI
GET /oauth/{provider}/callback
OAuth callback handler. Do not call directly — Keymaster handles this.
POST /oauth/invite-accept
Accept an invite code using a pending OAuth identity (when an OAuth user hits an invite-only app).
Request:
{
"oauth_token": "pending-oauth-session-token",
"app_id": "uuid",
"invite_code": "ABCD1234",
"display_name": "Jane Doe"
}
Success Response (200):
{
"redirect": "https://yourapp.com/auth/callback?access_token=...&refresh_token=..."
}