Onboarding and Session Bootstrap

The end-to-end flow for first sign-in, requirements discovery, token acquisition, and session continuity.

Use this flow for browser clients.

1. Load Auth Requirements

Call GET /v1/auth/requirements before showing registration or login forms. The response includes whether registration requires admin approval, whether MFA is enforced for password login, and the active password policy.

2. Register Or Login

Register with POST /v1/auth/register. If restricted registration is enabled, the account is created pending approval and no session cookies are issued.

Email identity is matched after trimming surrounding whitespace and applying canonical lowercase comparison. Clients must not treat case variants such as User@example.com and user@example.com as separate accounts.

Login with POST /v1/auth/login:

const response = await fetch(`${apiBaseUrl}/v1/auth/login`, {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ usernameOrEmail, password }),
});

If requiresMfa is true, prompt for the selected MFA method and call POST /v1/auth/login again with the returned mfaToken. When MFA enforcement is disabled, password login can complete without this second step.

After successful login or MFA completion, the API sets the browser session cookies. The response body contains the user profile and admin role, but not a readable access token.

Use credentials: 'include' on subsequent API calls.

4. Refresh

Call POST /v1/auth/refresh before or after a 401 caused by access-token expiry. The API rotates the refresh token and sets a new access-token cookie.

If the response includes X-Auth-Error, treat the session as invalid and return the user to login.

5. Logout

Call POST /v1/auth/logout with credentials included. The API revokes the refresh token and clears session cookies.

Recover A Forgotten Password

Start recovery with the anonymous request endpoint:

const response = await fetch(`${apiBaseUrl}/v1/auth/password-reset/request`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email }),
});

A valid request returns 202 Accepted with the same message whether or not the email belongs to an eligible account. Do not use the response to decide whether an account exists. The endpoint is rate-limited and returns 429 Too Many Requests when its request limits are exceeded.

For an eligible account, the email contains two separate values:

  • a PixAtlas link ending in /password-reset/{challengeId}, where challengeId is a UUID
  • a six-digit verification code that is not included in the link

The frontend owns that route and should read the UUID from the path, ask the user for the emailed code and a new password, and then call:

const response = await fetch(
  `${apiBaseUrl}/v1/auth/password-reset/${challengeId}`,
  {
    method: 'POST',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ code, newPassword }),
  },
);

The code must contain exactly six digits, including any leading zero. A successful reset returns 204 No Content; it does not create a new login session. Invalid, expired, consumed, or attempt-limited challenges all return the same 400 Bad Request challenge error. A new password that violates the active password policy also returns 400 Bad Request with the policy error, so the frontend should load GET /v1/auth/requirements before presenting the form.

After success, all refresh-token browser sessions and trusted-device tokens for that account are revoked, and authentication cookies on the current response are expired. Personal access tokens and enrolled MFA methods are preserved. The user must sign in normally with the new password and complete MFA when required.

On this page