Skip to main content
These endpoints are not part of a domain RPC service — they sit at the root of the API surface and handle platform concerns.

Bootstrap

GET /api/bootstrap

Single round-trip hydration for the dashboard. Returns all bootstrap-registered Redis cache keys unwrapped from their seed envelopes in one response.
  • Auth: browser wm-session cookie, X-WorldMonitor-Key, or the X-Api-Key alias. User-issued keys are validated for current API access.
  • Anonymous weather: ?keys=weatherAlerts is public only when no API key header is sent. If you attach X-WorldMonitor-Key / X-Api-Key, the request is fully validated even for weather — a malformed key returns 401, a key without current API access returns 403. Callers that always send a key must send a valid, entitled key (or omit the header to use the anonymous weather path). This URL is no-store, which is what makes that contract hold at the edge as well as the origin: nothing caches it, so an invalid key can never be answered by a warm anonymous entry.
  • Public weather: ?keys=weatherAlerts&public=1 is the CDN-cached weather read. Like ?tier=fast&public=1, it returns the same shared seed payload to every caller and ignores any credentials you attach — a CDN hit precedes auth, so the marker is what lets the response be cached safely. Prefer it for high-volume anonymous reads; use the bare URL when you need your key validated.
  • Server-to-server: call https://api.worldmonitor.app/api/bootstrap directly with X-WorldMonitor-Key: wm_.... There is no separate gateway host, token exchange, activation step, or IP allow-list requirement for this endpoint.
  • Cache: only the explicitly-marked ?...&public=1 URLs are shared-cacheable, because only they answer every caller identically. ?tier=fast&public=1 / ?tier=slow&public=1 use browser max-age=60 / max-age=300 and CDN s-maxage=600 / s-maxage=7200. Single-key public URLs: on-demand keys (?keys=<onDemandName>&public=1) inherit the slow profile — browser max-age=300, CDN s-maxage=7200 — unless the key declares its own, which every key published more often than that shield does: correlationCards (browser max-age=60, CDN s-maxage=300), chinaDecisionSignals (browser max-age=60, CDN s-maxage=900), canadaRoads (browser max-age=60, CDN s-maxage=900), albertaRoads (browser max-age=60, CDN s-maxage=900), manitobaRoads (browser max-age=60, CDN s-maxage=900), marketCorrelationSeries (browser max-age=60, CDN s-maxage=900), imdCycloneMarine (browser max-age=60, CDN s-maxage=900), bcOpen511 (browser max-age=60, CDN s-maxage=1800), flightDelays (browser max-age=60, CDN s-maxage=1800), and forecasts (browser max-age=300, CDN s-maxage=3600); ?keys=weatherAlerts&public=1 uses Cache-Control: public, s-maxage=600, stale-while-revalidate=120, stale-if-error=900 with the fast-tier CDN shield. Everything else — key-authenticated, session-authenticated, the unmarked ?tier=... URLs, and the anonymous ?keys=weatherAlerts path — uses Cache-Control: no-store and emits no CDN cache headers. That split is deliberate: a CDN hit precedes auth, so a URL whose answer depends on credentials must never be cacheable.
  • Rate limit: user API key validation on this endpoint has a fail-closed fixed 60 s per-IP pre-validation limit of 600 attempts, separate from the default API sliding-window limiter.
  • Shape: { "data": { "earthquakes": ..., "outages": ..., "marketQuotes": ... }, "missing": [] } — ~40+ unwrapped seeded-domain payloads nested under data, plus a missing list for cache keys not present in Redis.
Use this on initial page load to avoid 40 parallel RPC calls. The on-demand tier includes chinaDecisionSignals, the bounded six-domain China country-summary contract. Its anonymous country/RPC representation, Pro MCP representation, and operator health registration share stable group IDs and provenance; see China Decision Signals.

Version

GET /api/version

Returns the latest GitHub Release of koala73/worldmonitor. Used by the desktop app to detect a newer published release and prompt the user to update. It is not the currently-deployed Vercel commit.
Cached public, s-maxage=300, stale-while-revalidate=60, stale-if-error=3600. Returns 502 { "error": "upstream" } or 502 { "error": "fetch_failed" } when the GitHub API is unreachable.

