The Causeloop API enforces a sliding-window rate limit per client IP. All requests — authenticated or not — count against the limit for the originating IP address.
Limits
| Window | Max requests |
|---|
| 60 seconds | 600 |
The limit is applied per IP address (or the first value of X-Forwarded-For when the request passes through a proxy). Requests from different IPs are counted independently.
When Redis is configured (REDIS_URL), the limit is enforced across all API replicas using a shared fixed-window counter. Without Redis, the counter is per-process — a single IP hitting multiple replicas gets a higher effective limit. In production, configure Redis for consistent enforcement.
Every response — including successful ones — carries two rate limit headers:
| Header | Description |
|---|
X-RateLimit-Limit | Maximum requests allowed in the 60-second window. |
X-RateLimit-Remaining | Requests remaining in the current window. |
When the limit is exceeded, the 429 response also includes:
| Header | Description |
|---|
Retry-After | Seconds to wait before the next window opens. |
X-RateLimit-Remaining | Always 0 on a 429. |
429 response body
{
"error": {
"code": "rate_limited",
"message": "Too many requests"
}
}
The 429 error body does not include a trace_id. Use the Retry-After header value, not a fixed delay, to determine how long to wait.
Handling rate limits
Read the X-RateLimit-Remaining header on every response. When it reaches zero or you receive a 429, back off and retry:
# Read headers from any response
curl -I https://api.causeloop.ai/v1/issues \
-H "Authorization: Bearer $CAUSELOOP_TOKEN"
# HTTP/2 200
# x-ratelimit-limit: 600
# x-ratelimit-remaining: 598
Best practices
Read X-RateLimit-Remaining proactively. If it’s low, slow down your request rate before hitting the limit.
Implement exponential backoff with jitter. Start at the Retry-After value, then double the delay on each subsequent 429. Add random jitter (±500 ms) to avoid thundering-herd when multiple clients retry in sync.
Avoid polling for status. Use the WebSocket stream for real-time updates — WebSocket messages do not count against REST rate limits. For endpoints that do require polling:
| Endpoint | Safe poll interval |
|---|
GET /v1/reports/jobs/{id} | Every 5 seconds |
GET /v1/exports/{id} | Every 10 seconds |
GET /v1/me/notifications/unread-count | Every 30 seconds |
GET /v1/shell/nav | On app focus, not on a timer |
Batch requests where possible. Filter lists server-side (?status=open) rather than fetching all records and filtering client-side.