Skip to content

Authentication

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)
POST /api/v1/auth/login
Content-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.

POST /api/v1/auth/logout

Requires the CSRF token, invalidates the session server-side, and clears both session cookies.

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.
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.

Create from Dashboard, Settings, API keys. Tokens begin with z4k_ and are shown once at creation.

Terminal window
curl -H "Authorization: Bearer z4k_..." \
https://z4j.example.com/api/v1/projects/billing-prod/tasks

Management 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.

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.

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.