Rate limiting
How OpenQuok caps traffic so sign-in, your workspace, and public pages stay reliable.
Connect your agent today
Draft from chat, review in your calendar, and publish only what you approve.
Overview
Rate limiting means OpenQuok counts how many requests someone sends in a time window. A request can be from a signed-in user, an API key, or a visitor IP address.
When the count goes over the cap, the API returns 429 Too Many Requests. The person must wait until the window resets.
Limits protect sign-in, the dashboard, public blog and listing pages, uploads, and integrations. Messages from social networks (webhooks) are not rate limited. Those go server to server.
The table below shows typical production caps. You can change them with environment variables (see Environment variables).
Limits by area
Each row is one rule. One request matches one rule, not every rule at once.
| Area | Who it applies to | Default cap (production) |
|---|---|---|
| Public blog, listings, and similar read pages | Visitors (by IP) | 600 requests per hour |
| Signed-in workspace API | Your account (by user id) | 2000 requests per hour |
| Other anonymous API calls | Visitors (by IP) | 120 requests per hour |
| Sign-in and password routes | By IP | 50 requests per 15 minutes |
| OAuth sign-in (Google and similar) | By IP | 20 requests per 5 minutes |
| Public API (API keys) | Per key, or IP if no key | 30 requests per hour |
| MCP tools endpoint | Token or IP | 120 requests per hour |
| File uploads | Token or IP | 20 requests per hour |
| Feedback form | By IP | 10 requests per hour |
| Connect a social channel | By IP | 30 requests per 15 minutes |
| OAuth token exchange for apps | By IP | 30 requests per 15 minutes |
| Small public write actions (stats, activity) | By IP | 60 requests per hour |
Public read pages are not unlimited. The first row stops heavy scraping from overloading your server.
Visitor IP address
The API needs each visitor’s real IP to count fairly.
If Cloudflare sits in front of OpenQuok, set TRUST_CLOUDFLARE_HEADERS so the API sees the visitor IP, not one shared edge IP. Without that, many real users can look like one person and hit limits too soon.
Optional VERIFY_CLOUDFLARE_IP_RANGE only trusts that header when the request really came through Cloudflare.
Shared counters with Redis
In production, counters usually live in Redis when RATE_LIMIT_REDIS_ENABLED is on. Then every API server shares the same counts.
Redis uses the same host settings as cache (REDIS_HOST, REDIS_PORT). If Redis is off or unreachable at startup, each API instance keeps its own in-memory counts until Redis is available.
Public content cache (related)
Public read routes also send cache headers. Browsers and CDNs can reuse responses for a short time. That lowers load and helps you stay under read limits.
| Content type | Typical behavior |
|---|---|
| List and hub pages (indexes, categories, tags) | Short public cache (about 1 minute) |
| Single pages you edit in secret-admin | No cache — fresh after save |
| RSS feed | Longer cache (about 1 day) |
| Public images | About 1 hour, with a longer stale window |
The public website follows similar rules. For why more than one cache layer exists, see Cache design → Public content and caching.
Tune cache duration with PUBLIC_CMS_CACHE_ENABLED and PUBLIC_CMS_CACHE_*.
Cloudflare and Vercel
Many visitors can share one edge IP if IP headers are wrong. Set TRUST_CLOUDFLARE_HEADERS when you use Cloudflare. Public page caching also reduces how often every visit hits your API.
When something is blocked
Search API logs for Rate limit exceeded. Each line shows which rule fired, the IP, the path, and the cap.
If real users hit limits, raise the cap for that area (public read or signed-in session). Lower caps only when you see abuse.
Environment variables
Set RATE_LIMIT_ENABLED to false to turn off all limiters. Do not do this in production.
Copy backend/.env.development.example to backend/.env.development.local for sample values when you test locally:
# From repo root — example only; adjust values for your machine
cp backend/.env.development.example backend/.env.development.local | Group | What you change |
|---|---|
| Master switch | RATE_LIMIT_ENABLED |
| Visitor IP | TRUST_CLOUDFLARE_HEADERS, VERIFY_CLOUDFLARE_IP_RANGE |
| Public read pages | PUBLIC_READ_RATE_LIMIT_WINDOW_MS, PUBLIC_READ_RATE_LIMIT_MAX |
| Signed-in workspace | SESSION_RATE_LIMIT_WINDOW_MS, SESSION_RATE_LIMIT_MAX |
| Other anonymous API | RATE_LIMIT_WINDOW_MS, RATE_LIMIT_MAX |
| Sign-in and OAuth | AUTH_RATE_LIMIT_*, OAUTH_RATE_LIMIT_* |
| Public API and MCP | PUBLIC_API_RATE_LIMIT_*, MCP_RATE_LIMIT_* |
| Uploads and feedback | UPLOAD_RATE_LIMIT_*, FEEDBACK_RATE_LIMIT_* |
| Channel connect and app tokens | INTEGRATION_CONNECT_RATE_LIMIT_*, OAUTH_TOKEN_RATE_LIMIT_* |
| Public writes | PUBLIC_WRITE_RATE_LIMIT_* |
| Redis store | RATE_LIMIT_REDIS_ENABLED, RATE_LIMIT_REDIS_PREFIX, RATE_LIMIT_REDIS_DB |
| Public page cache | PUBLIC_CMS_CACHE_ENABLED, PUBLIC_CMS_CACHE_MAX_AGE, PUBLIC_CMS_CACHE_STALE_WHILE_REVALIDATE, PUBLIC_CMS_RSS_CACHE_MAX_AGE, PUBLIC_CMS_IMAGE_CACHE_* |
Example production-related values (full list is in the example env file):
RATE_LIMIT_ENABLED=true
RATE_LIMIT_REDIS_ENABLED=true
TRUST_CLOUDFLARE_HEADERS=true
PUBLIC_READ_RATE_LIMIT_MAX=600
SESSION_RATE_LIMIT_MAX=2000 Production checklist
Deploy
Run the API in production mode (NOT_SECURED set to false) with Redis configured. After deploy, confirm logs show Redis for rate limits when RATE_LIMIT_REDIS_ENABLED is on. Watch Rate limit exceeded in logs. Raise caps if real users hit limits. If many visitors see errors on a public page, check public read limits and Cloudflare IP settings.
Workers and the web app do not need these variables. See Production deployment.