Authentication and Access
How PixService combines JWTs, sessions, personal access tokens, MFA, and scoped authorization.
PixService separates interactive browser sessions from external API credentials.
Browser users authenticate with password and, when MFA enforcement is enabled, MFA. The API issues a short-lived access JWT as an HttpOnly cookie and a refresh token as a separate HttpOnly cookie. The frontend never needs to read or store the JWT.
External clients authenticate with personal access tokens. A PAT is sent as Authorization: Bearer <token> on each request and is validated directly by the API.
PAT authentication requires both a live token and an active owning account. Access JWTs obtained by exchanging a PAT retain the PAT identifier and are checked against that live token and account on every request, so revocation, expiry, deletion, or account deactivation is not deferred until JWT expiry. PAT-authenticated identities cannot mint another PAT.
Session State
Every browser access token is tied to a refresh-token session id. If the session is revoked, expired, or rotated anomalously, access-token validation fails even if the JWT has not reached its expiry time.
The session model supports:
- refresh-token rotation
- session revocation
- deployment-level MFA enforcement
- trusted-device MFA bypass when MFA enforcement is enabled
- MFA completion by email or TOTP
- admin role claims in access tokens
Authorization Scope
After authentication, controllers and application services enforce owner, organization, cluster, location, and role checks. Authentication identifies the caller; it does not by itself grant access to every resource. Legacy free-form PAT permissions values are not enforced scopes and are not emitted as authorization claims.
Account closure preview
An interactive account holder can call GET /v1/users/me/account-deletion-preview to see the protected organizations they own and whether they are the sole owner of each one. The response also lists personal-data categories under review and says whether account deletion or a bounded export is currently available. It is private to the signed-in account, is returned with Cache-Control: no-store, and does not accept personal access tokens (403). An unauthenticated request returns 401.
Account closure is available only when the deployment has enabled its reviewed policy, independent erasure authority and processing safeguards. The preview does not close the account, create an export, or reserve an expiry date. exportAvailable: true, exportScope: "profile_and_preferences", and exportEndpoint describe the separate immediate download below. Its organization list can change; the deletion request must check ownership again when it is accepted. The organizationResourcesRetained field expresses the intended policy for organization-owned work, not a completed cleanup action. Chat and detailed personal-data treatment still require policy review. Clients should use deletionAvailable to disable closure and show a plain-language unavailable state rather than displaying the response's machine-readable unavailableReasons directly.
Download your profile data
GET /v1/users/me/data-export downloads a JSON attachment for the exact signed-in account. It includes profile identifiers and contact fields (id, username, email, displayName, address, creation and update times), account preferences (language, timeZoneId, defaultMeasurementProfileId), and the account's phone numbers. The current export uses schemaVersion: 2; version 2 adds the effective personal measurement profile to preferences. Clients supporting older servers should also accept version 1 without that field. The JSON includes schemaVersion, exportedAtUtc, includedCategories, and excludedCategories so consumers can identify its bounded scope. The export does not include credentials, sessions, MFA secrets, personal access tokens, profile-picture bytes, notifications, search history, chat messages, organization or company records, shared content, or activity and audit records. It is not a complete legal access response or a closure archive.
The download requires a fresh interactive login within ten minutes. An enrolled MFA method must be verified during that login. Send requireMfa: true on the password step of POST /v1/auth/login to force an available MFA challenge even if normal login allows a trusted device; complete the challenge using the usual mfaToken, mfaMethod, and mfaCode fields. Accounts without an enabled MFA method can use fresh password login. Refreshed sessions do not satisfy this requirement. A stale session receives 403 with code: "data_export_recent_authentication_required"; an enrolled account without MFA verification receives 403 with code: "data_export_mfa_required". PATs receive 403, and unauthenticated requests receive 401. Responses use Cache-Control: no-store; the server does not retain an export archive.
Close your account and keep a recovery receipt
When deletionAvailable is true, the preview supplies policyVersion, closureEndpoint, closureExportScope and closureExportMaximumDays. Check the exact supported version and scope before enabling confirmation. Protected organization ownership must be transferred or removed through the existing organization-owner workflow before submitting closure; the account endpoint does not bypass that audit trail or delete company projects.
Before calling DELETE /v1/users/me, generate a UUID receiptId and a cryptographically random 32-byte base64url receiptSecret, and let the user save them in a recovery file. Send these with policyVersion: 1, confirmation: "close_account" and exportRequested: true or false in the JSON body. Require a login within ten minutes and enrolled MFA, as for the independent export. Send exactly the same body for a retry; do not generate a new receipt when the outcome is uncertain.
Preparation restricts normal account access. A 202 response confirms that the independent erasure ledger and account access barriers are in place; individual cleanup steps may remain pending. If the request times out or returns 503, keep the original recovery receipt and check status. Do not show success or direct the user to restore the account. A 409 with account_closure_owner_action_required requires the organization ownership action before preparation; account_closure_policy_changed requires a fresh preview.
The receipt authorizes only the following routes, without normal session cookies:
| Request | Purpose |
|---|---|
POST /v1/account-closures/{receiptId}/status | Check preparing, accepted or cleanup_complete, cleanup progress and export availability. |
POST /v1/account-closures/{receiptId}/export | Download the frozen personal export before its fixed deadline. |
DELETE /v1/account-closures/{receiptId}/export | Irreversibly erase the optional archive early. |
Send { "receiptSecret": "..." } in the JSON body. Never put the secret in a URL, analytics, referrer or persistent browser storage. Losing the recovery file after leaving the page loses this download capability; it cannot be replaced by logging back into the closed account. The closure scope is profile_and_private_templates: JSON contains schemaVersion: 1, profile (the independent profile export shape), and personalReportTemplates (allowlisted private template content). Organization documents, shared report templates and chats are excluded. An archive larger than 1 MiB is rejected before preparation with account_closure_export_too_large; export private templates separately before choosing closure without a retained archive. It is encrypted with a key derived from the holder's secret; the server stores only a hash and ciphertext, not that key.
The exact exportExpiresAtUtc is fixed at preparation, at most thirty days later. Waiting for processing, retries and downloads never extend it. At expiry or after early erasure, download returns 410. A restored application database cannot override the independently recorded erasure decision or deadline. Account closure does not restore memberships, grants or normal credentials. Retained organization records, permitted recipients' conversation history and restricted historical backups have separate retention and restore controls; cleanup_complete does not claim that every historical physical copy has disappeared.
An authenticated early-erasure request revokes export download immediately. A 204 confirms durable erasure and removal of the live archive. A temporary 503 does not confirm completion: the worker retains the request and retries automatically, and the client may safely retry with the same receipt. A backup outage never extends the archive's original expiry.
Shared user-owned templates referenced by retained reports stay under the deleted-owner record until ownership is resolved. If an older database restore lacks the original archive, status uses exportUnavailableReason: "archive_unavailable_after_restore" and disables download while restore replay remains pending. The saved receipt still authorizes status; it never restores account access.
Legacy user-owned collaboration, chat, upload or search-index records can also remain under the deleted-owner record. Account access closes, while cleanup stays incomplete for operator review of ownership and shared dependencies; the status can show accepted and cleanupStatus: "running" during retries. No automatic deletion is promised. These legacy records are outside the optional archive. Keep the saved receipt to check status.