/api/health
Primary health endpoint. Checks all Redis-backed data keys and seed freshness metadata in a single pipeline call.
Authentication: None required. Browser origins must still pass the CORS allowlist in api/_cors.js; requests with no Origin header, such as server-side monitors, are allowed. Health responses are never cached (Cache-Control: private, no-store, max-age=0 and CDN-Cache-Control: no-store).
HTTP Method: GET
Query Parameters
| Parameter | Values | Description |
|---|---|---|
compact | 1 | Omit per-key details; only return keys with problems |
Response Status Codes
| HTTP Status | Overall Status | Meaning |
|---|---|---|
200 | HEALTHY | All checks OK, no warnings |
200 | WARNING | Some keys stale or on-demand keys empty, but no critical failures |
200 | DEGRADED | Critical keys empty, but ≤3% of all probed keys |
200 | UNHEALTHY | Critical keys empty, >3% of all probed keys |
503 | REDIS_DOWN | Could not connect to Redis — the only state that returns a non-200 code |
The overall health verdict lives in the JSONstatusfield, not the HTTP code. Every state exceptREDIS_DOWNreturns200so warn-level seed jitter doesn’t flap HTTP-status monitors (see PR #2699).REDIS_DOWNreturns503because with Redis unreachable the endpoint can assess nothing, so a plain HTTP probe must see a failure.
Response Body
summary fields: total is one entry per probed key (currently ~194 and growing as panels are added). warn excludes on-demand-empty keys — those are surfaced separately as onDemandWarn so they don’t drive the overall verdict to WARNING. staleContent is a subset of warn (fresh seeder but the upstream feed stopped advancing). Only crit (EMPTY/EMPTY_DATA) drives DEGRADED/UNHEALTHY.
With ?compact=1, the checks object is replaced by problems containing only non-OK keys.
Key Classifications
Keys are grouped into three tiers that determine alert severity:| Tier | Severity when empty | Description |
|---|---|---|
| Bootstrap | CRIT | Seeded data required at startup. Empty means the dashboard is missing critical data |
| Standalone | CRIT (seeded) / WARN (on-demand) | Populated by seed loops or RPC handlers. On-demand keys are expected to be empty until first request |
| On-demand | WARN | Populated lazily by RPC calls. Empty is normal if nobody has requested the data yet |
Per-Key Statuses
| Status | Severity | Meaning |
|---|---|---|
OK | Green | Data present, seed fresh |
OK_CASCADE | Green | Key empty but a sibling in the cascade group has data (e.g., theater posture fallback chain) |
STALE_SEED | Warn | Data present but seed-meta age exceeds maxStaleMin |
STALE_CONTENT | Warn | Seeder is fresh but the upstream content stopped advancing (frozen feed); counted in summary.staleContent |
COVERAGE_PARTIAL | Warn | Record count is below the key’s declared minRecordCount (e.g. 10/13 chokepoints or 139/174 PortWatch port-activity countries) |
SEED_ERROR | Warn | seed-meta reports status: "error" from the last seed run |
REDIS_PARTIAL | Warn | A single per-command Redis error on this key’s STRLEN/GET (not a full outage) |
EMPTY_ON_DEMAND | Warn | On-demand key has no data yet (expected until first request); counted in summary.onDemandWarn |
EMPTY | Crit | Bootstrap or seeded standalone key has no data |
EMPTY_DATA | Crit | Key exists but contains zero records (and 0 is not a valid state for it) |
Cascade Groups
Some keys use fallback chains. If any sibling has data, empty siblings reportOK_CASCADE:
- Theater Posture:
theaterPostureLive->theaterPosture(stale) ->theaterPostureBackup - Military Flights:
militaryFlights->militaryFlightsStale - Displacement:
displacement(current UTC year) ->displacementPrev(prior year, covers the Jan-1 window before the new-year seed runs)
riskScores is intentionally stricter than a raw feed heartbeat. Its
recordCount is realtime signal-density coverage: the count of score-relevant
Tier-1 conflict, news, and cyber signal families present during the CII refresh.
The conflict family is satisfied by either the ACLED path or the UCDP event
feed, matching the CII v8 scorer. When those feeds are reachable but quiet,
riskScores can still report
COVERAGE_PARTIAL; underlying feed freshness is tracked by the source-specific
health entries where those feeds publish seed metadata.
portwatchPortActivity also uses minRecordCount. A fresh
seed-meta:supply_chain:portwatch-ports record below 174 countries reports
COVERAGE_PARTIAL instead of OK; partial runs may still refresh per-country
PortWatch cache entries, but the canonical country list and healthy seed-meta
signal do not advance until full 174-country coverage returns.
Staleness Thresholds (maxStaleMin)
Selected thresholds fromSEED_META:
| Domain | Max Stale (min) | Notes |
|---|---|---|
| Market quotes, crypto, sectors, earthquakes, insights | 30 | High-frequency relay loops / critical event data |
| Military flights | 30 | Near-real-time tracking |
| Predictions, flight delays (FAA) | 90 | Polymarket / airport snapshots |
| Unrest | 120 | 45min cron, 2h grace |
| Cyber threats | 240 | APT data updated less frequently |
| Wildfires | 360 | FIRMS NRT accumulates over hours |
| Climate anomalies | 540 | 3h cron, 3× cadence |
| BIS extended, World Bank, IMF | 2160-100800 | Institutional data, weekly/monthly/annual |
These are illustrative;SEED_METAinapi/health.jsis the source of truth and each entry documents its own cadence rationale.
Example Requests
/api/seed-health
Focused endpoint for seed loop freshness. Checks only seed-meta:* keys without fetching actual data payloads.
Authentication: Requires valid API key or allowed origin.
HTTP Method: GET
Response Status Codes
| HTTP Status | Overall Status | Meaning |
|---|---|---|
200 | healthy | All seed loops reporting on time |
200 | warning | Some seeds stale (age > 2x interval) or below a declared coverage floor |
200 | degraded | Some seeds missing entirely |
401 | - | Invalid or missing API key |
503 | - | Redis unavailable |
Response Body
Staleness Logic
A seed is considered stale when its age exceeds 2x the configured interval. This accounts for normal jitter in cron/relay timing. Seeds with an explicitminRecordCount also report coverage_partial and stale: true until their coverage floor is met.
| Domain | Interval (min) | Stale After (min) |
|---|---|---|
| Predictions, military flights | 8 | 16 |
| Market quotes, earthquakes, unrest | 15 | 30 |
| ETF flows, stablecoins, chokepoints | 30 | 60 |
| Service statuses, spending, wildfires | 60 | 120 |
| Shipping rates, satellites | 90-120 | 180-240 |
| GPS jamming, displacement | 360 | 720 |
| Iran events, UCDP | 210-5040 | 420-10080 |
Example Request
Integration with Monitoring Tools
UptimeRobot
Use/api/health as the monitor URL. The HTTP status code only distinguishes a total Redis outage from everything else:
503=REDIS_DOWN(Redis unreachable — a true hard outage)200= every other state, includingDEGRADEDandUNHEALTHY
https://api.worldmonitor.app/api/health?compact=1 and alert when the compact token "status":"HEALTHY" (no space after the colon) is absent from the response body. Compact mode serializes with no indentation, so this exact token is stable regardless of formatting.
The bare/api/healthURL pretty-prints the body, so the healthy token there is"status": "HEALTHY"with a space after the colon. If you monitor the non-compact URL, match that spaced token instead — the no-space"status":"HEALTHY"is absent even when healthy and will false-alert. Prefer?compact=1, or parsesummary.crit > 0.
Custom Alerting
Parse the JSON response to build granular alerts:Differences Between Endpoints
| Aspect | /api/health | /api/seed-health |
|---|---|---|
| Scope | Data keys + seed metadata | Seed metadata only |
| Auth | None (public) | API key or allowed origin |
| Data fetched | Full Redis values (to count records) | Only seed-meta:* keys |
| HTTP 503 | Only REDIS_DOWN (DEGRADED/UNHEALTHY return 200) | No (always 200 unless Redis down) |
| Best for | Uptime monitoring, dashboard health | Debugging seed loop issues |
| Response size | Larger (~194 keys with record counts) | Smaller (seed-meta domains only) |
