Skip to main content

Documentation Index

Fetch the complete documentation index at: https://worldmonitor.app/docs/llms.txt

Use this file to discover all available pages before exploring further.

WorldMonitor has three authentication modes. Which one applies depends on how you’re calling.

Auth matrix

ModeHeaderUsed byTrusted on which endpoints?
Browser originOrigin: https://www.worldmonitor.app (browser-set)Dashboard, desktop appMost public endpoints — but not forceKey: true routes.
API keyX-WorldMonitor-Key: wm_live_...Server-to-server, scripts, SDKsAll endpoints, including forceKey: true.
OAuth bearerAuthorization: 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 JWTAuthorization: Bearer <clerk-jwt>Authenticated browser usersUser-specific routes: /api/latest-brief, /api/user-prefs, /api/notification-channels, /api/brief/share-url, etc.

forceKey: true — which endpoints ignore browser origin?

Some endpoints explicitly reject the “trusted browser origin” shortcut and require a real API key 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 X-WorldMonitor-Key.

Browser origin mode

CORS and validateApiKey together decide whether a given Origin is trusted. The allowlist is centralized in api/_cors.js.
  • Allowed origins get Access-Control-Allow-Origin: <echoed> and pass the key check.
  • Disallowed origins get no CORS header (browser rejects) and fail the key check.
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_live_abcdef0123456789...
Minimum 16 characters. Keep keys out of client-side code — use a server-side proxy if you need to call from the browser to a forceKey endpoint.

Server-side validation

The edge function calls validateApiKey(req, { forceKey?: boolean }):
  1. If forceKey is false AND the origin is trusted → pass.
  2. Else, check X-WorldMonitor-Key against WORLDMONITOR_VALID_KEYS (env).
  3. Also check the caller’s entitlement cache (invalidate-user-api-key-cache flushes this).
  4. If neither 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 a trusted browser Origin as proof of PRO, even though it accepts Origin for anonymous/public access. 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.
TierAccess
AnonymousPublic reads only (conflicts, natural disasters, markets basics)
Signed-in freeSame as anonymous + user preferences
PROAll 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).