Authentication

Ragz uses Argon2id-hashed passwords, short-lived JWT access tokens, and rotating refresh tokens for the web app, plus long-lived API keys for programmatic access. This page covers both.

Endpoints

Method & pathAuthPurpose
GET /api/v1/auth/bootstrap-statusnoneWhether a fresh install still needs its first superadmin
POST /api/v1/auth/registernoneFirst-run only — the first registrant becomes superadmin, then this 409s forever after
POST /api/v1/auth/loginnoneEmail + password → access token + refresh cookie
POST /api/v1/auth/refreshrefresh cookieRotate the refresh token, issue a new access token
POST /api/v1/auth/logoutrefresh cookieRevoke the current session
POST /api/v1/auth/invitationsJWT, admin/superadminInvite a user by email into an org
POST /api/v1/auth/invitations/acceptnone (invite token)Accept an invitation, set a password
POST /api/v1/auth/forgot-passwordnoneRequest a password-reset email (enumeration-safe: always 202)
POST /api/v1/auth/reset-passwordnone (reset token)Complete a password reset
POST /api/v1/auth/change-passwordJWTChange your own password
GET /api/v1/auth/sessionsJWTList your own live refresh-token sessions
DELETE /api/v1/auth/sessions/{family_id}JWTRevoke one of your own sessions
POST /api/v1/auth/sessions/revoke-othersJWTRevoke every session but the current one
POST /api/v1/admin/api-keysJWT, superadminIssue an API key for a user + workspace
GET /api/v1/admin/api-keysJWT, superadminList issued API keys (masked — no secret material)
DELETE /api/v1/admin/api-keys/{key_id}JWT, superadminRevoke an API key

Session JWTs (web app)

POST /auth/login returns a 15-minute access token in the response body and sets a rotating refresh token as an httpOnly, Secure, SameSite=Strict cookie scoped to /api/v1/auth. Send the access token on every subsequent request as a bearer header; when it expires, call /auth/refresh (the cookie authenticates that call — no body needed) to get a new one and a rotated refresh cookie.

# Use it
curl https://ragz.example.com/api/v1/workspaces \
  -H "Authorization: Bearer eyJ..."

Bootstrap Status

Whether a fresh install still needs its first superadmin.

GET /api/v1/auth/bootstrap-status · Auth: none

Sample response

{"needs_setup": true}

Response fields

FieldTypeDescription
needs_setupbooleantrue when no superadmin exists yet and POST /auth/register is open.

Register

First-run only — the first registrant becomes the platform superadmin, then this returns 409 Conflict forever after.

POST /api/v1/auth/register · Auth: none

First-run bootstrap

A brand-new install has no users. GET /auth/bootstrap-status reports {"needs_setup": true}, and POST /auth/register is open — the very first account created becomes the platform superadmin. Every registration after that returns 409 Conflict; new users join via invitations from then on.


Login

Email + password → access token + refresh cookie.

POST /api/v1/auth/login · Auth: none

Request body

ParameterTypeRequiredDescription
emailstringYesAccount email.
passwordstringYesAccount password.

Sample request

# Log in — access token in the body, refresh token set as a cookie
curl -sX POST https://ragz.example.com/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -c cookies.txt \
  -d '{"email": "you@example.com", "password": "correct horse battery staple"}'

Sample response

{"access_token": "eyJ...", "token_type": "bearer"}

Response fields

FieldTypeDescription
access_tokenstring15-minute JWT access token.
token_typestringAlways bearer.

Notes

  • Also sets a rotating refresh token as an httpOnly, Secure, SameSite=Strict cookie scoped to /api/v1/auth.
  • Send access_token as a bearer header on every subsequent request.

Refresh

Rotate the refresh token, issue a new access token.

POST /api/v1/auth/refresh · Auth: refresh cookie

Sample request

# Refresh when it expires (cookie jar carries the refresh token)
curl -sX POST https://ragz.example.com/api/v1/auth/refresh -b cookies.txt -c cookies.txt

Response fields

Same {access_token, token_type} shape as Login, plus a rotated refresh cookie.

Notes

  • The refresh cookie authenticates the call — no request body needed.

Logout

Revoke the current session.

POST /api/v1/auth/logout · Auth: refresh cookie


Invitations

An admin or superadmin creates an invitation to bring a new user into an org.

