Skip to Content
API ReferenceEndpointsAuth

Auth Endpoints

The auth service handles email/password login and JWT token exchange. All endpoints are served through the API gateway at http://localhost:8000.

Endpoints

MethodPathDescription
POST/auth/tokenExchange email + password for a JWT token (also sets the aegis_token cookie since R42a)
POST/auth/resolveJWT → DB-fresh identity + roles + permissions (internal)
GET/auth/meCaller’s identity, roles, permissions
POST/auth/refreshSliding refresh (re-mints tokens older than 1h)
POST/auth/logoutExpire the session cookie
POST/auth/logout-allRevoke all of the caller’s outstanding tokens
POST/auth/admin/usersCreate an invited user → one-time invite URL (admin)
GET/auth/admin/usersAdmin user directory with status (admin)
PATCH/auth/admin/users/{id}Edit display name / active status / roles (admin)
POST/auth/admin/users/{id}/reset-passwordOne-time reset URL, revokes sessions (admin)
GET/auth/invites/{token}Validate an invite/reset token (public)
POST/auth/invites/redeemSet password via token and log in (public)
POST/auth/validateValidate a JWT token (legacy claims-only)
GET/auth/usersTenant user directory (admin-only)

POST /auth/token

Exchange email and password credentials for a JWT access token.

Request:

curl -i -X POST http://localhost:8000/api/v1/auth/token \ -H "Content-Type: application/json" \ -d '{"email": "admin@aegis.local", "password": "aegis-dev-admin"}'

Request body:

{ "email": "admin@aegis.local", "password": "aegis-dev-admin" }

Response (200) — since R42b the JWT rides only in the Set-Cookie: aegis_token=... header; the body carries identity fields:

{ "expires_in": 86400, "user_id": "52c8f756-...", "roles": ["admin", "operator", "reviewer"], "email": "admin@aegis.local", "display_name": "Bootstrap Admin" }

Returns 401 Unauthorized with {"detail": "Invalid email or password"} when the credentials are wrong, the user is inactive, or the account is still invited (no password set). Email is matched case-insensitively.

The response also carries Set-Cookie: aegis_token=<jwt>; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=86400 (HttpOnly/Secure were enabled at the R42b client cutover), and the JWT includes a tv token-version claim used for revocation.

For local development, log in with admin@aegis.local / aegis-dev-admin (the seeded bootstrap admin), which grants admin access.


POST /auth/validate

Legacy claims-only token check. Superseded by /auth/resolve (R42b) — the gateway now resolves DB-fresh identity + permissions rather than trusting the claims. Kept for backward compatibility; it decodes the JWT and returns its claims without any is_active / token_version check.

The token is passed as an authorization parameter (a Bearer <jwt> string), not a JSON body:

curl -X POST "http://localhost:8000/api/v1/auth/validate?authorization=Bearer%20eyJhbGciOiJIUzI1NiIs..."

Response (200):

{ "valid": true, "user_id": "9b2f1c4e-...", "roles": ["admin", "operator", "reviewer"] }

An unparseable or empty token returns {"valid": false, "user_id": null, "roles": []}.


POST /auth/resolve

JWT → DB-fresh authorization state (R42a, spec D2). Internal: backs the perimeters; the gateway NotFounds /api/v1/auth/resolve from R42b. Enforces is_active and token_version (tv claim, missing = 0) behind a ~5s per-user cache — this is why role changes and deactivation bind within seconds.

Request: Authorization: Bearer <jwt> (or the aegis_token cookie), no body.

Response (200, always):

{ "valid": true, "user_id": "9b2f1c4e-...", "email": "admin@aegis.local", "display_name": "Bootstrap Admin", "tenant_id": "00000000-0000-0000-0000-000000000001", "roles": ["admin", "operator", "reviewer"], "permissions": ["conversations.use", "dashboards.read", "hitl.approve", "users.manage", "..."] }

Invalid tokens return {"valid": false, "reason": "signature|expired|revoked|inactive|unknown_user", "roles": [], "permissions": []}.


Session endpoints (R42a)

  • GET /auth/me — the caller’s {user_id, email, display_name, tenant_id, roles, permissions}; 401 if the token doesn’t resolve. The UI’s identity source once the cookie is httpOnly (R42b).
  • POST /auth/refresh — if the token is valid and older than 1 hour, mints a fresh 24h token (200 + Set-Cookie + body token); younger tokens get 204.
  • POST /auth/logout — expires the aegis_token cookie. Unauthenticated, idempotent.
  • POST /auth/logout-all — bumps the caller’s token_version; every outstanding token then resolves {"valid": false, "reason": "revoked"} within the cache window.

Admin user lifecycle (R42a)

All gated on permissions resolved from the caller’s own JWT (users.manage; role changes additionally roles.grant). Accounts are deactivate-only — there is no delete endpoint.

POST /auth/admin/users — create an invited user:

{ "email": "jane@example.com", "display_name": "Jane", "roles": ["operator", "reviewer"] }

Response (201): the user (status invited) plus a one-time invite_token / invite_url (7-day expiry). The raw token is never stored — share it out-of-band; there is no email delivery.

GET /auth/admin/users — directory rows with roles, is_active, and status (active / invited / disabled).

PATCH /auth/admin/users/{id} — any of display_name, is_active, roles. Deactivation bumps token_version (live sessions die ≤5s). Removing the last active admin returns 409.

POST /auth/admin/users/{id}/reset-password — voids open tokens, bumps token_version, returns a one-time reset_url (1-hour expiry).


Invite redemption (public)

GET /auth/invites/{token} — validate before rendering the set-password form. Returns {email, display_name, purpose, expires_at}; unknown, used, and expired tokens all return 404.

POST /auth/invites/redeem{"token": "...", "password": "..."} (≥8 chars). Sets the password (single-use, row-locked), then logs the user in — the response mirrors /auth/token including Set-Cookie. A second redeem of the same token returns 404.


GET /auth/users

List the tenant’s users — the directory behind the Platform Settings page’s user picker. Admin-only: the JWT (Bearer header or aegis_token cookie) is decoded in-service, and non-admin callers get 403.

Request:

curl http://localhost:8000/api/v1/auth/users \ -H "Authorization: Bearer $TOKEN"

Response (200):

[ { "id": "9b2f1c4e-...", "email": "admin@aegis.local", "display_name": "Bootstrap Admin", "roles": ["admin", "operator", "reviewer"], "is_active": true } ]
Last updated on