Most scripts should use an API token, not a login. You still need a session (what the console uses) for the few things a token may not do: creating your first personal token, revoking one, or changing account security. This page shows the session side. The samples use the helper from Using the Zaroz API; sign-in itself is unauthenticated.
Sign in with email and password
A correct password does not open a session by itself: it earns a one-time code, emailed to the address. Submit the code to open the session.
curl
Code
curl -sS -X POST "$ZAROZ_API/auth/login" -H "Content-Type: application/json" \ -d '{"email": "[email protected]", "password": "…"}'# {"challenge_id": "…", "status": "…"} the code is now in your inboxcurl -sS -X POST "$ZAROZ_API/auth/login/verify" -H "Content-Type: application/json" \ -d '{"challenge_id": "…", "code": "123456"}'# {"token": "<access token>", "refresh_token": "…", "session_id": "…", "user": {…}}
The code expires quickly and the challenge is burned after a few wrong attempts (start again with /auth/login). Use token as the bearer token. Accounts that sign in with a provider (the list is GET /auth/oauth/providers) use the browser flow instead; password sign-up is POST /auth/password/signup then POST /auth/password/verify, and POST /auth/password/forgot with POST /auth/password/reset recovers a password. These answer identically whether or not the address has an account, so they cannot be used to find out who is registered.
Keep the session alive
The access token is short-lived. Exchange the refresh token for a new pair and use the new refresh token next time: it rotates.
curl
Code
curl -sS -X POST "$ZAROZ_API/auth/refresh" -H "Content-Type: application/json" -d '{"refresh_token": "…"}'# {"token": "…", "refresh_token": "<the NEW one>", "session_id": "…"}curl -sS "$ZAROZ_API/auth/session" -H "$AUTH" # 401 once it is no longer validcurl -sS -X POST "$ZAROZ_API/auth/logout" -H "$AUTH" -H "Content-Type: application/json" -d '{"refresh_token": "…"}' # 204
TypeScript
Code
const next = await post("/auth/refresh", { refresh_token: session.refresh_token });session.token = next.token;session.refresh_token = next.refresh_token; // keep the rotated one; the old one is spent
Python
Code
nxt = requests.post(f"{API}/auth/refresh", json={"refresh_token": session["refresh_token"]}, timeout=20).json()session["token"], session["refresh_token"] = nxt["token"], nxt["refresh_token"] # the old one is spent
Always store the rotated refresh token and never reuse a spent one.
Your sessions
Code
curl -sS "$ZAROZ_API/me/sessions" -H "$AUTH"curl -sS -X DELETE "$ZAROZ_API/me/sessions/$SESSION" -H "$AUTH" # 204; ends every token pair of that session
id is the session family, stable across every rotation (it is the session_id returned at sign-in). Revoking is immediate and never reveals whether an id exists: another person's id is a silent no-op. If a laptop is lost, revoke its session, then revoke the API tokens it could have created.
Changing the password signs every other session out; pass current_session_id to keep this one, or omit it to be signed out everywhere. Every field of PATCH /me is optional (name, language, discord_notifications_enabled, billing_country_code); the billing country is refused once onboarding is complete and a country is already stored, because it decides tax treatment.
Elevated (sudo) sessions
Some actions on the account (staff actions, for example, and the sensitive ones you chose to protect) need an elevated session: a fresh window in which you proved a second factor. Ask whether you need one, then elevate with your PIN.
Code
curl -sS "$ZAROZ_API/auth/sudo/status" -H "$AUTH"# {"is_elevated": false, "elevated_until": null, "available_methods": [...], "enrolled_methods": [...]}curl -sS -X POST "$ZAROZ_API/auth/sudo/elevate" -H "$AUTH" -H "Content-Type: application/json" -d '{"pin": "…"}'# {"token": "<new access token>", "elevated_until": "…"}curl -sS -X POST "$ZAROZ_API/auth/sudo/exit" -H "$AUTH" # drop it early; answers a new, un-elevated token
Code
const status = await call("GET", "/auth/sudo/status");if (!status.body.is_elevated) { const el = await call("POST", "/auth/sudo/elevate", { pin: process.env.SUDO_PIN }); // use el.body.token from now on}
Code
_, status = call("GET", "/auth/sudo/status")if not status["is_elevated"]: _, el = call("POST", "/auth/sudo/elevate", {"pin": os.environ["SUDO_PIN"]}) # use el["token"] from now on
Elevation returns a new access token carrying the window; the token you presented stays valid but un-elevated. A wrong PIN and an account with no PIN are answered identically (five attempts per fifteen minutes). Methods are set up with the /auth/sudo/setup, /auth/sudo/totp/* and /auth/sudo/webauthn/* routes; adding a second method requires an already-elevated session. A scripted job should never hold an elevated session: use a narrowly scoped API token instead.