WorldMonitor has four authentication modes. Which one applies depends on how you’re calling.
Auth matrix
| Mode | Header | Used by | Trusted on which endpoints? |
|---|
| Browser session | wm-session HttpOnly cookie | Dashboard browser reads | Public endpoints that do not set forceKey: true. |
| API key | X-WorldMonitor-Key: wm_0123456789abcdef0123456789abcdef01234567 | Server-to-server, scripts, SDKs | User API keys cover entitled API access; operator-issued enterprise keys cover internal/partner access. |
| OAuth bearer | Authorization: Bearer <oauth-token> | MCP clients (Claude, Cursor, Inspector) | /api/mcp. The handler also accepts a direct X-WorldMonitor-Key in lieu of an OAuth token — see MCP. |
| Clerk session JWT | Authorization: Bearer <clerk-jwt> | Authenticated browser users | User-specific routes: /api/latest-brief, /api/user-prefs, /api/notification-channels, /api/brief/share-url, etc. |
forceKey: true — which endpoints ignore browser session cookies?
Some endpoints explicitly reject anonymous browser session cookies and require a user API key, enterprise API key, or Pro Clerk bearer even from inside the dashboard:
/api/v2/shipping/route-intelligence
/api/v2/shipping/webhooks
/api/widget-agent
- Vendor / partner endpoints
For these, you must send an API key; X-WorldMonitor-Key is the canonical header.
Browser session mode
CORS decides whether a browser is allowed to read the response, but Origin is not authentication. Browser public reads authenticate with a short-lived wms_ session token minted by /api/wm-session and carried in the wm-session HttpOnly cookie.
- Allowed origins get
Access-Control-Allow-Origin: <echoed> and can use credentialed browser cookies.
- Disallowed origins are rejected by the edge function guard before the route body runs.
- Requests with no
Origin header, such as curl or server-to-server calls, are not blocked by CORS; they still need the route’s normal credentials.
See CORS for the origin patterns.
A Cloudflare Worker (api-cors-preflight) is the authoritative CORS handler for api.worldmonitor.app — it overrides _cors.js and vercel.json. If you’re changing origin rules, change them in the Cloudflare dashboard.
API key mode
Generate a key
PRO subscribers get a key automatically on subscription. To rotate, contact support.
Use it
X-WorldMonitor-Key: wm_0123456789abcdef0123456789abcdef01234567
User-issued keys are exactly wm_ followed by 40 lowercase hex characters. Enterprise keys are opaque operator-issued strings and are only distributed out of band. Keep keys out of client-side code — use a server-side proxy if you need to call from the browser to a forceKey endpoint.
X-WorldMonitor-Key is the canonical header. API-key-authenticated endpoints also accept X-Api-Key as an alias for compatibility with generic API clients, including standalone edge functions that use validateApiKey() and gateway-backed routes.
Server-side validation
The edge function calls validateApiKey(req, { forceKey?: boolean }):
- Desktop origins must send an enterprise key in
X-WorldMonitor-Key.
- If
forceKey is false, a valid wms_ browser session cookie satisfies the anonymous/public gate.
- Enterprise keys are checked against
WORLDMONITOR_VALID_KEYS.
- User keys with the
wm_ + 40-hex shape are validated by the gateway against the user-key table and entitlement cache (invalidate-user-api-key-cache flushes this).
- If none passes → 401.
OAuth bearer (MCP only)
Full flow documented at OAuth 2.1 Server. For client setup, see MCP.
Clerk session (authenticated dashboard)
The dashboard exchanges Clerk’s __session cookie for a JWT and sends it on user-specific API calls:
Authorization: Bearer eyJhbGc...
Server-side verification uses jose with a cached JWKS — no round-trip to Clerk per request. Implemented in server/auth-session.ts. See Authentication overview for full details.
Entitlement / tier gating
Valid key ≠ PRO. Authentication and entitlement are orthogonal. Every PRO-gated endpoint runs a separate isCallerPremium(req) check (server/_shared/premium-check.ts) that does not accept Origin or an anonymous browser session as proof of PRO.
isCallerPremium returns true only when one of these is present:
- A valid
X-WorldMonitor-Key (env-allowlisted from WORLDMONITOR_VALID_KEYS, or a user-owned wm_-prefixed key whose Convex record has the apiAccess entitlement), or
- A Clerk
Authorization: Bearer … token whose user has role pro or Dodo entitlement tier ≥ 1.
From the browser, premiumFetch() (src/services/premium-fetch.ts) handles this by injecting one of those credentials on every request. Desktop app uses WORLDMONITOR_API_KEY from the runtime config. Server-to-server callers must send the header explicitly.
| Tier | Access |
|---|
| Anonymous | Public reads only (conflicts, natural disasters, markets basics) |
| Signed-in free | Same as anonymous + user preferences |
| PRO | All endpoints, MCP, AI Brief, Shipping v2, Scenarios |
Tier is resolved from Convex on each call, so a subscription change takes effect on the next request (after cache invalidation).