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:
| Route | Internal key |
|---|---|
POST /auth/login | auth.login |
POST /auth/register-organization | auth.register |
POST /analysis/validate and POST /rulesets/compile | analysis.validate |
POST /analysis/logtest-native | analysis.logtest |
POST /…/webhooks/{id}/test | webhook.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:
| Window | Default limit | Environment variable |
|---|---|---|
| Per minute | 120 requests | NATIVE_WAZUH_API_RATE_LIMIT_PER_MINUTE |
| Per hour | 5,000 requests | NATIVE_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.
Recommended strategy
- Read
X-RateLimit-Remainingproactively. If near 0, slow down before getting blocked. - Handle 429 explicitly. Respect
Retry-After; don't implement aggressive retry without waiting. - Use exponential backoff for
5xxerrors, but not for429— thereRetry-Afteralready defines the interval. - 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.
Related links
- Errors — error envelope including
rate_limited. - Authentication
- Billing and plans