Rate limits
z4j applies in-process sliding-window limits to public auth paths, agent ingress, expensive write paths, OpenAPI generation, and the activity feed. Most authenticated read paths are uncapped, but authenticated status alone does not bypass a limiter.
Per-IP buckets
Section titled “Per-IP buckets”Except where noted, these are the in-code defaults from z4j_brain.domain.ip_rate_limit and use a 60-second window. Each worker process has independent counters.
| Endpoint | Cap (per IP) |
|---|---|
POST /api/v1/auth/login |
20 / min |
POST /api/v1/auth/password-reset/request and /confirm |
10 / min (shared bucket) |
POST /api/v1/invitations/preview and POST /api/v1/invitations/accept |
30 / min (shared bucket) |
| Bulk writes (task bulk-delete, command bulk-retry and purge, durable bulk-retry creation, schedule trigger and resync, project channel and default-subscription creation) | 10 / min (shared bucket) |
| Notification-channel test endpoints | 20 / min (shared project/user bucket) |
| Notification-channel import endpoints | 30 / min (shared project/user bucket) |
| Agent WebSocket connect and agent long-poll/event HTTP endpoints | 600 / min (shared bucket) |
| OpenAPI JSON and docs endpoints | 10 / min (shared bucket, even when authenticated) |
| MFA verification endpoints | 10 / min by default; configured by Z4J_MFA_VERIFICATION_RATE_PER_MIN |
POST /api/v1/setup/complete uses a separate 5 requests per 15 minutes per-IP bucket, followed by a database-backed attempt budget documented in rate limits (security). The authenticated activity feed has its own 60 requests per minute per-user, per-worker counter.
HTTP limiters return 429 Too Many Requests. An over-limit agent WebSocket upgrade is closed with code 4429 instead.
Per-account login lockout (not per-IP)
Section titled “Per-account login lockout (not per-IP)”In addition to the per-IP login bucket, the brain tracks failed logins per account:
| Setting | Default | Meaning |
|---|---|---|
Z4J_LOGIN_LOCKOUT_THRESHOLD |
10 |
Failed attempts before lockout. |
Z4J_LOGIN_LOCKOUT_DURATION_SECONDS |
900 (15 min) |
How long the lockout lasts. |
Z4J_LOGIN_BACKOFF_BASE_SECONDS |
0.5 |
Deprecated compatibility value; the login path does not read it. |
Z4J_LOGIN_BACKOFF_MAX_SECONDS |
5.0 |
Deprecated compatibility value; the login path does not read it. |
Z4J_LOGIN_MIN_DURATION_MS |
300 |
Minimum response floor. It reduces timing differences but is not a whole-request constant-time guarantee. |
The lockout is per-account, so an attacker rotating IPs to evade the per-IP cap still hits the account-level wall.
Response headers
Section titled “Response headers”The brain does not emit X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset. Detect throttling via the 429 status alone.
Tuning
Section titled “Tuning”Tunables for login lockout, MFA verification, and the durable setup budget live in Settings (see above and the env-vars reference). The other HTTP per-IP bucket sizes are in-code defaults; changing them requires a code change.
Bypass
Section titled “Bypass”/health and /metrics are not rate-limited; scraping is expected. /metrics still applies its configured authentication policy.