---
title: "Rate Limits"
description: "Per-endpoint, per-key, and per-IP rate limits across the WorldMonitor API surface."
---

Rate limits are enforced at the Vercel Edge runtime using Upstash Redis counters. All limits are **sliding 60-second windows** unless noted.

## Default public API rate limit

| Scope | Limit | Window |
|-------|-------|--------|
| Per IP (default) | **600 requests** | 60 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

| Scope | Limit | Window |
|-------|-------|--------|
| Per API key (MCP tools) | **60 requests** | 60 s |

See [MCP](/mcp-overview) for details.

## Per-plan API rate limits

Authenticated REST API keys (`wm_…`) are limited **per account**, not per IP — a key behind a shared egress IP is not throttled by other tenants' traffic, and all of an account's keys share one allowance.

| Plan | Per-minute (burst) | Daily included | Beyond the daily allowance |
|------|--------------------|----------------|----------------------------|
| **API Starter** | **60** / 60 s | **1,000** / UTC day | metered — hard stop at 10,000 / day |
| **API Business** | **300** / 60 s | **10,000** / UTC day | metered — hard stop at 100,000 / day |
| **Enterprise** | **1,000** / 60 s | unlimited | — |

- **Per-minute** is a hard burst limit — exceeding it returns 429 immediately.
- **Daily included** is your plan's allowance; it resets at **00:00 UTC**. Requests beyond it are **metered, not rejected** — usage-based overage billing is rolling out. Until then a safety ceiling (10× the included allowance) caps runaway usage with a 429.
- The per-minute burst, daily allowance, and ceiling are all **per account** (shared across all of an account's `wm_…` keys), so issuing more keys does not raise your limit. (Operator-issued Enterprise keys are the exception — each is rate-limited independently.)
- Need a higher limit? **Contact support** to raise your plan's allowance.

## OAuth endpoints

| Endpoint | Limit | Window | Scope |
|----------|-------|--------|-------|
| `POST /api/oauth/register` | 5 | 60 s | Per IP |
| `GET /api/oauth/authorize` | 10 | 60 s | Per IP |
| `POST /api/oauth/token` | 10 | 60 s | Per 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

| Endpoint | Limit | Window | Scope |
|----------|-------|--------|-------|
| `POST /api/scenario/v1/run-scenario` | 10 | 60 s | Per IP |
| `POST /api/scenario/v1/run-scenario` (queue depth) | 100 in-flight | — | Global |
| `POST /api/leads/v1/register-interest` | 5 | 60 min | Per IP + Turnstile (desktop sources require signed HMAC bypass) |
| `POST /api/leads/v1/submit-contact` | 3 | 60 min | Per 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 mostly use the default public API limit. Cache headers vary by endpoint:

- `GET /api/bootstrap` — browser/session responses keep the default cache policy: all-keys responses use `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`. Key-authenticated responses use `Cache-Control: no-store` and do not emit CDN cache headers. User API key validation also has a fail-closed fixed 60 s per-IP pre-validation limit of 600 attempts.
- `GET /api/health` — `private, no-store, max-age=0` plus `CDN-Cache-Control: no-store`.
- `GET /api/version` — `public, s-maxage=300, stale-while-revalidate=60, stale-if-error=3600`.

## Response when limited

HTTP 429 with the standard rate-limit headers so you can self-throttle:

```
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: <limit>
X-RateLimit-Remaining: 0
X-RateLimit-Reset: <reset, ms since epoch>
Retry-After: <seconds>
Content-Type: application/json

{ "error": "Too many requests" }
```

For a daily-ceiling 429 the `Retry-After` counts down to the next 00:00 UTC.

## 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.
