Skip to main content
Rate limits are enforced at the Vercel Edge runtime using Upstash Redis sliding-window counters. All limits are sliding 60-second windows unless noted.

Default public API rate limit

ScopeLimitWindow
Per IP (default)600 requests60 s
Applies to all /api/* routes that don’t have a stricter override. Implemented by api/_rate-limit.js / api/_ip-rate-limit.js.

MCP server

ScopeLimitWindow
Per API key (MCP tools)60 requests60 s
See MCP for details.

OAuth endpoints

EndpointLimitWindowScope
POST /api/oauth/register560 sPer IP
GET /api/oauth/authorize1060 sPer IP
POST /api/oauth/token1060 sPer credential / client / IP fallback
Matches the implementations in api/oauth/register.js, api/oauth/authorize.js, and api/oauth/token.ts. For /api/oauth/token, the limiter key is client_secret hash for client_credentials, then client_id when present, and only falls back to caller IP when neither credential identifier is available. Exceeding any of these during the OAuth flow will cause the MCP client to fail the connection handshake — wait 60 s and retry.

Write endpoints

EndpointLimitWindowScope
POST /api/scenario/v1/run-scenario1060 sPer IP
POST /api/scenario/v1/run-scenario (queue depth)100 in-flightGlobal
POST /api/leads/v1/register-interest560 minPer IP + Turnstile (desktop sources require signed HMAC bypass)
POST /api/leads/v1/submit-contact360 minPer IP + Turnstile
Other write endpoints (/api/brief/share-url, /api/notification-channels, /api/create-checkout, /api/customer-portal, etc.) fall back to the default per-IP limit above.

Bootstrap / health / version

These have no custom limit beyond the default. Cache headers vary by endpoint:
  • GET /api/bootstrap — default all-keys response uses Cache-Control: public, s-maxage=600, stale-while-revalidate=120, stale-if-error=900 plus fast-tier CDN-Cache-Control; explicit ?tier=fast / ?tier=slow requests use browser max-age=60 / max-age=300 and CDN s-maxage=600 / s-maxage=7200.
  • GET /api/healthprivate, no-store, max-age=0 plus CDN-Cache-Control: no-store.
  • GET /api/versionpublic, s-maxage=300, stale-while-revalidate=60, stale-if-error=3600.

Response when limited

HTTP 429 with:
Retry-After: <seconds>
Content-Type: application/json

{ "error": "Too many requests" }

Retry guidance

  • Respect Retry-After. Don’t pound on a 429.
  • For batch work, pace yourself: at 600 req/min/IP the default gives you ~10 req/s headroom.
  • For MCP, 60/min is generous for conversational use but tight for scripted batch fetches — prefer the REST API for batch.
  • Spurious 429s often mean you’re sharing an egress IP (corporate proxy, CI runner). Contact support for a per-key limit bump if needed.

Hard caps (not soft limits)

  • Webhook callback URLs must be HTTPS (except localhost).
  • api/download file sizes capped at ~50 MB per request.
  • POST /api/scenario/v1/run-scenario globally pauses new jobs when the pending queue exceeds 100 — returns 429.
  • api/v2/shipping/webhooks TTL is 30 days — re-register to extend.