Cache purge

POST /api/cache-purge

Internal. Invalidates Redis cache keys by explicit list or glob patterns.
  • Auth: Authorization: Bearer $RELAY_SHARED_SECRET (timing-safe compared). Anything else returns 401.
  • Body (at least one of keys / patterns required):
  • Limits: up to 20 explicit keys, up to 3 patterns (each must end in *, bare * rejected), up to 200 deletions total, up to 5 SCAN iterations per pattern.
  • Safety: keys with prefixes rl: / __ are always skipped; patterns that would match military:bases:*, conflict:iran-events:*, conflict:ucdp-events:* (durable seeds) are skipped.
  • Non-production: on preview / development deploys, keys are auto-prefixed with {env}:{git-sha}: so purges can’t affect production data.
  • Response:

Health

GET /api/health

Aggregated freshness report for all registered seed keys. Returns HEALTHY, WARNING, DEGRADED, UNHEALTHY, or REDIS_DOWN in the JSON status field. All states except REDIS_DOWN return HTTP 200; REDIS_DOWN returns HTTP 503 because Redis was unreachable and the endpoint could not assess seed state. Responses are not cached (private, no-store, max-age=0 plus CDN-Cache-Control: no-store). Monitor via UptimeRobot / Better Stack with ?compact=1 — alert on any status other than HEALTHY. The full detailed view requires an operator/enterprise API key because it includes canonical cache key names and freshness thresholds.

GET /api/seed-health

Parallel registry for Railway-cron-driven seeders with their own cadence thresholds. Distinct from /api/health — both must be updated when cadence changes. See health endpoints. chinaDecisionSignals is refreshed by the derived-signals bundle every 15 minutes. /api/health allows 60 minutes before STALE_SEED; /api/seed-health uses a 30-minute interval (60-minute alarm) so both operator surfaces agree.

POST /api/seed-contract-probe

Internal probe that validates each seed producer’s envelope shape matches its consumers. Returns violations if any consumer reads a field the producer no longer emits.

User preferences

GET /api/user-prefs

POST /api/user-prefs

Per-user dashboard preferences (layout, toggles, filters). Clerk bearer required. Backed by Convex.
  • Idempotency: optional Idempotency-Key supported on POST /api/user-prefs. Retrying the same key with an identical body replays the original preferences response instead of applying the update again.

API key cache invalidation

POST /api/invalidate-user-api-key-cache

Invalidates a user’s entitlement cache after a subscription change (Dodo webhook → Convex → this endpoint). Internal — requires RELAY_SHARED_SECRET.

Geo utilities

GET /api/geo

Geo-IP echo: returns { "country": "<ISO2>" } derived from the CDN’s country header for the calling IP. Takes no parameters. (For reverse geocoding of coordinates, use GET /api/infrastructure/v1/reverse-geocode?lat=…&lon=….)

GET /api/reverse-geocode?lat=40.7&lon=-74.0

Reverse geocodes a lat/lon to the nearest country + city via OpenStreetMap Nominatim. Results are cached on a 0.001-degree grid for seven days, and the route is limited to 60 req/min/IP.

Account & session helpers

These routes back the dashboard and Settings UI. They are documented here so their behavior is discoverable, but they are internal helpers, not versioned product contracts — shapes can change with the UI that consumes them.

GET /api/me/entitlement

Returns {"isPro": true|false} for the signed-in user. Requires a Clerk bearer token (Authorization: Bearer …); a missing or invalid token returns 401 {"error":"unauthenticated"} so callers can distinguish “not signed in” from “signed in, free tier”. Used by the /pro marketing page to swap upgrade CTAs. Always Cache-Control: private, no-store.

GET /api/user/mcp-quota

Settings-UI read of the caller’s MCP daily quota, from the same counter the MCP server enforces against. Requires a Clerk session. Returns {"used": 12, "limit": 250, "resetsAt": "<next UTC midnight>"}; limit: null means unlimited. Free-account callers see the free-allowance meter instead. Backend failures fail soft (used: 0 / plan default) rather than erroring. Cache-Control: no-store.

