Skip to content

Rate limiting

Configure backend rate limiting (public read, session, global, auth, public API, uploads, and other route-specific limits) for OpenQuok.

4 min read

Connect your agent today

Draft from chat, review in your calendar, and publish only what you approve.

Start for $0

Overview

The backend uses express-rate-limit. Route rules live in backend/middlewares/publicRouteRegistry.ts. Limits and auth skip rules use the same registry.

LimiterScopeKeyDefault (production)
publicReadPublic CMS GETs (/company/*, /blog-system/*, /listings/*, allowlisted GET /image/download)Trusted client IP600 / hr
sessionAuthenticated API under API_PREFIXJWT sub2000 / hr
globalOther anonymous routesTrusted client IP120 / hr
auth/auth (not /oauth/*)IP50 / 15 min
oauth/auth/oauth/*IP20 / 5 min
publicApi/public/*opo_ token or IP30 / hr
mcp/mcpBearer, path token, or IP120 / hr
upload/media/upload*, /public/upload*Token or IP20 / hr
feedbackPOST /feedbackIP10 / hr
integrationConnectOAuth connect under /integrationsIP30 / 15 min
oauthTokenPOST /oauth/tokenIP30 / 15 min
publicWriteListing stats, blog activity, conversion trackingIP60 / hr

Config: backend/middlewares/rateLimit.ts, backend/config/GlobalConfig.ts.

Client IP

backend/middlewares/trustedClientIp.ts picks the IP for keys and 429 logs:
  • TRUST_CLOUDFLARE_HEADERS — use CF-Connecting-IP instead of req.ip (default true in production when NOT_SECURED is false).
  • VERIFY_CLOUDFLARE_IP_RANGE — use CF-Connecting-IP only when req.ip is a Cloudflare edge IP.

Redis store

When RATE_LIMIT_REDIS_ENABLED is true (production default), all limiters use Redis. They share REDIS_HOST and REDIS_PORT with cache and BullMQ. Set RATE_LIMIT_REDIS_PREFIX and optional RATE_LIMIT_REDIS_DB to tune keys. If Redis is off or down at startup, counters stay in memory per API instance.

CMS cache headers

Public CMS GETs (same routes as publicRead) get Cache-Control from backend/middlewares/publicCmsCacheHeaders.ts:

Route kindDefault header
CMS JSONpublic, max-age=60, stale-while-revalidate=300
GET /blog-system/rsspublic, max-age=86400
Public imagespublic, max-age=3600, stale-while-revalidate=86400

Anonymous public HTML uses matching hints in web/src/lib/seo/publicCmsPageCache.ts. Tune with PUBLIC_CMS_CACHE_ENABLED and PUBLIC_CMS_CACHE_* (on by default when NOT_SECURED is false).

429 logs

Search logs for Rate limit exceeded. Fields: limiter, trustedClientIp, userId, path, method, windowMs, max.

Environment variables

Set RATE_LIMIT_ENABLED to false to disable all limiters.

GroupVariables
Master switchRATE_LIMIT_ENABLED
Client IPTRUST_CLOUDFLARE_HEADERS, VERIFY_CLOUDFLARE_IP_RANGE
Public readPUBLIC_READ_RATE_LIMIT_WINDOW_MS, PUBLIC_READ_RATE_LIMIT_MAX
SessionSESSION_RATE_LIMIT_WINDOW_MS, SESSION_RATE_LIMIT_MAX
GlobalRATE_LIMIT_WINDOW_MS, RATE_LIMIT_MAX
Auth / OAuthAUTH_RATE_LIMIT_*, OAUTH_RATE_LIMIT_*
Public API / MCPPUBLIC_API_RATE_LIMIT_*, MCP_RATE_LIMIT_*
Upload / feedbackUPLOAD_RATE_LIMIT_*, FEEDBACK_RATE_LIMIT_*
IntegrationsINTEGRATION_CONNECT_RATE_LIMIT_*, OAUTH_TOKEN_RATE_LIMIT_*
Public writesPUBLIC_WRITE_RATE_LIMIT_*
Redis storeRATE_LIMIT_REDIS_ENABLED, RATE_LIMIT_REDIS_PREFIX, RATE_LIMIT_REDIS_DB
CMS cachePUBLIC_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 (development)

Copy backend/.env.development.example to backend/.env.development.local and adjust:

RATE_LIMIT_ENABLED=true

# Global (session / dashboard API)
RATE_LIMIT_WINDOW_MS=3600000
RATE_LIMIT_MAX=60

# Auth
AUTH_RATE_LIMIT_WINDOW_MS=900000
AUTH_RATE_LIMIT_MAX=50

# OAuth (Google)
OAUTH_RATE_LIMIT_WINDOW_MS=300000
OAUTH_RATE_LIMIT_MAX=20

# Public API (per opo_ token)
PUBLIC_API_RATE_LIMIT_WINDOW_MS=3600000
PUBLIC_API_RATE_LIMIT_MAX=30

# Uploads
UPLOAD_RATE_LIMIT_WINDOW_MS=3600000
UPLOAD_RATE_LIMIT_MAX=20

# Feedback
FEEDBACK_RATE_LIMIT_WINDOW_MS=3600000
FEEDBACK_RATE_LIMIT_MAX=10

# Integration connect + OAuth token exchange
INTEGRATION_CONNECT_RATE_LIMIT_WINDOW_MS=900000
INTEGRATION_CONNECT_RATE_LIMIT_MAX=30
OAUTH_TOKEN_RATE_LIMIT_WINDOW_MS=900000
OAUTH_TOKEN_RATE_LIMIT_MAX=30

# Anonymous public writes
PUBLIC_WRITE_RATE_LIMIT_WINDOW_MS=3600000
PUBLIC_WRITE_RATE_LIMIT_MAX=60

Production deploy

  1. Deploy the API with NOT_SECURED set to false and Redis configured.
  2. Check startup logs for store: redis when RATE_LIMIT_REDIS_ENABLED is on.
  3. Watch Rate limit exceeded logs. Raise PUBLIC_READ_RATE_LIMIT_MAX or SESSION_RATE_LIMIT_MAX for real traffic. Lower caps only when you confirm abuse.
  4. If a public page returns 503, check for limiter: ”publicRead” in API logs. Confirm CF-Connecting-IP behind Cloudflare and Cache-Control on CMS GETs.

Workers and the web app do not need rate-limit env vars. See Production deployment.

Search documentation
Find a docs page
Discord Support