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-sessioncookie,X-WorldMonitor-Key, or theX-Api-Keyalias. User-issued keys are validated for current API access. - Anonymous weather:
?keys=weatherAlertsis public only when no API key header is sent. If you attachX-WorldMonitor-Key/X-Api-Key, the request is fully validated even for weather — a malformed key returns401, a key without current API access returns403. Callers that always send a key must send a valid, entitled key (or omit the header to use the anonymous weather path). - Server-to-server: call
https://api.worldmonitor.app/api/bootstrapdirectly withX-WorldMonitor-Key: wm_.... There is no separate gateway host, token exchange, activation step, or IP allow-list requirement for this endpoint. - Cache: browser/session responses keep the default tier cache policy: all-keys responses use
Cache-Control: public, s-maxage=600, stale-while-revalidate=120, stale-if-error=900plus fast-tierCDN-Cache-Control; explicit?tier=fast/?tier=slowrequests use browsermax-age=60/max-age=300and CDNs-maxage=600/s-maxage=7200. Key-authenticated responses useCache-Control: no-storeand do not emit CDN cache headers. - 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 underdata, plus amissinglist for cache keys not present in Redis.
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.
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 returns401. - Body (at least one of
keys/patternsrequired): - 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 matchmilitary: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.
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-Keysupported onPOST /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?iso2=US
Returns country metadata: centroid, bbox, capital, ISO codes.
GET /api/reverse-geocode?lat=40.7&lon=-74.0
Reverse geocodes a lat/lon to the nearest country + city using the bundled coordinate dataset.
GET /api/data/city-coords?q=Tokyo
City name → coordinates lookup.
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):
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>.
<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.