POST /api/user/mcp-revoke

Settings-UI revocation of one Pro MCP token. Requires a Clerk session; the user id comes from the verified session, never the body. Body: {"tokenId": "<id>"}. Returns 200 {"ok":true}; errors: 400 (invalid_json, missing_token_id), 401, 404 not_found (deliberately collapsed against token enumeration), 409 already_revoked, 503 service_unavailable with Retry-After: 5. Revocation takes effect on the MCP server within its 60-second negative-cache window.

Operational endpoints

These anonymous internal-operations surfaces are not part of the public API contract. /api/analytics-health and /api/correlation-runtime-mode are origin-gated to WorldMonitor origins (other origins get a plain 403 Forbidden). /api/security/report is the bounded, wildcard-CORS exception described below.

POST /api/security/report

Anonymous browser Reporting API sink for COOP/COEP violation reports (wired via the site-wide Reporting-Endpoints: wm-coop-coep="/api/security/report" header). It intentionally uses wildcard CORS (Access-Control-Allow-Origin: *) rather than the operational origin guard. It accepts application/reports+json / application/report+json / application/json (else 415), bodies up to 32 KiB (else 413), applies the shared per-IP limiter, and answers successful reports with 204 and no body. Report URLs are reduced to origins before logging — no query strings or tokens are retained.

POST /api/analytics-health

Aggregate telemetry counter for the analytics collector’s own health. Accepts a tiny JSON body (≤1 KiB) of per-cohort write/failure counters — no event payloads, user ids, URLs, or fingerprints are accepted. Answers 204. Rate-limited 60/min (fail-closed).

GET /api/correlation-runtime-mode

Read-only control-plane switch: returns {"mode": "legacy"|"exact"|"fuzzy"} telling browser and seeder paths which correlation engine is active, without a redeploy. There is no write surface. If the backing store is unreachable it still returns 200 with "legacy". Cache-Control: no-store.

Utilities

GET /api/download?platform=<id>&variant=<id>

Redirects to the matching asset on the latest GitHub release of koala73/worldmonitor. Returns 302 to the asset URL on success, or 302 to releases/latest on any failure (unknown platform, no match, GitHub error). platform (required, exact string): variant (optional): full, world, tech, finance, commodity, energy, happy. One desktop binary ships and every variant is selected in-app after install, so each supported value resolves to the same World Monitor asset for the requested platform — the parameter records which variant the caller came from, it does not select a different download. An unrecognized value redirects to releases/latest without calling GitHub. Omitting variant resolves the same way a supported value does — the identity filter applies on every path, so a release carrying a stray asset that merely matches the platform suffix never wins. Caches the 302 for 5 minutes (s-maxage=300, stale-while-revalidate=60, stale-if-error=600).

POST /api/leads/v1/submit-contact

Public enterprise contact form. Turnstile-verified, rate-limited per IP. Part of LeadsService.

POST /api/leads/v1/register-interest

Captures email for Pro-waitlist signup. Writes to Convex and sends a confirmation email. Part of LeadsService. Browser callers must pass Turnstile. Desktop callers using source: "desktop-settings" bypass Turnstile only when the request is authenticated with the shared desktop secret:
  • X-WorldMonitor-Desktop-Timestamp: Unix epoch milliseconds, within 5 minutes of server time.
  • X-WorldMonitor-Desktop-Signature: sha256=<hex HMAC-SHA256>.
The HMAC input is <timestamp>\n<canonical JSON>, where canonical JSON contains email, source, appVersion, referredBy, website, and turnstileToken in that order. Configure WM_DESKTOP_SHARED_SECRET on both the desktop sidecar and the cloud API. During rollout, WM_DESKTOP_AUTH_ALLOW_LEGACY=true only accepts unsigned legacy desktop requests while the cloud API has no WM_DESKTOP_SHARED_SECRET configured. Once the cloud secret is set, desktop requests fail closed unless they include a valid signature, still subject to the tighter desktop rate limit.