Skip to main content

Limits and usage

RuleForge protects the platform and the customer's plan with rate limiting on sensitive routes. This page documents the limits, the headers you receive and what to do when you are blocked.

How the limit works

Rate limiting is applied per sensitive route and per authenticated subject (API key, user or IP for unauthenticated). Windows are dual: per minute and per hour.

Monitored routes:

RouteInternal key
POST /auth/loginauth.login
POST /auth/register-organizationauth.register
POST /analysis/validate and POST /rulesets/compileanalysis.validate
POST /analysis/logtest-nativeanalysis.logtest
POST /…/webhooks/{id}/testwebhook.test

Routes outside this list have no per-route limit but are still subject to the organization's global limit defined by the billing plan.

Default limits

Server default values:

WindowDefault limitEnvironment variable
Per minute120 requestsNATIVE_WAZUH_API_RATE_LIMIT_PER_MINUTE
Per hour5,000 requestsNATIVE_WAZUH_API_RATE_LIMIT_PER_HOUR

Commercial plans may have higher or lower limits — check GET /billing/plans/public or the Billing and plans page.

Informational headers

Every response on a rate-limited route carries, on success:

X-RateLimit-Limit: 120
X-RateLimit-Remaining: 117
X-RateLimit-Reset: 2026-04-24T15:31:00Z
  • X-RateLimit-Limit: configured quota for the 1-minute window.
  • X-RateLimit-Remaining: how many calls are still allowed before reset.
  • X-RateLimit-Reset: ISO 8601 UTC — when the counter resets.

When you get blocked

429 rate_limited response:

{
"code": "rate_limited",
"message": "Rate limit exceeded.",
"hint": "Wait for the reset shown and try again.",
"details": {
"route": "analysis.validate",
"reset_at": "2026-04-24T15:31:00Z"
},
"debug_id": "..."
}

Extra headers:

Retry-After: 30
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0

Retry-After is in seconds and is the only field you need to watch to react.

  1. Read X-RateLimit-Remaining proactively. If near 0, slow down before getting blocked.
  2. Handle 429 explicitly. Respect Retry-After; don't implement aggressive retry without waiting.
  3. Use exponential backoff for 5xx errors, but not for 429 — there Retry-After already defines the interval.
  4. Batch expensive calls (like analysis/full). Running thousands of them in parallel is counterproductive.

Minimal example in Python:

import time, httpx

def call_with_retry(client, method, url, **kwargs):
for _ in range(5):
resp = client.request(method, url, **kwargs)
if resp.status_code != 429:
return resp
wait = int(resp.headers.get("Retry-After", "30"))
time.sleep(wait)
resp.raise_for_status()

Cross-cutting limits

Beyond per-route, there are cross-cutting limits:

Payload size

Maximum of 8 MB per request (NATIVE_WAZUH_MAX_REQUEST_BYTES=8388608). When exceeded, the response is 413 payload_too_large before the body is even read.

Organization quota (plan)

Some operations (analysis runs, case runs) consume plan units. When the balance hits zero, the server returns 403 with a specific code (plan_quota_exceeded). Current consumption is at GET /platform/organizations/{id}/billing.

SSE connections

Streams (POST /ai/stream, GET /notifications/stream) have a simultaneous connection limit per user. Hit: 429 with Retry-After.

Login throttle

POST /auth/login is limited to 5 attempts in 15 minutes per (email + IP) pair. POST /auth/register-organization is limited to 10 attempts per hour per IP. Exceeded: 429 with Retry-After.

Enable/disable (operator)

The instance operator may disable global rate limiting with NATIVE_WAZUH_API_RATE_LIMIT_ENABLED=false. Only for controlled environments (internal test/CI) — should stay on in production.