Skip to main content

Management authentication

Login by email and password. This is the only profile with public self-registration.

EndpointWhat it does
POST /v1/auth/management/registerPublic self-registration, protected by Cloudflare Turnstile. Requires confirming the email before logging in.
POST /v1/auth/management/loginLogin with email/password. Sets wallet_management_access_token, wallet_management_refresh_token (HttpOnly) and wallet_management_csrf_token. Rejects an operator credential even if valid.
POST /v1/auth/management/refreshRotates the refresh token. Accepts the token from the body, the X-Refresh-Token header, or the cookie — in that priority order. Re-issues all three cookies.
POST /v1/auth/management/logoutRevokes the current session and clears all three cookies.

Example: login

curl -X POST http://127.0.0.1:8787/v1/auth/management/login \
-H "Content-Type: application/json" \
-d '{ "email": "manager@example.com", "password": "a-strong-password" }'
POST /v1/auth/management/login { email, password }

receives a body with accessToken/refreshToken
+ Set-Cookie: wallet_management_access_token, wallet_management_refresh_token, wallet_management_csrf_token

subsequent requests: the browser sends the cookies on its own

a state-changing request (POST/PATCH/DELETE) also sends
X-CSRF-Token: <value read from the wallet_management_csrf_token cookie>

A client that prefers Bearer tokens can simply ignore the cookies and use the accessToken from the response body in the Authorization: Bearer ... header — both modes work at the same time, with no conflict.

What the management JWT contains

{
"sub": "supabase-auth-user-id",
"principal_type": "management",
"app_user_id": "global-business-user-id",
"session_id": "global-session-id",
"exp": 1784000000
}

It never contains an organization, role, or permission — that's resolved on every call by looking up app.organization_members by app_user_id. A manager can belong to several organizations with different roles in each; the token doesn't pin that down.