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=..."
}