POST /api/v1/auth/invitations · Auth: JWT, admin/superadmin

Request body

ParameterTypeRequiredDescription
emailstringYesEmail address to invite.
rolestringYes"admin" or "user".
org_idstring (uuid) | nullNoOptional and superadmin-only — a superadmin can invite into any org; a plain org admin can only invite into their own (the field is ignored/overridden for them server-side).

Sample request

curl -sX POST https://ragz.example.com/api/v1/auth/invitations \
  -H "Authorization: Bearer eyJ..." \
  -H "Content-Type: application/json" \
  -d '{ "email": "newperson@example.com", "role": "user", "org_id": null }'

Response fields

FieldTypeDescription
invite_tokenstringOne-time token the invitee redeems via Accept Invitation to set their password and activate the account.

Accept Invitation

Accept an invitation and set a password.

POST /api/v1/auth/invitations/accept · Auth: none (invite token)

Request body

ParameterTypeRequiredDescription
tokenstringYesThe invite_token from the invitation.
passwordstringYesPassword to set for the new account.

Sample request

curl -sX POST https://ragz.example.com/api/v1/auth/invitations/accept \
  -H "Content-Type: application/json" \
  -d '{ "token": ..., "password": ... }'

Notes

  • Sets the account's password and activates it.

Forgot Password

Request a password-reset email. Enumeration-safe: always returns 202, whether or not the email matches an active account.

POST /api/v1/auth/forgot-password · Auth: none


Reset Password

Complete a password reset.

POST /api/v1/auth/reset-password · Auth: none (reset token)


Change Password

Change your own password.

POST /api/v1/auth/change-password · Auth: JWT


List Sessions

List your own live refresh-token sessions.

GET /api/v1/auth/sessions · Auth: JWT


Revoke Session

Revoke one of your own sessions.

DELETE /api/v1/auth/sessions/{family_id} · Auth: JWT


Revoke Other Sessions

Revoke every session but the current one.

POST /api/v1/auth/sessions/revoke-others · Auth: JWT


API Keys (Programmatic Access)

For scripts, backend services, or the OpenAI-compatible endpoint, a superadmin issues an API key scoped to exactly one user and one workspace.


Create API Key

Issue an API key for a user + workspace.

POST /api/v1/admin/api-keys · Auth: JWT, superadmin

Request body

ParameterTypeRequiredDescription
namestringYesHuman-readable label for the key.
user_idstring (uuid)YesThe user the key acts as.
workspace_idstring (uuid)YesThe workspace the key is scoped to.
expires_atstring (datetime)NoOptional expiry. Keys default to expiring after a bounded lifetime if omitted — there are no perpetual keys.

Sample request

curl -sX POST https://ragz.example.com/api/v1/admin/api-keys \
  -H "Authorization: Bearer $SUPERADMIN_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "reporting-bot",
    "user_id": "5b6c...-uuid",
    "workspace_id": "a1b2...-uuid"
  }'

Notes

  • The response includes api_key — the raw key, shown exactly once (the server stores only its hash).
  • Every other read of the key (GET /admin/api-keys) returns the masked ApiKeyOut shape: id, name, prefix, org/user/workspace ids, expiry, timestamps — never the secret itself.
  • Revoke early with DELETE /admin/api-keys/{key_id}.

List API Keys

List issued API keys (masked — no secret material).

GET /api/v1/admin/api-keys · Auth: JWT, superadmin

Notes

  • Returns the masked ApiKeyOut shape: id, name, prefix, org/user/workspace ids, expiry, timestamps — never the secret itself.

Revoke API Key

Revoke an API key.

DELETE /api/v1/admin/api-keys/{key_id} · Auth: JWT, superadmin

Keys are write-only after creation

The raw key value is never retrievable again after the create response. If it's lost, revoke it and issue a new one — there is no "reveal" endpoint.


Using an API Key

Send the key as a bearer token (or X-API-Key) against the /external/v1 surface — not /api/v1:

curl -sX POST https://ragz.example.com/external/v1/chat \
  -H "Authorization: Bearer $RAGZ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"question": "What is our refund policy?"}'

Notes

  • Every API-key request revalidates that the backing user still has a live membership in the key's workspace and still holds the permission to chat — so disabling a user or removing their workspace membership immediately invalidates any key issued for them, without a separate revocation step.