Authentication
Three credential types
Section titled “Three credential types”| Type | Who | Lifetime | Where used |
|---|---|---|---|
| Session cookie | Users (dashboard) | Normally 7 days absolute / 30 min idle; remembered sessions are 30 days and bypass idle expiry | Browser |
| API key | Users (CLI, scripts) | Optional expiry; revocable | Authorization: Bearer z4k_... |
| Agent token + HMAC secret | Agents | No expiry; revocable | WebSocket handshake (token) + frame signing (HMAC secret) |
Login (session)
Section titled “Login (session)”POST /api/v1/auth/loginContent-Type: application/json
{ "email": "...", "password": "..." }In production the response sets __Host-z4j_session (HttpOnly, Secure,
SameSite=Lax) plus a JavaScript-readable __Host-z4j_csrf cookie (Secure,
SameSite=Strict). Development uses the unprefixed names and permits insecure
localhost cookies. A normal session is rejected after
Z4J_SESSION_ABSOLUTE_LIFETIME_SECONDS (default 7 days) or
Z4J_SESSION_IDLE_TIMEOUT_SECONDS (default 30 minutes), whichever comes first.
When login sends "remember_me": true, the session instead uses
Z4J_SESSION_REMEMBER_ME_LIFETIME_SECONDS (default 30 days) and bypasses idle
expiry.
Logout
Section titled “Logout”POST /api/v1/auth/logoutRequires the CSRF token, invalidates the session server-side, and clears both session cookies.
Other session endpoints
Section titled “Other session endpoints”| Endpoint | Purpose |
|---|---|
GET /api/v1/auth/me |
Current user + project memberships. |
PATCH /api/v1/auth/me |
Update display name etc. |
POST /api/v1/auth/change-password |
Authenticated password change. |
GET /api/v1/auth/sessions |
List the user's active sessions. |
POST /api/v1/auth/sessions/{session_id}/revoke |
Revoke one of the caller's sessions; CSRF-protected. |
POST /api/v1/auth/sessions/revoke-others |
Revoke every session the caller holds except the current one; CSRF-protected. |
GET /api/v1/auth/policy |
The active password policy, for client-side pre-validation. |
Password reset
Section titled “Password reset”POST /api/v1/auth/password-reset/request # public; body: {"email": "..."}POST /api/v1/auth/password-reset/confirm # public; body: {"token": "...", "new_password": "..."}The reset token TTL is 30 minutes. The request endpoint never reveals whether the email exists -- it returns success either way.
API keys
Section titled “API keys”Create from Dashboard, Settings, API keys. Tokens begin with z4k_ and are shown once at creation.
curl -H "Authorization: Bearer z4k_..." \ https://z4j.example.com/api/v1/projects/billing-prod/tasksManagement endpoints:
| Endpoint | Purpose |
|---|---|
GET /api/v1/api-keys |
List key metadata: id, name, prefix, scopes, project binding, expiry, revocation, creation, and last-use fields. Plaintext is never re-emitted. |
POST /api/v1/api-keys |
Mint a new key. Response carries the plaintext once. |
DELETE /api/v1/api-keys/{key_id} |
Revoke a key. |
GET /api/v1/api-keys/scopes |
Catalog of valid scopes. |
API keys can be project-scoped (bound to one project) or unscoped. A
project-scoped key filters GET /projects to its bound project and is rejected
on a different project's slug. This is not a complete method-level boundary:
for example, a global-admin-owned project-scoped key with projects:write can
still create a project because /projects is on the non-slug allow-list.
Agent tokens
Section titled “Agent tokens”Minted via the agents API -- POST /api/v1/projects/{slug}/agents returns both a bearer token (WebSocket handshake) and an hmac_secret (per-frame signing). Agents refuse to start without the HMAC secret.
Rate limits on auth (per IP and per brain process, 1-minute window)
Section titled “Rate limits on auth (per IP and per brain process, 1-minute window)”| Endpoint | Cap |
|---|---|
POST /auth/login |
20 hits / minute |
POST /auth/password-reset/request and /confirm |
10 hits / minute (combined bucket) |
POST /invitations/preview and POST /invitations/accept |
30 hits / minute (combined bucket) |
Per-account lockout runs in parallel with the per-IP login cap: repeated wrong passwords on a single account lock that account for a cooling-off window regardless of source IP. See security rate limits for the design rationale.
The three IP buckets are held in process memory. With multiple serve workers, each worker has its own bucket, so the aggregate requests accepted by the deployment can exceed the table's nominal caps.
Multi-factor authentication is built in: a TOTP second factor from any standard authenticator app, ten single-use recovery codes, and an opt-in remember-this-device cookie. Sensitive actions require a fresh second-factor step-up. Org-wide enforcement is available and off by default, with a per-user grace window. See multi-factor authentication and MFA enforcement.
SSO / OAuth2
Section titled “SSO / OAuth2”z4j has no SSO or OAuth2 login. Put z4j behind an authenticated reverse proxy (oauth2-proxy, Cloudflare Access, Pomerium) so the SSO layer authenticates the user before they reach z4j's login form.