Skip to content

Authentication

TypeWhoLifetimeWhere used
Session cookieUsers (dashboard)7 days absolute, 30 min sliding idleBrowser
API keyUsers (CLI, scripts)No expiry; revocableAuthorization: Bearer z4k_...
Agent token + HMAC secretAgentsNo expiry; revocableWebSocket handshake (token) + frame signing (HMAC secret)
POST /api/v1/auth/login
Content-Type: application/json
{ "email": "...", "password": "..." }

Response sets z4j_session=...; HttpOnly; Secure; SameSite=Lax. Subsequent requests send the cookie automatically. The session is rejected after Z4J_SESSION_ABSOLUTE_LIFETIME_SECONDS (default 604800 = 7 days) or after Z4J_SESSION_IDLE_TIMEOUT_SECONDS (default 1800 = 30 minutes) of inactivity, whichever fires first.

POST /api/v1/auth/logout

Invalidates the session server-side.

EndpointPurpose
GET /api/v1/auth/meCurrent user + project memberships.
PATCH /api/v1/auth/meUpdate display name etc.
POST /api/v1/auth/change-passwordAuthenticated password change.
GET /api/v1/auth/sessionsList the user’s active sessions.
POST /api/v1/auth/sessions/{session_id}/revokeRevoke a single session.
POST /api/v1/auth/password-reset/request # public; body: {"email": "..."}
POST /api/v1/auth/password-reset/confirm # public; body: {"token": "...", "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:

EndpointPurpose
GET /api/v1/api-keysList the caller’s keys (prefix + scopes only; plaintext never re-emitted).
POST /api/v1/api-keysMint a new key. Response carries the plaintext once.
DELETE /api/v1/api-keys/{key_id}Revoke a key.
GET /api/v1/api-keys/scopesCatalog of valid scopes.

API keys can be project-scoped (bound to one project’s slug at mint time) or unscoped. A project-scoped key automatically filters GET /projects to that one project and is rejected on cross-project paths.

Minted via the agents APIPOST /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, 1-minute window)

Section titled “Rate limits on auth (per IP, 1-minute window)”
EndpointCap
POST /auth/login20 hits / minute
POST /auth/password-reset/request and /confirm10 hits / minute (combined bucket)
GET /invitations/preview and POST /invitations/accept30 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.

Not currently shipped. The recommended interim approach is to 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.