Rate Limiting
The InfoConnect Hybrid Search Engine uses sliding-window rate limiting to protect authenticated API routes from abuse and to ensure fair usage across clients.
How It Works
Sliding-Window Algorithm
Rate limiting tracks every request in a rolling 60-second window rather than a hard reset every minute. This prevents burst attacks at the top of each fixed window.
What happens on every request:
- Evict old timestamps — drop any request records older than 60 seconds.
- Check the count — if the remaining records in the window are greater than or equal to the limit, the request is blocked with HTTP 429.
- Record and allow — otherwise the current timestamp is stored and the request proceeds.
Example Behavior
With a limit of 2 requests per minute:
| Time | Event | Result | Window State |
|---|---|---|---|
t=0s | Request 1 | Allowed | [0] |
t=1s | Request 2 | Allowed | [0, 1] |
t=2s | Request 3 | Blocked (429) | [0, 1] — count == limit |
t=61s | Request 4 | Allowed | [1, 61] — 0 has aged out |
Because the window slides continuously, a burst at t=59s and t=61s does not allow 4 requests in 2 seconds. The oldest record is evicted only after it is more than 60 seconds old.
Configuration
| Setting | Default | Control |
|---|---|---|
RATE_LIMIT | 30 | Requests per minute for authenticated protected routes |
AUTH_FAILURE_RATE_LIMIT | 10 | Failed authentication attempts per minute per source IP |
Set RATE_LIMIT=0 to disable rate limiting entirely.
Quick Setup
# 30 requests per minute per resolved client id (default)
RATE_LIMIT=30
# 10 failed auth attempts per minute per source (default)
AUTH_FAILURE_RATE_LIMIT=10
# Stricter limit for production
RATE_LIMIT=60
# Disable rate limiting (not recommended for production)
RATE_LIMIT=0
Protected route limits are keyed by resolved client id. Failed-auth limits are keyed by source address and apply before a request can resolve to a client.
Scope
Rate limiting is applied in two layers:
- A protected-route middleware checks all authenticated routes except health, metrics, docs, OpenAPI, and CORS preflight.
POST /searchkeeps an additional search-specific counter because search remains the most frequent expensive operation.
Currently Protected Endpoints
| Endpoint | Rate-Limit Key |
|---|---|
| Protected authenticated routes | protected:{client_id} |
POST /search additional route check | search:{client_id} |
| Failed authentication | auth-failure:{source_ip} |
Unprotected Endpoints
The following endpoints do not check the rate limiter:
GET /health— health checksGET /metrics— Prometheus metrics/docs,/redoc,/openapi.json— development documentation surfacesOPTIONSpreflight requests — handled by CORS middleware
POST /search consumes both the global protected-route budget and a search-specific budget. This preserves backwards-compatible search throttling while protecting upload, status, document, and update routes too.
Response Format
When the limit is exceeded, the API returns:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
X-Request-ID: 5d7f4c1a2b9e4b3e8f6f0d2a4c9b8a1e
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 42
{
"detail": "Rate limit exceeded",
"error_code": "rate_limited",
"request_id": "5d7f4c1a2b9e4b3e8f6f0d2a4c9b8a1e"
}
Response Codes
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized — invalid API key |
| 429 | Rate Limited — too many requests |
| 422 | Validation Error — invalid parameters |
Runtime State
Redis-Backed Sliding Window
When the FastAPI lifespan starts, the built-in limiter stores request timestamps in Redis using an atomic sorted-set script. This means:
- Shared across API workers — all workers enforce the same client counters through
REDIS_URL. - Reset on Redis data loss — counters survive worker restarts, but clearing Redis data clears the 60-second windows.
- No client-supplied tenancy — protected-route counters are keyed by the server-resolved
client_id, and auth-failure counters are keyed by source address.
The in-memory limiter remains only as a test fallback when no Redis client is configured.
Production Recommendations
For normal production Compose deployments, the built-in Redis-backed limiter is sufficient for per-client request caps. For public internet exposure or DDoS protection, layer an edge or reverse-proxy limiter in front of the API as a broader network control.
| Layer | Tool | When to Use |
|---|---|---|
| Built-in | Redis-backed RATE_LIMIT env var | Per-client API quota across API workers |
| Reverse proxy | Nginx limit_req, Traefik middleware | Network-level throttling before requests reach FastAPI |
| Edge / CDN | Cloudflare rate limiting, AWS WAF | Public-facing APIs, DDoS protection |
Testing Rate Limits
You can test the limiter with a quick loop:
for i in {1..35}; do
curl -s -o /dev/null -w "%{http_code}\n" \
-X POST http://localhost:8000/search \
-H "X-API-Key: super-secret-key" \
-H "Content-Type: application/json" \
-d '{"query": "test"}'
done
With the default limit of 30, the first 30 requests for one resolved client should return 200 and later requests within the same 60-second window should return 429, even when the API runs multiple workers.
What's Next
- Review the Search API for endpoint details
- See Configuration for all environment variables
- Read the Architecture Guide to understand how requests flow through the system