Second of three PRs for v0.5.0 (J shipped the framework; this fills in real backends + documents the reframed design principle in-tree; L is the events tab + map fix, then tag). Backends (all satisfy GeocoderBackend; never raise, all-null on any failure): - NaviBackend — composed Navi /api/reverse/<lat>/<lon> (name/address + timezone + landclass + elevation in one call). Near-passthrough: response already matches the canonical 9-field shape. Best-effort warmup ping (Boise) on construction when a loop is running; config `headers` slot for a future Authorization: Bearer (config-only, no code change). Default base_url http://192.168.1.130:8440. - PhotonBackend — raw Photon /reverse?lat&lon&limit=1 (name/address only). Maps features[0].properties; postal_code <- postcode; timezone/landclass/ elevation_m null (Navi-composed-endpoint extras). - NominatimBackend — OSM Nominatim /reverse?format=jsonv2 (name/address only). Configurable rate limit (default 1/sec; 0 disables for self-hosted) + required User-Agent. Maps the address block; landclass/elevation_m/timezone null. Registered all three in supervisor _BACKEND_REGISTRY (resolved by EnrichmentConfig backend_class name). Docs — design pivot now in-tree: - PRODUCER §2 reframed: the verbatim Matt quote stays; the translation inverts. Central is the consumer's only data plane (consumers can't do follow-up lookups), so enrich deliberately and centrally, namespaced under _enriched, failing to null. "No enrichment" is gone. - PRODUCER §10.1 inverted: enrichment is expected; the anti-pattern is doing it OUTSIDE the framework (inline in poll(), bypassing cache + _enriched namespacing + the never-raise safety net). - PRODUCER new §13 Enrichment contract: Enricher / GeocoderEnricher / GeocoderBackend Protocols, NoOpBackend default, sqlite cache + TTL + cache-all-null + don't-cache-on-raise semantics, _enriched.<name> provenance, per-field coverage matrix (cross-checked against GEOCODER_FIELDS), and the landclass antimeridian known wrinkle. - CONSUMER FIRMS section: documents the data._enriched.geocoder bundle (9 fields), per-region coverage (US-full, non-US timezone+elevation), and the antimeridian landclass caveat. Tests: - test_navi/photon/nominatim_backend.py — happy-path field mapping, null handling, extra-key drop, network/timeout/non-200/malformed -> all-null (never raises), Nominatim rate-limit (disabled + spacing) + User-Agent. Env-gated live Navi smoke (NAVI_INTEGRATION_TEST=1; skipped by default — the 192.168.1.130 endpoint isn't reachable from CT104's segment). - test_producer_doc.py — +4: §2 verbatim quote present, §10.1 subsection exists, §13 names all four protocol types, §13 coverage matrix == GEOCODER_FIELDS (derived from code, not hardcoded). Verification: full pytest 525 passed, 1 skipped (was 495; +30 backend + 4 doc tests, -1 the env-gated skip). grep subject_for_event/_ADAPTER_REGISTRY clean. All three backends import + resolve via the registry. Flagged for later (NOT done here): adapters besides FIRMS that should declare enrichment_locations (nwis, eonet, gdacs, usgs_quake, wfigs_*) — that's PR L scope alongside the events tab. See PR description. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
104 KiB
\
Central — Consumer Integration
"Central takes it all and gives it all. It's up to the pipe to do with it what it will." — Matt Johnson, PM
Central is a faithful firehose. Adapters preserve every upstream field; nothing is enriched, formatted, or opinionatedly translated by Central itself. The CloudEvents envelope adds routing + dedup support; everything else is upstream-shaped. Consumers decide what to render and how.
This document is the consumer contract. A consumer (MeshAI today, anything else tomorrow) should be able to read it once and have everything needed to subscribe, deserialize, dedup, recover from disconnects, and handle adapter-specific fall-off semantics. The doc IS the spec. If a downstream consumer has to grep Central's source to understand a field, the doc has failed.
Where this doc lists an upstream lookup endpoint for an ID-only field, that is a consumer-side convenience — explicitly NOT a recommendation that Central should enrich. That's a consumer choice, not Central's job.
Table of contents
- Quick start
- Connection details
- Stream layout
- Subject namespace registry
- Wire format
- Per-adapter reference
- Fall-off / removal semantics
- Consumer patterns
- Dedup implementation guide
- Writing a new consumer — checklist
- Troubleshooting
1. Quick start
Subscribe to everything Central publishes and print one line per event:
import asyncio
import json
import nats
async def main():
nc = await nats.connect("nats://central.local:4222")
js = nc.jetstream()
# Wildcard at the top: every stream's subject filter is `central.<domain>.>`,
# so `central.>` covers them all.
sub = await js.subscribe("central.>", durable="my-consumer")
async for msg in sub.messages:
envelope = json.loads(msg.data)
print(msg.subject, envelope["time"], envelope["source"])
await msg.ack()
asyncio.run(main())
The NATS subject is on msg.subject (transport-level). Everything else lives inside
the CloudEvents envelope at json.loads(msg.data). See §5 for the
full envelope shape.
2. Connection details
Server URL
Central runs JetStream-enabled NATS on the default 4222 port. The connection URL shape is one of:
nats://<host>:4222— plain TCP, intra-trust-boundarytls://<host>:4222— TLS-wrapped (Central deployments serving external consumers should require this)
There is no fixed public hostname; ask your Central operator for the URL.
Auth options
Central supports three NATS auth modes; the operator chooses one at deployment time:
- Token — set
token="<value>"onnats.connect() - NKEY — set
nkeys_seed_str=<seed>ornkeys_seed=<path> - User / password — set
user="..."andpassword="..."
Never embed credentials in source — read them from a secret store. Central does not document specific credentials in this doc because they are operator state, not contract state.
JetStream context
nc = await nats.connect(...) # core NATS client
js = nc.jetstream() # JetStream extension client
All persistent subscriptions go through js, not nc — Central's events live in
JetStream streams (not core NATS pub/sub).
Discovering streams
async for stream in js.streams_info():
print(stream.config.name, stream.config.subjects)
The full registry of streams and subjects is documented in §3 and §4. Discovery is useful for operational sanity-checking; do not rely on it for routing decisions (subject patterns are stable; rely on those).
3. Stream layout
Central operates seven JetStream streams. Six are event-bearing (consumer-relevant);
one (CENTRAL_META) carries status-only messages and is explicitly skipped by
Central's archive.
| Stream | Subject filter | Retention (days) | Storage cap | Event-bearing | Dashboard |
|---|---|---|---|---|---|
CENTRAL_WX |
central.wx.> |
7 | 1 GiB | ✓ | ✓ |
CENTRAL_FIRE |
central.fire.> |
7 | 1 GiB | ✓ | ✓ |
CENTRAL_QUAKE |
central.quake.> |
7 | 1 GiB | ✓ | ✓ |
CENTRAL_SPACE |
central.space.> |
7 | 1 GiB | ✓ | ✓ |
CENTRAL_DISASTER |
central.disaster.> |
7 | 1 GiB | ✓ | ✓ |
CENTRAL_HYDRO |
central.hydro.> |
7 | 1 GiB | ✓ | ✓ |
CENTRAL_META |
central.meta.> |
1 | 1 GiB | — | ✓ |
Retention and storage caps are migration-seeded defaults visible in config.streams;
operators may tune them at runtime, so treat the values above as starting points,
not invariants. Stream names and subject filters are code-level structural state
(src/central/streams.py) and do not change without a code release.
4. Subject namespace registry
graph TD
root["central.>"]
root --> wx["central.wx.>"]
root --> fire["central.fire.>"]
root --> quake["central.quake.>"]
root --> space["central.space.>"]
root --> disaster["central.disaster.>"]
root --> hydro["central.hydro.>"]
root --> meta["central.meta.> (status-only)"]
wx -->|nws| wx_alert["wx.alert.us.<state>.<type>.<code>"]
fire -->|firms| fh["fire.hotspot.<satellite>.<confidence>"]
fire -->|inciweb| fn["fire.narrative.inciweb.<state>"]
fire -->|wfigs_incidents| fi["fire.incident.<state>.<county> (+ .removed.<state>)"]
fire -->|wfigs_perimeters| fp["fire.perimeter.<state>.<county> (+ .removed.<state>)"]
quake -->|usgs_quake| qe["quake.event.<tier>"]
space -->|swpc_alerts| sa["space.alert.<product_id>"]
space -->|swpc_kindex| sk["space.kindex"]
space -->|swpc_protons| sp["space.proton_flux"]
disaster -->|eonet| de["disaster.eonet.<category>.global (+ .removed.global)"]
disaster -->|gdacs| dg["disaster.<eventtype>.<country> (+ .removed.<country>)"]
hydro -->|nwis| hn["hydro.<parameter_code>.<agency>.<site_no>"]
Full concrete subject patterns, one row per pattern, including removal flavors where applicable:
| Subject pattern | Adapter | Token semantics |
|---|---|---|
central.wx.alert.us.<state>.<county|zone>.<code> |
nws |
<state> 2-letter lowercase, <county|zone> literal, <code> UGC-style code |
central.wx.alert.us.unknown |
nws |
Fallback when the alert lacks a primary region |
central.fire.hotspot.<satellite>.<confidence> |
firms |
<satellite> is viirs_snpp / viirs_noaa20 / viirs_noaa21 / modis_terra / modis_aqua; <confidence> is low/nominal/high |
central.fire.narrative.inciweb.<state> |
inciweb |
<state> 2-letter lowercase or unknown |
central.fire.incident.<state>.<county> |
wfigs_incidents |
<state> 2-letter lowercase, <county> lowercased with spaces hyphenated |
central.fire.incident.removed.<state> |
wfigs_incidents |
Tombstone subject for fallen-off incidents |
central.fire.perimeter.<state>.<county> |
wfigs_perimeters |
Same shape as incidents |
central.fire.perimeter.removed.<state> |
wfigs_perimeters |
Tombstone subject for fallen-off perimeters |
central.quake.event.<tier> |
usgs_quake |
<tier> is minor / light / moderate / strong / major / great (USGS magnitude bands) |
central.space.alert.<product_id> |
swpc_alerts |
<product_id> is the NOAA SWPC product code lowercased, e.g. a20f |
central.space.kindex |
swpc_kindex |
Single fixed subject |
central.space.proton_flux |
swpc_protons |
Single fixed subject |
central.disaster.<eventtype>.<country> |
gdacs |
<eventtype> is GDACS 2-letter code lowercased (wf/fl/tc/vo/dr/eq); <country> is country name lowercased + hyphenated, or unknown |
central.disaster.<eventtype>.removed.<country> |
gdacs |
Tombstone (subtype before removed per §8 canonical pattern) |
central.disaster.eonet.<category>.global |
eonet |
<category> is the EONET upstream id lower_snake_case'd (e.g. wildfires, sea_lake_ice, severe_storms); global is the literal country-equivalent suffix (no per-country resolution in v1) |
central.disaster.eonet.<category>.removed.global |
eonet |
Tombstone for missing-from-feed events |
central.hydro.<parameter_code>.<agency>.<bare_site_no> |
nwis |
<parameter_code> is the 5-digit USGS pcode (00060 discharge, 00065 gage height, 00010 water temp); <agency> is the lowercased agency prefix from monitoring_location_id (usgs, mo005); <bare_site_no> is the agency-prefix-stripped site number |
Subscriber wildcard patterns work as expected — e.g. central.fire.> for all fire
events, central.fire.hotspot.> for satellite hotspots only, central.fire.>.> for
two-token depth (NATS * matches one token; > matches one or more).
5. Wire format
Central publishes CNCF CloudEvents v1.0 envelopes onto JetStream subjects. A NATS
message body is the JSON-serialized envelope. The inner data field carries
Central's Event model.
5a. CloudEvents envelope
| Field | Spec | Type | Description |
|---|---|---|---|
specversion |
CE core | str | Always "1.0" |
id |
CE core | str | Same as Event.id; serves as the dedup id (Nats-Msg-Id header on publish) |
source |
CE core | str | Central's source URI (e.g. "central.echo6.co"); identifies the publishing Central instance |
type |
CE core | str | "central.<category>.v1" — derived from the inner Event.category. Useful for CE-aware routers; consumers should prefer the NATS msg.subject for routing |
time |
CE core | str | ISO 8601 timestamp of the underlying event (same as Event.time) |
datacontenttype |
CE core | str | Always "application/json" |
centralcategory |
CE extension | str | Mirror of Event.category. Lowercase-no-underscores per CloudEvents extension rules |
centralseverity |
CE extension | int | Mirror of Event.severity when non-null; omitted entirely when severity is None |
centralschemaversion |
CE extension | str | Central's schema version (currently "1.0") — bumped when the inner Event shape changes |
data |
CE core | object | The inner Central Event (see §5b) |
5b. Inner Event payload
The CloudEvents data field is a JSON-serialized central.models.Event Pydantic
model. Fields:
| Field | Type | Nullable | Description |
|---|---|---|---|
id |
str | no | Stable across re-publish (dedup key — see §9 for adapter-specific composite shapes) |
adapter |
str | no | Adapter identity ("nws", "firms", …) |
category |
str | no | Hierarchical category ("wx.alert.severe_thunderstorm_warning", "fire.hotspot.viirs_noaa20.high", …) |
time |
str (ISO 8601 UTC) | no | Event-time (upstream timestamp), not processing-time |
expires |
str (ISO 8601 UTC) | yes | Adapter-specific expiry (NWS alerts set this; most don't) |
severity |
int | yes | 0–4 or None. Adapter-specific mapping (Green=1/Orange=2/Red=3 for GDACS; FIRMS confidence-driven 1/2/3; etc.) |
geo |
object | no | See Geo model below |
data |
object | no | Adapter-specific upstream payload — see each adapter's section in §6 |
The geo sub-object:
| Field | Type | Nullable | Description |
|---|---|---|---|
centroid |
[float, float] |
yes | [lon, lat] GeoJSON order, NOT [lat, lon]. The single most common consumer bug; check your axis order. |
bbox |
[float, float, float, float] |
yes | [minLon, minLat, maxLon, maxLat] |
regions |
list[str] |
no (may be empty) | Region codes — adapter-specific (e.g. NWS uses US-<STATE>-<UGC-code>; GDACS uses ISO-3 codes; WFIGS uses US-US-<STATE>-<COUNTY>) |
primary_region |
str | yes | Alphabetically-first region from regions, used as the subject's primary regional token |
Event.data shape varies per adapter. See §6 for
the per-adapter field surface. Central never normalizes or strips upstream fields —
whatever the upstream returns lives in Event.data verbatim.
6. Per-adapter reference
Twelve adapters, twelve subsections. Same template. The live JSON examples are
verbatim CloudEvents envelopes pulled from CT104 via psql ... events.payload.
firms — NASA FIRMS satellite-detected fire hotspots
Active-fire detections from the VIIRS and MODIS instruments on NASA / NOAA polar satellites. Each event is a single thermal-anomaly pixel within ~3 hours of overpass (NRT delivery). Useful for early ignition detection, hotspot maps, and triggering ground-survey workflows.
-
Stream:
CENTRAL_FIRE -
Subject pattern:
central.fire.hotspot.<satellite>.<confidence><satellite>isviirs_snpp,viirs_noaa20,viirs_noaa21,modis_terra, ormodis_aqua<confidence>islow,nominal, orhigh(derived from upstream'sconfidencefield)
-
Cadence default: 300s (5 min)
-
Dedup key shape:
<satellite>:<acq_date>:<acq_time>:<lat_3dp>:<lon_3dp>- Coordinates rounded to 3 decimals (~100 m) to absorb floating-point jitter between successive NRT batches
- Same shape is reused as the inner
Event.id
-
Event.data fields:
key type nullable description latitudefloat no Pixel center, decimal degrees longitudefloat no Pixel center, decimal degrees acq_datestr ( YYYY-MM-DD)no UTC date of acquisition acq_timestr ( HHMM)no UTC time of acquisition, zero-padded satellitestr no Upstream label ( N,N20,N21,Terra,Aqua)instrumentstr no VIIRSorMODISconfidencestr no Normalized to low/nominal/highconfidence_rawstr no Original upstream confidence (single letter for VIIRS; numeric for MODIS) daynightstr no DorNfrpfloat yes Fire Radiative Power, MW bright_ti4float yes Brightness temperature, channel 4 (Kelvin) bright_ti5float yes Brightness temperature, channel 5 (Kelvin) scanfloat yes Along-scan pixel size, km trackfloat yes Along-track pixel size, km versionstr yes Upstream data version -
Decipherable as-is: yes.
confidence+satellite+instrumentare human-readable labels; FRP and brightness temperatures are numeric but self-describing. -
Upstream lookup endpoints: none required. The complete pixel context lives in
data. (For users who want the satellite's orbit metadata, the FIRMS public archive is athttps://firms.modaps.eosdis.nasa.gov/.) -
Removal semantics: none. FIRMS publishes detections; absence is the signal if a fire stops burning. Consumers should not expect explicit "removal" events.
-
Enrichment (
data._enriched.geocoder): FIRMS is the enrichment pilot, so each event carries a Central-derived geocoder bundle underdata._enriched.geocoder. It is not an upstream FIRMS field — Central reverse-geocodes the hotspot'slatitude/longitudeand attaches the result. The bundle always has these nine keys (any unresolved field isnull, never missing):key meaning nameplace / feature name citycity / town / village countycounty (or equivalent) statestate / province countrycountry postal_codepostal / ZIP code timezoneIANA tz (e.g. America/Boise)landclassland-management class (US PAD-US) elevation_mground elevation, metres Coverage by region (v0.5.0): US hotspots get the full bundle (with sparsity gaps in deep wilderness); non-US hotspots currently get only
timezoneandelevation_mpopulated (both planet-scale), the restnull, pending an upstream planet expansion. Treatnullas "not resolved," not "does not exist."Known wrinkle —
landclassantimeridian false-positive: a non-US hotspot near 51–53°N can spuriously get a non-nulllandclass(it false-matches the Aleutian "Rat Islands" US land-management polygon across the dateline). If you consumelandclass, treat a non-nullvalue on a clearly non-US point as suspect. Fix is tracked upstream. -
Live example (verbatim from CT104):
{
"id": "VIIRS_NOAA20_NRT:2026-05-17:2008:33.593:-105.314",
"data": {
"id": "VIIRS_NOAA20_NRT:2026-05-17:2008:33.593:-105.314",
"geo": {
"bbox": [
-105.31443,
33.5932,
-105.31443,
33.5932
],
"regions": [
],
"centroid": [
-105.31443,
33.5932
],
"primary_region": null
},
"data": {
"frp": 224.83,
"scan": 0.39,
"track": 0.36,
"version": "2.0NRT",
"acq_date": "2026-05-17",
"acq_time": "2008",
"daynight": "D",
"latitude": 33.5932,
"longitude": -105.31443,
"satellite": "N20",
"bright_ti4": 367.0,
"bright_ti5": 331.11,
"confidence": "high",
"instrument": "VIIRS",
"confidence_raw": "h"
},
"time": "2026-05-17T20:08:00Z",
"adapter": "firms",
"expires": null,
"category": "fire.hotspot.viirs_noaa20.high",
"severity": 3
},
"time": "2026-05-17T20:08:00+00:00",
"type": "central.fire.hotspot.viirs_noaa20.high.v1",
"source": "central.echo6.co",
"specversion": "1.0",
"centralcategory": "fire.hotspot.viirs_noaa20.high",
"centralseverity": 3,
"datacontenttype": "application/json",
"centralschemaversion": "1.0"
}
inciweb — NIFC InciWeb wildfire narrative (editorial RSS)
Narrative wildfire updates from inciweb.wildfire.gov, the NIFC editorial RSS feed.
Each event is one incident's full narrative — title, description (plain-text and
HTML forms), state, coordinates parsed from the description body. Editorial quality;
lower spatial precision than WFIGS but rich in plain-language context (incident
overview, recent activity, planned operations).
InciWeb publishes the same narrative in three near-identical forms for downstream tooling compatibility:
raw.descriptionanddescription_htmlcarry the original HTML-entity-laden form (with ,<br/>, etc.)descriptionis the same text with HTML entities decoded, tags stripped, and whitespace normalizedConsumers typically pick one (
descriptionfor plain text;description_htmlif they want the HTML markup) and ignore the others. Both are preserved verbatim because Central never drops upstream fields.
-
Stream:
CENTRAL_FIRE -
Subject pattern:
central.fire.narrative.inciweb.<state><state>is the 2-letter US state lowercased (e.g.co,ca), parsed fromprimary_regionunknownif the description doesn't yield a parseable state
-
Cadence default: 600s (10 min)
-
Dedup key shape:
<guid>— the InciWeb RSS feed's numeric incident id -
Event.data fields:
key type nullable description guidstr no InciWeb incident id (numeric, e.g. "312546")titlestr no Incident title with optional prefix code (e.g. COSJF= Colorado / San Juan Forest)urlstr no Public InciWeb page for the incident descriptionstr no Plain-text narrative, HTML entities decoded description_htmlstr no Original narrative with HTML entities intact raw.guidstr no Same as top-level guid(legacy compatibility)raw.linkstr no Same as top-level urlraw.titlestr no Same as top-level titleraw.creatorstr yes RSS dc:creator— InciWeb username of the publisherraw.pubDatestr yes RSS pubDate(RFC 822 timestamp)raw.descriptionstr no Same as top-level description_html(RSS raw form) -
Decipherable as-is: yes.
title+descriptionare plain English. -
Upstream lookup endpoints: none required.
urlalready resolves to the full public page. -
Removal semantics: none in v1. InciWeb keeps stale incidents in the feed indefinitely; consumers may filter on
raw.pubDaterecency. -
Live example (verbatim from CT104):
{
"id": "312546",
"data": {
"id": "312546",
"geo": {
"bbox": [
-107.89302283333333,
37.273410999999996,
-107.89302283333333,
37.273410999999996
],
"regions": [
"US-CO"
],
"centroid": [
-107.89302283333333,
37.273410999999996
],
"primary_region": "US-CO"
},
"data": {
"raw": {
"guid": "312546",
"link": "http://inciweb.wildfire.gov/incident-information/cosjf-2026-san-juan-national-forest-prescribed-fire-program",
"title": "COSJF 2026 San Juan National Forest Prescribed Fire Program",
"creator": "stowen",
"pubDate": "Wed, 02 Nov 2022 03:33:16 EDT",
"description": "Last updated: 2026-04-25\n\n\n--- \n\nThe type of incident is Prescribed Fire and involves the following unit(s) San Juan National Forest. \n\n--- \n\nState: Colorado\n\n--- \n\nCoordinates:\n\nLatitude: 37° 16 24.2796 Longitude: 107° 53 34.8822 \n\n--- \n\nNOTE: All fire perimeters and points are approximations. \n\n--- \n\nIncident Overview: The Dolores Ranger District is planning a prescribed fire operation for Wednesday/Thursday 5/20-5/21 for the following area:Salter-Carlyle (1660 acres). This project is located 15 miles east of Cahone, near Salter Y along NFSR 510 in Montezuma and Dolores Counties. See map for more details. San Juan National Forest began broadcast prescribed fire projects in late March, 2026. Project operations take place in spring and fall. The forests in Southwest Colorado are part of a fire-adapted ecosystem, which historically experienced frequent, low intensity fires on a large scale. Prescribed fire replicates that fire regime and increases the area on our landscape that has been burned at low and moderate conditions. These efforts reduce hazardous ground fuels, lessen the risk of unplanned large-scale wildfire, help restore ponderosa pine ecosystems, and improve wildlife habitat. Prescribed fire also reduces the ladder fuels that can carry fire into the canopy, killing mature trees. Prescribed fire smoke may affect your health. For more information, visit Wood smoke and your health.Columbine Ranger District: Beginning in late March, the Columbine Ranger District will begin the following prescribed fire projects as conditions allow. Smoke will be visible from Arboles, Bayfield, Pagosa Springs, as well as the U.S. Highway 160 and 151 corridors near those towns.Sauls Creek: This project area includes multiple potential units totaling approximately 900 acres. This project is located in Sauls Creek near NFSR 608, east of Bayfield and south of Highway 160 in La Plata County. It is part of a larger fuels reduction strategy that has included commercial logging and other fuels reduction work. The Columbine Ranger District completed 255 acres on the Sauls Creek prescribed fire project March 28-29, 2026 and another 314 acres on April 24-25, 2026. Southern HDs Prescribed Fire: This project is approximately 2,000 acres in the Fosset Gulch area (NFSR 613), west of Chimney Rock National Monument in Archuleta County. Units will be burned using ground ignitions and possibly unmanned aircraft systems (drones) as well. In past years the Columbine District burned adjacent units to help reduce wildfire risk to local homes and infrastructure.Pagosa Ranger District:Beginning in early April, the Pagosa Ranger District plans to implement multiple prescribed fire projects in Archuleta County as weather and conditions allow.Brockover-Devil Prescribed Fire: This 2,100-acre project area consists of \"maintenance\" burning, further reducing ground and ladder fuels. This treatment will build on the 2025 fall prescribed fire operations in the area and complement numerous mastication, thinning, and timber harvest treatments. These units are located along NFSR 629 and 923, west of Pagosa Springs and north of Highway 160. The Pagosa Ranger District completed 1,365 acres of this prescribed fire project April 23, 2026.Echo Canyon Prescribed Fire: This 1,139-acre project is located along NFSR 029 southeast of Pagosa Springs and east of Highway 84. In 2018, this project area was the site of commercial thinning, followed by mastication work in 2021 and prescribed fire in 2023. This year, the District hopes to conduct a \"maintenance\" burn to maximize fuel reduction benefits. Eight Mile Mesa Prescribed Fire: This project is located on Eight Mile Mesa (NFSR 651), south of Pagosa Springs and west of Highway 84. 275 acres will be burned to clean up slash and forest litter following a 2022 oak mastication project as well as other hazardous fuels and timber harvest activities.Dolores Ranger District: The Dolores Ranger District plans to begin prescribed fire implementation in early April. During burning operations, smoke may be visible around Mancos, Dolores, Dove Creek, Cortez, and Highway 491 between Dove Creek and Pleasant View. Smoke may settle into low lying valleys and the Dolores River Canyon overnight.Salter-Carlyle Prescribed Fire: This project is located 15 miles east of Cahone, near Salter Y along NFSR 510 in Montezuma and Dolores Counties. Boggy Draw Prescribed Fire: This 4,107-acre project is located 4-8 miles northeast of Dolores in Montezuma County, along NFSR 526 and 527. Smaller units of this overall project will be implemented as weather and conditions allow (see map). The Dolores Ranger District completed 1,586 acres on the Boggy Draw Prescribed Fire on April 219-20. The area closure was lifted on April 24.------------------------------------------------------------------------------------------------------------------------------------------------------2025/26 Winter Pile Burning Accomplishments:Columbine Ranger District:Junction Creek Road, 32 p iles, 15 acres.Ellington Lane, 100 piles, 4 acres. Pagosa Ranger District:Huerto, 183 acres. Mariposa, 439 acres. Lower Turkey, 12 acres.Valle Seco, 26 acres.Buckles Lake Road, 13 acres.Kenny Flat, 128 acres.Dolores Ranger District:Glade, 231 piles, 652 acres.------------------------------------2025 Broadcast Burning Accomplishments:Dolores Ranger District: Boggy Draw, 1,579 acresHaycamp Mesa, 1,657 acres------------------------------------2024/25 Winter Pile Burning AccomplishmentsDolores Ranger District Haycamp Mesa, 250 acres, 800 pilesTaylor Mesa, 390 acres, 134 pilesPagosa Ranger District Brockover-Devil, 490 acres Kenney Flats, 30 acres Quartz Piles, 20 acres Columbine Ranger District:Saul’s Creek, 50 acres, 100 pilesFossett Gulch, 17 acres, 100 pilesBaldy Mountain, 117 acres, 10 piles------------------------------------2024 Broadcast Prescribed Fire Accomplishments:DOLORES RANGER DISTRICT:Boggy Draw: 1,156 acresSalter Y Units 34 and 37: 1,279 acres COLUMBINE RANGER DISTRICT:Vallecito-Piedra: 235 acresSaul's Creek: 1,615 acresSouthern HDs: 1,080 acresPAGOSA RANGER DISTRICT:Fawn Gulch 1,100 acresMill Creek: 400 acres------------------------------------"
},
"url": "http://inciweb.wildfire.gov/incident-information/cosjf-2026-san-juan-national-forest-prescribed-fire-program",
"guid": "312546",
"title": "COSJF 2026 San Juan National Forest Prescribed Fire Program",
"description": "Last updated: 2026-04-25 --- The type of incident is Prescribed Fire and involves the following unit(s) San Juan National Forest. --- State: Colorado --- Coordinates: Latitude: 37° 16 24.2796 Longitude: 107° 53 34.8822 --- NOTE: All fire perimeters and points are approximations. --- Incident Overview: The Dolores Ranger District is planning a prescribed fire operation for Wednesday/Thursday 5/20-5/21 for the following area:Salter-Carlyle (1660 acres). This project is located 15 miles east of Cahone, near Salter Y along NFSR 510 in Montezuma and Dolores Counties. See map for more details. San Juan National Forest began broadcast prescribed fire projects in late March, 2026. Project operations take place in spring and fall. The forests in Southwest Colorado are part of a fire-adapted ecosystem, which historically experienced frequent, low intensity fires on a large scale. Prescribed fire replicates that fire regime and increases the area on our landscape that has been burned at low and moderate conditions. These efforts reduce hazardous ground fuels, lessen the risk of unplanned large-scale wildfire, help restore ponderosa pine ecosystems, and improve wildlife habitat. Prescribed fire also reduces the ladder fuels that can carry fire into the canopy, killing mature trees. Prescribed fire smoke may affect your health. For more information, visit Wood smoke and your health.Columbine Ranger District: Beginning in late March, the Columbine Ranger District will begin the following prescribed fire projects as conditions allow. Smoke will be visible from Arboles, Bayfield, Pagosa Springs, as well as the U.S. Highway 160 and 151 corridors near those towns.Sauls Creek: This project area includes multiple potential units totaling approximately 900 acres. This project is located in Sauls Creek near NFSR 608, east of Bayfield and south of Highway 160 in La Plata County. It is part of a larger fuels reduction strategy that has included commercial logging and other fuels reduction work. The Columbine Ranger District completed 255 acres on the Sauls Creek prescribed fire project March 28-29, 2026 and another 314 acres on April 24-25, 2026. Southern HDs Prescribed Fire: This project is approximately 2,000 acres in the Fosset Gulch area (NFSR 613), west of Chimney Rock National Monument in Archuleta County. Units will be burned using ground ignitions and possibly unmanned aircraft systems (drones) as well. In past years the Columbine District burned adjacent units to help reduce wildfire risk to local homes and infrastructure.Pagosa Ranger District:Beginning in early April, the Pagosa Ranger District plans to implement multiple prescribed fire projects in Archuleta County as weather and conditions allow.Brockover-Devil Prescribed Fire: This 2,100-acre project area consists of \"maintenance\" burning, further reducing ground and ladder fuels. This treatment will build on the 2025 fall prescribed fire operations in the area and complement numerous mastication, thinning, and timber harvest treatments. These units are located along NFSR 629 and 923, west of Pagosa Springs and north of Highway 160. The Pagosa Ranger District completed 1,365 acres of this prescribed fire project April 23, 2026.Echo Canyon Prescribed Fire: This 1,139-acre project is located along NFSR 029 southeast of Pagosa Springs and east of Highway 84. In 2018, this project area was the site of commercial thinning, followed by mastication work in 2021 and prescribed fire in 2023. This year, the District hopes to conduct a \"maintenance\" burn to maximize fuel reduction benefits. Eight Mile Mesa Prescribed Fire: This project is located on Eight Mile Mesa (NFSR 651), south of Pagosa Springs and west of Highway 84. 275 acres will be burned to clean up slash and forest litter following a 2022 oak mastication project as well as other hazardous fuels and timber harvest activities.Dolores Ranger District: The Dolores Ranger District plans to begin prescribed fire implementation in early April. During burning operations, smoke may be visible around Mancos, Dolores, Dove Creek, Cortez, and Highway 491 between Dove Creek and Pleasant View. Smoke may settle into low lying valleys and the Dolores River Canyon overnight.Salter-Carlyle Prescribed Fire: This project is located 15 miles east of Cahone, near Salter Y along NFSR 510 in Montezuma and Dolores Counties. Boggy Draw Prescribed Fire: This 4,107-acre project is located 4-8 miles northeast of Dolores in Montezuma County, along NFSR 526 and 527. Smaller units of this overall project will be implemented as weather and conditions allow (see map). The Dolores Ranger District completed 1,586 acres on the Boggy Draw Prescribed Fire on April 219-20. The area closure was lifted on April 24.------------------------------------------------------------------------------------------------------------------------------------------------------2025/26 Winter Pile Burning Accomplishments:Columbine Ranger District:Junction Creek Road, 32 p iles, 15 acres.Ellington Lane, 100 piles, 4 acres. Pagosa Ranger District:Huerto, 183 acres. Mariposa, 439 acres. Lower Turkey, 12 acres.Valle Seco, 26 acres.Buckles Lake Road, 13 acres.Kenny Flat, 128 acres.Dolores Ranger District:Glade, 231 piles, 652 acres.------------------------------------2025 Broadcast Burning Accomplishments:Dolores Ranger District: Boggy Draw, 1,579 acresHaycamp Mesa, 1,657 acres------------------------------------2024/25 Winter Pile Burning AccomplishmentsDolores Ranger District Haycamp Mesa, 250 acres, 800 pilesTaylor Mesa, 390 acres, 134 pilesPagosa Ranger District Brockover-Devil, 490 acres Kenney Flats, 30 acres Quartz Piles, 20 acres Columbine Ranger District:Saul’s Creek, 50 acres, 100 pilesFossett Gulch, 17 acres, 100 pilesBaldy Mountain, 117 acres, 10 piles------------------------------------2024 Broadcast Prescribed Fire Accomplishments:DOLORES RANGER DISTRICT:Boggy Draw: 1,156 acresSalter Y Units 34 and 37: 1,279 acres COLUMBINE RANGER DISTRICT:Vallecito-Piedra: 235 acresSaul's Creek: 1,615 acresSouthern HDs: 1,080 acresPAGOSA RANGER DISTRICT:Fawn Gulch 1,100 acresMill Creek: 400 acres------------------------------------",
"description_html": "Last updated: 2026-04-25\n\n\n--- \n\nThe type of incident is Prescribed Fire and involves the following unit(s) San Juan National Forest. \n\n--- \n\nState: Colorado\n\n--- \n\nCoordinates:\n\nLatitude: 37° 16 24.2796 Longitude: 107° 53 34.8822 \n\n--- \n\nNOTE: All fire perimeters and points are approximations. \n\n--- \n\nIncident Overview: The Dolores Ranger District is planning a prescribed fire operation for Wednesday/Thursday 5/20-5/21 for the following area:Salter-Carlyle (1660 acres). This project is located 15 miles east of Cahone, near Salter Y along NFSR 510 in Montezuma and Dolores Counties. See map for more details. San Juan National Forest began broadcast prescribed fire projects in late March, 2026. Project operations take place in spring and fall. The forests in Southwest Colorado are part of a fire-adapted ecosystem, which historically experienced frequent, low intensity fires on a large scale. Prescribed fire replicates that fire regime and increases the area on our landscape that has been burned at low and moderate conditions. These efforts reduce hazardous ground fuels, lessen the risk of unplanned large-scale wildfire, help restore ponderosa pine ecosystems, and improve wildlife habitat. Prescribed fire also reduces the ladder fuels that can carry fire into the canopy, killing mature trees. Prescribed fire smoke may affect your health. For more information, visit Wood smoke and your health.Columbine Ranger District: Beginning in late March, the Columbine Ranger District will begin the following prescribed fire projects as conditions allow. Smoke will be visible from Arboles, Bayfield, Pagosa Springs, as well as the U.S. Highway 160 and 151 corridors near those towns.Sauls Creek: This project area includes multiple potential units totaling approximately 900 acres. This project is located in Sauls Creek near NFSR 608, east of Bayfield and south of Highway 160 in La Plata County. It is part of a larger fuels reduction strategy that has included commercial logging and other fuels reduction work. The Columbine Ranger District completed 255 acres on the Sauls Creek prescribed fire project March 28-29, 2026 and another 314 acres on April 24-25, 2026. Southern HDs Prescribed Fire: This project is approximately 2,000 acres in the Fosset Gulch area (NFSR 613), west of Chimney Rock National Monument in Archuleta County. Units will be burned using ground ignitions and possibly unmanned aircraft systems (drones) as well. In past years the Columbine District burned adjacent units to help reduce wildfire risk to local homes and infrastructure.Pagosa Ranger District:Beginning in early April, the Pagosa Ranger District plans to implement multiple prescribed fire projects in Archuleta County as weather and conditions allow.Brockover-Devil Prescribed Fire: This 2,100-acre project area consists of \"maintenance\" burning, further reducing ground and ladder fuels. This treatment will build on the 2025 fall prescribed fire operations in the area and complement numerous mastication, thinning, and timber harvest treatments. These units are located along NFSR 629 and 923, west of Pagosa Springs and north of Highway 160. The Pagosa Ranger District completed 1,365 acres of this prescribed fire project April 23, 2026.Echo Canyon Prescribed Fire: This 1,139-acre project is located along NFSR 029 southeast of Pagosa Springs and east of Highway 84. In 2018, this project area was the site of commercial thinning, followed by mastication work in 2021 and prescribed fire in 2023. This year, the District hopes to conduct a \"maintenance\" burn to maximize fuel reduction benefits. Eight Mile Mesa Prescribed Fire: This project is located on Eight Mile Mesa (NFSR 651), south of Pagosa Springs and west of Highway 84. 275 acres will be burned to clean up slash and forest litter following a 2022 oak mastication project as well as other hazardous fuels and timber harvest activities.Dolores Ranger District: The Dolores Ranger District plans to begin prescribed fire implementation in early April. During burning operations, smoke may be visible around Mancos, Dolores, Dove Creek, Cortez, and Highway 491 between Dove Creek and Pleasant View. Smoke may settle into low lying valleys and the Dolores River Canyon overnight.Salter-Carlyle Prescribed Fire: This project is located 15 miles east of Cahone, near Salter Y along NFSR 510 in Montezuma and Dolores Counties. Boggy Draw Prescribed Fire: This 4,107-acre project is located 4-8 miles northeast of Dolores in Montezuma County, along NFSR 526 and 527. Smaller units of this overall project will be implemented as weather and conditions allow (see map). The Dolores Ranger District completed 1,586 acres on the Boggy Draw Prescribed Fire on April 219-20. The area closure was lifted on April 24.------------------------------------------------------------------------------------------------------------------------------------------------------2025/26 Winter Pile Burning Accomplishments:Columbine Ranger District:Junction Creek Road, 32 p iles, 15 acres.Ellington Lane, 100 piles, 4 acres. Pagosa Ranger District:Huerto, 183 acres. Mariposa, 439 acres. Lower Turkey, 12 acres.Valle Seco, 26 acres.Buckles Lake Road, 13 acres.Kenny Flat, 128 acres.Dolores Ranger District:Glade, 231 piles, 652 acres.------------------------------------2025 Broadcast Burning Accomplishments:Dolores Ranger District: Boggy Draw, 1,579 acresHaycamp Mesa, 1,657 acres------------------------------------2024/25 Winter Pile Burning AccomplishmentsDolores Ranger District Haycamp Mesa, 250 acres, 800 pilesTaylor Mesa, 390 acres, 134 pilesPagosa Ranger District Brockover-Devil, 490 acres Kenney Flats, 30 acres Quartz Piles, 20 acres Columbine Ranger District:Saul’s Creek, 50 acres, 100 pilesFossett Gulch, 17 acres, 100 pilesBaldy Mountain, 117 acres, 10 piles------------------------------------2024 Broadcast Prescribed Fire Accomplishments:DOLORES RANGER DISTRICT:Boggy Draw: 1,156 acresSalter Y Units 34 and 37: 1,279 acres COLUMBINE RANGER DISTRICT:Vallecito-Piedra: 235 acresSaul's Creek: 1,615 acresSouthern HDs: 1,080 acresPAGOSA RANGER DISTRICT:Fawn Gulch 1,100 acresMill Creek: 400 acres------------------------------------"
},
"time": "2022-11-02T07:33:16Z",
"adapter": "inciweb",
"expires": null,
"category": "fire.narrative.inciweb",
"severity": 0
},
"time": "2022-11-02T07:33:16+00:00",
"type": "central.fire.narrative.inciweb.v1",
"source": "central.echo6.co",
"specversion": "1.0",
"centralcategory": "fire.narrative.inciweb",
"centralseverity": 0,
"datacontenttype": "application/json",
"centralschemaversion": "1.0"
}
nws — National Weather Service alerts
Active alerts from api.weather.gov (NWS CAP/Atom alerts): severe weather warnings,
hazard advisories, civil emergencies, public-safety messages. Each event is one
alert with its full CAP-style metadata — onset, expiry, urgency, severity, certainty,
geocoded SAME/UGC region codes, descriptive headline + body.
-
Stream:
CENTRAL_WX -
Subject pattern:
central.wx.alert.us.<state>.<county|zone>.<code>orcentral.wx.alert.us.unknown<state>is 2-letter US state lowercased, parsed fromprimary_region<county|zone>literal token:countyif the primary region is a county FIPS code,zoneif it's a UGC public/fire zone<code>is the FIPS/UGC code (possibly hyphenated for multi-part codes)
-
Cadence default: 60s (NWS publishes new alerts at the minute mark)
-
Dedup key shape:
<id>— theapi.weather.govalert URL (e.g.https://api.weather.gov/alerts/urn:oid:…) -
Event.data fields: mirror of the NWS Atom
propertiespayload. Highlights:key type nullable description eventstr no Alert type, e.g. Civil Emergency Message,Flood Warning,Severe Thunderstorm Warningheadlinestr no Short headline descriptionstr no Full alert body, plain text instructionstr yes Recommended action severitystr no Minor/Moderate/Severe/Extreme/Unknownurgencystr no Past/Future/Expected/Immediate/Unknowncertaintystr no Observed/Likely/Possible/Unlikely/Unknowncategorystr no NWS category ( Safety,Met,Geo, etc.) — note: this collides with the Centralcategorynamespace; this lives insidedata.data.category, distinct fromEvent.categoryeffectivestr (ISO 8601) no When the alert takes effect expiresstr (ISO 8601) no When the alert expires onsetstr (ISO 8601) yes When the event starts endsstr (ISO 8601) yes When the event ends sentstr (ISO 8601) no When the alert was sent areaDescstr no Human-readable area description senderNamestr no Issuing office senderstr no Email of the issuing operator geocode.SAMElist[str] yes SAME codes (FIPS counties) geocode.UGClist[str] yes UGC codes (NWS public/fire zones) parametersobject yes Free-form key-list dict; NWS uses this for IPAWS metadata, WEA handling, EAS-ORG, etc. referenceslist yes Update / cancel references affectedZoneslist[str] yes Full URL list of affected zones in the api.weather.gov hierarchy messageTypestr no Alert,Update,Cancel,Ack,Errorstatusstr no Actual,Exercise,System,Test,Draftscopestr no Public,Restricted,Privatelanguagestr yes IETF language tag (typically en-US) -
Decipherable as-is: yes. NWS publishes complete plain-language alerts.
-
Upstream lookup endpoints: SAME / UGC codes are decoded via NWS Zone APIs at
https://api.weather.gov/zones/{type}/{code}— consumer convenience only; Central does not dereference these. -
Removal semantics: none. Alert expiry is signaled by the
expiresfield; cancellation by themessageType: Cancelflavor. -
Live example (verbatim from CT104):
{
"id": "https://api.weather.gov/alerts/AS-WY-2ed69d64-1c84-4b78-8ae3-c4579f7b708e",
"data": {
"id": "https://api.weather.gov/alerts/AS-WY-2ed69d64-1c84-4b78-8ae3-c4579f7b708e",
"geo": {
"bbox": [
-106.8986,
41.0064,
-106.3273,
41.7593
],
"regions": [
"US-AR-FIPS056007"
],
"centroid": [
-106.66208333333333,
41.33711666666667
],
"primary_region": "US-AR-FIPS056007"
},
"data": {
"id": "AS-WY-2ed69d64-1c84-4b78-8ae3-c4579f7b708e",
"@id": "https://api.weather.gov/alerts/AS-WY-2ed69d64-1c84-4b78-8ae3-c4579f7b708e",
"web": null,
"code": "IPAWSv1.0",
"ends": null,
"note": null,
"sent": "2026-05-19T11:02:11-06:00",
"@type": "wx:Alert",
"event": "Civil Emergency Message",
"onset": null,
"scope": "Public",
"sender": "lennylayman@carboncountywy.gov",
"status": "Actual",
"expires": "2026-05-19T15:02:11-06:00",
"geocode": {
"SAME": [
"056007"
]
},
"urgency": "Immediate",
"areaDesc": "Saratoga, Wyoming Police Department",
"category": "Safety",
"headline": "Saratoga Wyoming Police Department",
"language": "en-US",
"response": "Execute",
"severity": "Extreme",
"certainty": "Observed",
"effective": "2026-05-19T11:02:11-06:00",
"eventCode": {
"SAME": [
"CEM"
]
},
"parameters": {
"EAS-ORG": [
"CIV"
],
"CMAMtext": [
"Saratoga PD N. Platte Valley ONLY: Phone outages if 911 does not work call (307)326-8316"
],
"timezone": [
"MDT"
],
"WEAHandling": [
"Public Safety"
],
"CMAMlongtext": [
"There is currently an intermittent phone outage. Direct calls to the Saratoga Police Department are currently being routed to Town Hall staff. Not all 911 calls are going through to emergency dispatchers. If 911 does not work call (307)326-8316 John W Moore ChiefSaratoga Police Department"
]
},
"references": [
],
"senderName": "200645, WY Carbon County, WY Carbon County",
"description": "There is currently an intermittent phone outage. Direct calls to the Saratoga Police Department are currently being routed to Town Hall staff. Not all 911 calls are going through to emergency dispatchers. If 911 does not work call (307)326-8316 John W Moore Chief Saratoga Police Department",
"instruction": "There is currently an intermittent phone outage. Direct calls to the Saratoga Police Department are currently being routed to Town Hall staff. Not all 911 calls are going through to emergency dispatchers. If 911 does not work call (307)326-8316 John W Moore Chief Saratoga Police Department",
"messageType": "Alert",
"affectedZones": [
]
},
"time": "2026-05-19T17:02:11Z",
"adapter": "nws",
"expires": "2026-05-19T21:02:11Z",
"category": "wx.alert.civil_emergency_message",
"severity": 4
},
"time": "2026-05-19T17:02:11+00:00",
"type": "central.wx.alert.civil_emergency_message.v1",
"source": "central.echo6.co",
"specversion": "1.0",
"centralcategory": "wx.alert.civil_emergency_message",
"centralseverity": 4,
"datacontenttype": "application/json",
"centralschemaversion": "1.0"
}
usgs_quake — USGS earthquake catalog
Earthquake feed from earthquake.usgs.gov, configurable window (default: past hour).
Each event is one quake with full USGS event-page metadata — magnitude, depth,
location, alert level, tsunami flag, contributor sources.
-
Stream:
CENTRAL_QUAKE -
Subject pattern:
central.quake.event.<tier><tier>isminor(M<3),light(3≤M<4),moderate(4≤M<5),strong(5≤M<6),major(6≤M<7), orgreat(M≥7)
-
Cadence default: 60s
-
Dedup key shape:
<feature.id>— USGS event identifier (e.g.ci10240102,nc75361901) -
Event.data fields: mirror of USGS GeoJSON
propertiesblock. Selected fields:key type nullable description titlestr no Pre-formatted summary ( "M 1.7 - 23 km ESE of Ocotillo Wells, CA")placestr no Human-readable location magnitudefloat no Magnitude value magTypestr no Scale ( ml,mw,mb, etc.)depthfloat no Depth in km latitudefloat no Decimal degrees longitudefloat no Decimal degrees time_msint no Origin time in milliseconds since epoch updated_msint no Last update time typestr no Almost always "earthquake"; can be"quarry blast","explosion", etc.statusstr no "automatic"or"reviewed"tsunamiint no 1if USGS issued a tsunami alert,0otherwisealertstr yes PAGER alert level: green/yellow/orange/red, orNonefor sub-threshold eventsmmifloat yes Maximum reported instrumental MMI cdifloat yes Did You Feel It community decimal intensity feltint yes Number of DYFI responses sigint no USGS significance score netstr no Reporting network ( ci,nc,us, …)codestr no Network-local id (without the netprefix)idsstr no Comma-flanked list of associated event ids sourcesstr no Comma-flanked list of contributing sources typesstr no Comma-flanked list of associated product types urlstr no USGS event page URL detailstr no URL to the full GeoJSON event detail nstint yes Number of seismic stations gapfloat yes Largest azimuthal gap between stations (degrees) dminfloat yes Minimum station-event distance (degrees) rmsfloat yes Root mean square of residuals (seconds) tzint yes (Deprecated by USGS; usually null) -
Decipherable as-is: yes.
title+place+magnitudeare user-ready. -
Upstream lookup endpoints: none.
urlresolves to the human-readable event page;detailresolves to the full GeoJSON if extra fields are needed. -
Removal semantics: none. Events stay in the feed indefinitely; downgrades (e.g. status
automatic→reviewed) re-publish under the same id with updated metadata, which dedup catches as a no-op (id unchanged). -
Live example (verbatim from CT104):
{
"id": "ci10240102",
"data": {
"id": "ci10240102",
"geo": {
"bbox": [
-115.9105,
33.0528333333333,
-115.9105,
33.0528333333333
],
"regions": [
],
"centroid": [
-115.9105,
33.0528333333333
],
"primary_region": null
},
"data": {
"tz": null,
"cdi": null,
"gap": 63,
"ids": ",ci10240102,",
"mmi": null,
"net": "ci",
"nst": 34,
"rms": 0.22,
"sig": 42,
"url": "https://earthquake.usgs.gov/earthquakes/eventpage/ci10240102",
"code": "10240102",
"dmin": 0.09728,
"felt": null,
"type": "earthquake",
"alert": null,
"depth": 2.77,
"place": "23 km ESE of Ocotillo Wells, CA",
"title": "M 1.7 - 23 km ESE of Ocotillo Wells, CA",
"types": ",nearby-cities,origin,phase-data,scitech-link,",
"detail": "https://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/ci10240102.geojson",
"status": "reviewed",
"magType": "ml",
"sources": ",ci,",
"time_ms": 1779092758810,
"tsunami": 0,
"latitude": 33.0528333333333,
"longitude": -115.9105,
"magnitude": 1.65,
"updated_ms": 1779118847342
},
"time": "2026-05-18T08:25:58.810000Z",
"adapter": "usgs_quake",
"expires": null,
"category": "quake.event.minor",
"severity": 0
},
"time": "2026-05-18T08:25:58.810000+00:00",
"type": "central.quake.event.minor.v1",
"source": "central.echo6.co",
"specversion": "1.0",
"centralcategory": "quake.event.minor",
"centralseverity": 0,
"datacontenttype": "application/json",
"centralschemaversion": "1.0"
}
wfigs_incidents — NIFC WFIGS active wildfire incidents
Active-incident point records from the NIFC Wildland Fire Interagency Geospatial Services (WFIGS) Current FeatureServer. Each event is one IRWIN-linked incident with its full ESRI attribute surface — incident name, agency, ICS reporting status, fuel model, perimeter acreage, percent contained, etc.
Sample is a removal tombstone. WFIGS incidents fall off the "current" FeatureServer when the incident is closed; Central emits an explicit removal event so consumers can clean up state. The active-incident shape carries the same broad
data.raw.attr_*surface visible in thewfigs_perimeterssection below (sameattr_IrwinID,attr_IncidentName,attr_IncidentSize,attr_PercentContained, etc.) — minus the polygon coordinates.
-
Stream:
CENTRAL_FIRE -
Subject pattern:
central.fire.incident.<state>.<county>(active) orcentral.fire.incident.removed.<state>(removal tombstone)<state>is the 2-letter state lowercased<county>is the county name lowercased with spaces hyphenated
-
Cadence default: 300s
-
Dedup key shape (active):
<IrwinID>— the IRWIN UUID (e.g.{01AAC875-E26E-49E4-9DB0-80B5965A7B9F}) -
Dedup key shape (removal):
<IrwinID>:removed:<iso_now>— appends the removal-detection timestamp so the tombstone is distinct from the original -
Event.data fields (active):
key type nullable description IrwinIDstr no IRWIN incident UUID IncidentNamestr yes e.g. "Rochelle 2"IncidentSizefloat yes Reported size (acres) PercentContainedint yes 0-100 FireCausestr yes Human,Natural,Undetermined, etc.IncidentTypeCategorystr yes WF(wildfire),RX(prescribed), etc.POOStatestr yes Point-of-Origin state code ( US-WY)POOCountystr yes Point-of-Origin county name rawobject no The full upstream attributesdict (50+ keys:attr_*andpoly_*from WFIGS) -
Event.data fields (removal):
key type nullable description irwin_idstr no IRWIN UUID of the removed incident statestr yes Last known POOState countystr yes Last known POOCounty reasonstr no fallen_off_current_service(only flavor in v1)last_observed_atstr (ISO 8601) no Timestamp of the last poll that saw this incident -
Decipherable as-is: yes.
IncidentName+POOState+POOCountyare user-ready; the richraw.attr_*surface is for consumers that want full ESRI fidelity. -
Upstream lookup endpoints: the IRWIN incident catalog is browsable at
https://irwin.doi.gov/— convenience only. -
Removal semantics: yes — explicit
…removed.<state>subject when an incident falls off the current-services feed. The dedup-suffixed id (with the:removed:<iso>tail) means the same incident can have one or more removal tombstones over its lifecycle if it re-enters and re-falls-off. -
Live example (verbatim from CT104; this sample is a removal):
{
"id": "{01AAC875-E26E-49E4-9DB0-80B5965A7B9F}:removed:2026-05-19T02:50:39.843049+00:00",
"data": {
"id": "{01AAC875-E26E-49E4-9DB0-80B5965A7B9F}:removed:2026-05-19T02:50:39.843049+00:00",
"geo": {
"bbox": null,
"regions": [
],
"centroid": null,
"primary_region": null
},
"data": {
"state": "US-WY",
"county": "Converse",
"reason": "fallen_off_current_service",
"irwin_id": "{01AAC875-E26E-49E4-9DB0-80B5965A7B9F}",
"last_observed_at": "2026-05-19T02:45:39.359036+00:00"
},
"time": "2026-05-19T02:50:39.843049Z",
"adapter": "wfigs_incidents",
"expires": null,
"category": "fire.incident.removed",
"severity": 0
},
"time": "2026-05-19T02:50:39.843049+00:00",
"type": "central.fire.incident.removed.v1",
"source": "central.echo6.co",
"specversion": "1.0",
"centralcategory": "fire.incident.removed",
"centralseverity": 0,
"datacontenttype": "application/json",
"centralschemaversion": "1.0"
}
wfigs_perimeters — NIFC WFIGS active wildfire perimeter polygons
Perimeter polygon records from the NIFC WFIGS Current FeatureServer. Same incident
metadata as wfigs_incidents but includes the polygon geometry — a GeoJSON
Polygon (or MultiPolygon) of the fire's burned footprint.
WFIGS perimeter polygons range from a few coordinate pairs (small spot fires) to thousands (large complex fires — 100-200 KB per event). The example below is a small perimeter for readability; consumers must handle the full range. Central never simplifies the polygon.
-
Stream:
CENTRAL_FIRE -
Subject pattern:
central.fire.perimeter.<state>.<county>(active) orcentral.fire.perimeter.removed.<state>(removal tombstone) -
Cadence default: 300s
-
Dedup key shape (active):
<IrwinID>— same IRWIN UUID space aswfigs_incidents -
Dedup key shape (removal):
<IrwinID>:removed:<iso_now> -
Event.data fields (active): mirror of
wfigs_incidentsactive fields PLUS:key type nullable description GISAcresfloat yes Polygon's GIS-computed area (different from IncidentSizewhich is the ICS-reported size)geometry.typestr no "Polygon"(or"MultiPolygon"for split fires)geometry.coordinatesnested list no GeoJSON polygon coordinates: [[[lon, lat], [lon, lat], …]]forPolygon; one extra level of nesting forMultiPolygon. Coordinate order is[lon, lat]per GeoJSON.rawobject no Full upstream attributes dict (same as wfigs_incidentsbut with extrapoly_*fields) -
Event.data fields (removal): same as
wfigs_incidentsremoval (no polygon in the tombstone — the incident is gone). -
Decipherable as-is: active records are
yesfor the metadata fields and the centroid; polygon rendering requires a map library. -
Upstream lookup endpoints: none beyond the optional IRWIN catalog.
-
Removal semantics: yes — same shape as
wfigs_incidents. -
Live example (verbatim from CT104; small active perimeter, ~30 coordinate pairs):
{
"id": "{9A457774-B621-49D3-9B56-F30FBC64DD69}",
"data": {
"id": "{9A457774-B621-49D3-9B56-F30FBC64DD69}",
"geo": {
"bbox": [
-113.07673380072,
48.5193411284641,
-113.076188525177,
48.5196908352166
],
"regions": [
"US-US-MT-GLACIER"
],
"centroid": [
-113.0764415581081,
48.51951092534296
],
"primary_region": "US-US-MT-GLACIER"
},
"data": {
"raw": {
"GlobalID": "629df3b7-9f5c-4931-9c34-fc27aa027e21",
"OBJECTID": 53618,
"attr_GACC": "NRCC",
"attr_CpxID": null,
"attr_FORID": null,
"poly_FORID": null,
"Shape__Area": 0.00000007723247108515352,
"attr_Source": "IRWIN",
"poly_Source": "2026 NIFS",
"attr_CpxName": null,
"attr_IrwinID": "{9A457774-B621-49D3-9B56-F30FBC64DD69}",
"attr_POOCity": null,
"attr_POOFips": "30035",
"poly_IRWINID": "{9A457774-B621-49D3-9B56-F30FBC64DD69}",
"Shape__Length": 0.0013087648492907415,
"attr_ABCDMisc": null,
"attr_FireCode": "SFRF",
"attr_POOState": "US-MT",
"poly_GISAcres": 0.16,
"attr_FSJobCode": null,
"attr_FireCause": null,
"attr_POOCounty": "Glacier",
"attr_SourceOID": 42629207,
"poly_IsVisible": "Yes",
"poly_MapMethod": "Hand Sketch",
"poly_SourceOID": 16522,
"attr_FinalAcres": null,
"attr_IsCpxChild": 0,
"attr_IsTrespass": 0,
"poly_CreateDate": 1778878644780,
"poly_DeleteThis": "No",
"poly_DateCurrent": 1778878644780,
"attr_IncidentName": "VAPE",
"attr_IncidentSize": null,
"attr_IsFSAssisted": 0,
"poly_IncidentName": "VAPE",
"poly_FeatureAccess": "Public",
"poly_FeatureStatus": "Approved",
"attr_DiscoveryAcres": null,
"attr_FSOverrideCode": null,
"attr_IsReimbursable": 0,
"attr_SourceGlobalID": "{9A457774-B621-49D3-9B56-F30FBC64DD69}",
"poly_Acres_AutoCalc": 0.15677346144478277,
"poly_SourceGlobalID": "{690C64DD-C03C-4BD4-86EF-3B0E4C93814E}",
"attr_CalculatedAcres": null,
"attr_ControlDateTime": null,
"attr_CreatedBySystem": "IROC",
"attr_FireOutDateTime": null,
"attr_InitialLatitude": null,
"attr_POOLegalDescQtr": null,
"poly_FeatureCategory": "Wildfire Daily Fire Perimeter",
"poly_PolygonDateTime": 1778878643567,
"attr_DispatchCenterID": "MTBFAC",
"attr_FireCauseGeneral": null,
"attr_FireDepartmentID": null,
"attr_IncidentTypeKind": "FI",
"attr_InitialLongitude": null,
"attr_IsUnifiedCommand": null,
"attr_ModifiedBySystem": "INFORM_Inspector",
"attr_POOLandownerKind": "Other",
"attr_PercentContained": null,
"attr_PrimaryFuelModel": null,
"attr_FireCauseSpecific": null,
"attr_POOLegalDescRange": null,
"attr_POOProtectingUnit": "MTBFA",
"attr_ADSPermissionState": "DEFAULT",
"attr_EstimatedFinalCost": null,
"attr_FireMgmtComplexity": null,
"attr_ICS209ReportStatus": null,
"attr_POOLegalDescQtrQtr": null,
"attr_SecondaryFuelModel": null,
"attr_ActiveFireCandidate": 1,
"attr_ContainmentDateTime": null,
"attr_EstimatedCostToDate": null,
"attr_FireBehaviorGeneral": null,
"attr_IsFireCodeRequested": 0,
"attr_POODispatchCenterID": "MTBFAC",
"attr_POOLegalDescSection": null,
"attr_POOProtectingAgency": "BIA",
"attr_WFDSSDecisionStatus": "No Decision",
"attr_CreatedOnDateTime_dt": 1768408389677,
"attr_FFReportApprovedDate": null,
"attr_FireBehaviorGeneral1": null,
"attr_FireBehaviorGeneral2": null,
"attr_FireBehaviorGeneral3": null,
"attr_ICS209ReportDateTime": null,
"attr_IncidentTypeCategory": "WF",
"attr_InitialResponseAcres": null,
"attr_POOLandownerCategory": "Tribal",
"attr_POOLegalDescTownship": null,
"attr_PredominantFuelGroup": null,
"attr_PredominantFuelModel": null,
"attr_UniqueFireIdentifier": "2026-MTBFA-000001",
"attr_FireDiscoveryDateTime": 1767294000000,
"attr_IncidentManagementOrg": null,
"attr_IsMultiJurisdictional": 0,
"attr_ModifiedOnDateTime_dt": 1779116357560,
"attr_POOJurisdictionalUnit": "MTBFA",
"attr_FFReportApprovedByUnit": null,
"attr_TotalIncidentPersonnel": null,
"attr_FFReportApprovedByTitle": null,
"attr_IncidentComplexityLevel": null,
"attr_InitialResponseDateTime": null,
"attr_IsFireCauseInvestigated": null,
"attr_LocalIncidentIdentifier": "000001",
"attr_POOJurisdictionalAgency": "BIA",
"attr_ICS209RptForTimePeriodTo": null,
"attr_IncidentShortDescription": null,
"attr_OrganizationalAssessment": null,
"attr_StratDecisionPublishDate": null,
"attr_FireStrategyFullSuppPrcnt": null,
"attr_PercentPerimToBeContained": null,
"attr_FireStrategyConfinePercent": null,
"attr_FireStrategyMonitorPercent": null,
"attr_FireStrategyPointZonePrcnt": null,
"attr_ICS209RptForTimePeriodFrom": null,
"attr_POOJurisdictUnitParentUnit": null,
"attr_POOLegalDescPrincipalMerid": null,
"attr_POOPredictiveServiceAreaID": "NR10"
},
"IrwinID": "{9A457774-B621-49D3-9B56-F30FBC64DD69}",
"GISAcres": 0.16,
"POOState": "US-MT",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-113.076508766265,
48.5195532461849
],
[
-113.076327007314,
48.5196908352166
],
[
-113.076223143747,
48.5196335063813
],
[
-113.076188525177,
48.5195647117801
],
[
-113.076569351709,
48.5193411284641
],
[
-113.07673380072,
48.5194271222974
],
[
-113.076508766265,
48.5195532461849
]
]
]
},
"POOCounty": "Glacier",
"DailyAcres": null,
"IncidentName": "VAPE",
"PercentContained": null,
"ModifiedOnDateTime": 1779116357560,
"IncidentTypeCategory": "WF",
"FireDiscoveryDateTime": 1767294000000
},
"time": "2026-01-01T19:00:00Z",
"adapter": "wfigs_perimeters",
"expires": null,
"category": "fire.perimeter.wf",
"severity": 1
},
"time": "2026-01-01T19:00:00+00:00",
"type": "central.fire.perimeter.wf.v1",
"source": "central.echo6.co",
"specversion": "1.0",
"centralcategory": "fire.perimeter.wf",
"centralseverity": 1,
"datacontenttype": "application/json",
"centralschemaversion": "1.0"
}
swpc_alerts — NOAA SWPC space weather alerts
Active alerts, watches, warnings, and summaries from NOAA's Space Weather Prediction
Center (services.swpc.noaa.gov). Each event is one product issuance — geomagnetic
storm watches (G-scale), radiation storm warnings (S-scale), radio blackouts
(R-scale), or daily summaries. Product body is a fixed-width text block with the
SWPC's signature Space Weather Message Code: … format.
-
Stream:
CENTRAL_SPACE -
Subject pattern:
central.space.alert.<product_id><product_id>is the SWPC product code lowercased (e.g.a20ffor "WATCH: Geomagnetic Storm Category G1 Predicted")
-
Cadence default: 300s
-
Dedup key shape:
<product_id>|<issue_datetime>— the product code plus the issue timestamp. A repeated issuance of the same product (e.g. successive G1 watches) gets a new key per issuance. -
Event.data fields:
key type nullable description product_idstr no SWPC product code (e.g. "A20F")issue_datetimestr no Upstream issue timestamp (string form, sub-second precision) messagestr no Full plain-text alert body — includes scale, impact summary, validity window, and operational guidance -
Decipherable as-is: yes. The
messagefield is the operational text the SWPC publishes to its mailing list. -
Upstream lookup endpoints: product-id taxonomy is documented at
https://www.swpc.noaa.gov/products/alerts-watches-and-warnings. Most consumers rely onmessagealone for display. -
Removal semantics: none. SWPC products supersede each other via the message body ("THIS SUPERSEDES ANY/ALL PRIOR WATCHES IN EFFECT").
-
Live example (verbatim from CT104):
{
"id": "A20F|2026-04-24 23:50:43.280",
"data": {
"id": "A20F|2026-04-24 23:50:43.280",
"geo": {
"bbox": null,
"regions": [
],
"centroid": null,
"primary_region": null
},
"data": {
"message": "Space Weather Message Code: WATA20\r\nSerial Number: 1109\r\nIssue Time: 2026 Apr 24 2350 UTC\r\n\r\nWATCH: Geomagnetic Storm Category G1 Predicted\r\n\r\nHighest Storm Level Predicted by Day:\r\nApr 25: G1 (Minor) Apr 26: G1 (Minor) Apr 27: None (Below G1)\r\n\r\nTHIS SUPERSEDES ANY/ALL PRIOR WATCHES IN EFFECT\r\n\r\nNOAA Space Weather Scale descriptions can be found at\r\nwww.swpc.noaa.gov/noaa-scales-explanation\r\n\r\nPotential Impacts: Area of impact primarily poleward of 60 degrees Geomagnetic Latitude.\r\nInduced Currents - Weak power grid fluctuations can occur.\r\nSpacecraft - Minor impact on satellite operations possible.\r\nAurora - Aurora may be visible at high latitudes, i.e., northern tier of the U.S. such as northern Michigan and Maine.",
"product_id": "A20F",
"issue_datetime": "2026-04-24 23:50:43.280"
},
"time": "2026-04-24T23:50:43.280000Z",
"adapter": "swpc_alerts",
"expires": null,
"category": "space.alert",
"severity": 0
},
"time": "2026-04-24T23:50:43.280000+00:00",
"type": "central.space.alert.v1",
"source": "central.echo6.co",
"specversion": "1.0",
"centralcategory": "space.alert",
"centralseverity": 0,
"datacontenttype": "application/json",
"centralschemaversion": "1.0"
}
swpc_kindex — NOAA SWPC planetary K-index
3-hour planetary K-index measurements from NOAA SWPC. The Kp index is the canonical 0–9 scale for global geomagnetic activity; consumers use it for aurora forecasting, satellite-ops triage, and HF-propagation predictions.
-
Stream:
CENTRAL_SPACE -
Subject pattern:
central.space.kindex(single fixed subject) -
Cadence default: 600s
-
Dedup key shape:
<time_tag>— the ISO timestamp of the 3-hour bin (UTC, bins starting at 00:00, 03:00, 06:00, …, 21:00) -
Event.data fields:
key type nullable description Kpfloat no Planetary K-index (0.0 — 9.0). Fractional values are SWPC's "estimated Kp" form time_tagstr (ISO 8601) no Start of the 3-hour bin a_runningint no Running daily-A index, derived from the trailing Kp measurements station_countint no Number of magnetometer stations contributing to this Kp value -
Decipherable as-is: partial. Numeric scale; the consumer must know the Kp interpretation:
0–3= quiet,4= unsettled,5+= G1+ geomagnetic storm. No upstream lookup required. -
Upstream lookup endpoints: none.
-
Removal semantics: none. K-index values are temporal observations; no removal flow.
-
Live example (verbatim from CT104):
{
"id": "2026-05-12T00:00:00",
"data": {
"id": "2026-05-12T00:00:00",
"geo": {
"bbox": null,
"regions": [
],
"centroid": null,
"primary_region": null
},
"data": {
"Kp": 0.67,
"time_tag": "2026-05-12T00:00:00",
"a_running": 3,
"station_count": 8
},
"time": "2026-05-12T00:00:00Z",
"adapter": "swpc_kindex",
"expires": null,
"category": "space.kindex",
"severity": 0
},
"time": "2026-05-12T00:00:00+00:00",
"type": "central.space.kindex.v1",
"source": "central.echo6.co",
"specversion": "1.0",
"centralcategory": "space.kindex",
"centralseverity": 0,
"datacontenttype": "application/json",
"centralschemaversion": "1.0"
}
swpc_protons — NOAA SWPC GOES proton flux
Integral proton flux measurements at three energy thresholds (≥10, ≥50, ≥100 MeV) from the GOES primary satellite. Used for solar-radiation-storm (S-scale) nowcasting, satellite-mode planning, and HF-comms operator decisions.
-
Stream:
CENTRAL_SPACE -
Subject pattern:
central.space.proton_flux(single fixed subject) -
Cadence default: 600s
-
Dedup key shape:
<time_tag>|<energy>— the measurement timestamp plus the energy threshold. Three events per cadence (one per energy band). -
Event.data fields:
key type nullable description fluxfloat no Integral proton flux in pfu (particles · cm⁻² · s⁻¹ · sr⁻¹) energystr no Energy threshold label: ">=10 MeV",">=50 MeV",">=100 MeV"time_tagstr (ISO 8601) no Measurement timestamp satelliteint no GOES satellite number (currently 19= GOES-19 primary) -
Decipherable as-is: partial. Numeric flux; consumer must know the S-radiation-storm thresholds: S1 ≥ 10 pfu at ≥10 MeV; S2 ≥ 100; S3 ≥ 1000; S4 ≥ 10⁴; S5 ≥ 10⁵. No upstream lookup required.
-
Upstream lookup endpoints: none.
-
Removal semantics: none.
-
Live example (verbatim from CT104):
{
"id": "2026-05-18T05:55:00Z|>=100 MeV",
"data": {
"id": "2026-05-18T05:55:00Z|>=100 MeV",
"geo": {
"bbox": null,
"regions": [
],
"centroid": null,
"primary_region": null
},
"data": {
"flux": 0.16147682070732117,
"energy": ">=100 MeV",
"time_tag": "2026-05-18T05:55:00Z",
"satellite": 19
},
"time": "2026-05-18T05:55:00Z",
"adapter": "swpc_protons",
"expires": null,
"category": "space.proton_flux",
"severity": 0
},
"time": "2026-05-18T05:55:00+00:00",
"type": "central.space.proton_flux.v1",
"source": "central.echo6.co",
"specversion": "1.0",
"centralcategory": "space.proton_flux",
"centralseverity": 0,
"datacontenttype": "application/json",
"centralschemaversion": "1.0"
}
gdacs — Global Disaster Alert and Coordination System
Humanitarian-triage alerts from gdacs.org. Multi-hazard: wildfires (WF), floods
(FL), tropical cyclones (TC), volcanoes (VO), droughts (DR). Earthquakes (EQ)
are filtered out by Central's default settings because USGS is canonical for
quakes on central.quake.>; operators may re-enable EQ via the adapter's
settings if needed.
GDACS triage signals: alertlevel (Green/Orange/Red) and alertscore (numeric)
classify humanitarian impact, NOT severity of the physical event.
-
Stream:
CENTRAL_DISASTER -
Subject pattern:
central.disaster.<eventtype>.<country>(active) orcentral.disaster.<eventtype>.removed.<country>(removal tombstone)<eventtype>is the GDACS 2-letter code lowercased<country>is the country name lowercased + hyphenated (e.g.united-states,solomon-islands), orunknown
-
Cadence default: 600s
-
Dedup key shape:
<guid>for active events,<guid>:removedfor tombstones -
Event.data fields:
key type nullable description guidstr no GDACS event id (e.g. "FL1103786","WF1028708")eventidstr no Numeric event id without the type prefix eventtypestr no Two-letter hazard code ( WF,FL,TC,VO,DR)titlestr no Pre-formatted alert title descriptionstr no Plain-text event description countrystr yes Affected country name iso3str yes ISO 3166-1 alpha-3 country code alertlevelstr yes Green/Orange/Red— humanitarian-impact classificationalertscorestr yes Numeric alert score iscurrentbool no truefor active events; transitions tofalsewhen GDACS retires the event — Central emits a tombstone in that caseurlstr no GDACS event page datemodifiedstr yes RFC 822 timestamp of the last upstream modification -
Decipherable as-is: yes.
title+description+country+alertlevelare user-ready. The<eventtype>codes are a small enumeration consumers typically have a lookup table for. -
Upstream lookup endpoints: ISO 3166-1 alpha-3 codes (
iso3) are universally available client-side. Detailed event analysis:https://www.gdacs.org/report.aspx?eventtype=<TYPE>&eventid=<id>. -
Removal semantics: yes — when GDACS sets
iscurrent: falseOR removes an event entirely from the feed, Central emitscentral.disaster.<eventtype>.removed.<country>withdata.reason="iscurrent_false"or"missing_from_feed". Subtype appears beforeremovedper the §8 canonical pattern. -
Live example (verbatim from CT104):
{
"id": "FL1103786",
"data": {
"id": "FL1103786",
"geo": {
"bbox": [
109.2903307,
-5.499583,
117.2903307,
2.500417
],
"regions": [
"IDN"
],
"centroid": [
113.2903307,
-1.499583
],
"primary_region": "IDN"
},
"data": {
"url": "https://www.gdacs.org/report.aspx?eventtype=FL&eventid=1103786",
"guid": "FL1103786",
"iso3": "IDN",
"title": "Green flood alert in Indonesia",
"country": "Indonesia",
"eventid": "1103786",
"eventtype": "FL",
"iscurrent": true,
"alertlevel": "Green",
"alertscore": "1",
"description": "On 02/03/2026, a flood started in Indonesia, lasting until 17/05/2026 (last update). The flood caused 19 deaths and 1624 displaced .",
"datemodified": "Tue, 19 May 2026 05:36:02 GMT"
},
"time": "2026-03-02T01:00:00Z",
"adapter": "gdacs",
"expires": null,
"category": "disaster.fl",
"severity": 1
},
"time": "2026-03-02T01:00:00+00:00",
"type": "central.disaster.fl.v1",
"source": "central.echo6.co",
"specversion": "1.0",
"centralcategory": "disaster.fl",
"centralseverity": 1,
"datacontenttype": "application/json",
"centralschemaversion": "1.0"
}
eonet — NASA EONET Earth Observatory Natural Event Tracker
Natural-event tracker from eonet.gsfc.nasa.gov. Multi-category (wildfires, severe
storms, volcanoes, sea/lake ice, dust/haze, drought, …). Often re-publishes events
from upstream sources like GDACS, IRWIN, JTWC, SIVolcano — see data.sources[] to
see where each EONET event originally came from.
A 200-event sample in May 2026 showed ~77.5% of EONET wildfires / floods /
severeStorms / volcanoes were GDACS-sourced — operators with the GDACS adapter
already running can disable those overlap categories via EONETSettings.category_allowlist.
-
Stream:
CENTRAL_DISASTER -
Subject pattern:
central.disaster.eonet.<category>.global(active) orcentral.disaster.eonet.<category>.removed.global(removal tombstone)<category>is the EONET upstream category id lower_snake_case'd (wildfires,sea_lake_ice,severe_storms,dust_haze,temp_extremes,water_color,earthquakes,landslides,floods,manmade,volcanoes,snow,drought)- The trailing
.globalis the literal country-equivalent suffix — EONET has no clean per-country tagging, and reverse-geocode was deemed scope creep for v1
-
Cadence default: 1800s (30 min — slow-moving)
-
Dedup key shape: composite
eonet:<event_id>:<latest_geometry_date_iso>- The geometry date is the timestamp of the most recent point in EONET's
geometry[]timeline; a new geometry point (storm path advances, iceberg drifts) bumps the dedup key and re-publishes the event with updated geo - Inner
Event.idis just<event_id>(e.g."EONET_10000") — stable across timeline updates so consumers can correlate
- The geometry date is the timestamp of the most recent point in EONET's
-
Event.data fields:
key type nullable description event_idstr no EONET id ( "EONET_NNNNN")category_idstr no EONET category id, camelCase (e.g. "seaLakeIce") — NOTE: this is the upstream camelCase form, distinct from the snake-case form in the subject tokentitlestr no Event title descriptionstr yes Often empty — EONET tends not to populate this urlstr no EONET event page closedstr (ISO 8601) yes When the event was closed by EONET, or nullif still open. Default poll excludes closed events; this field will be null on the regular subjectssourceslist[{id, url}] no (may be empty) Upstream attributions — idis the source key (e.g."GDACS","IRWIN");urlresolves to the source's own recordmagnitudeValuefloat yes Optional upstream magnitude (e.g. wildfire acres) — present on some categories, null on most magnitudeUnitstr yes Units for magnitudeValue(e.g."acres")latest_geometry_datestr (ISO 8601) yes Same value used in the composite dedup key — exposed so consumers can correlate without parsing the key -
Decipherable as-is: yes.
title+category_id+sources+urlare user-ready. -
Upstream lookup endpoints:
data.sources[].urlresolves to each upstream's own record (GDACS → gdacs.org, JTWC → metoc.navy.mil, etc.) — convenience only. -
Removal semantics: yes — when an event present in a prior poll is no longer in the current poll, Central emits
central.disaster.eonet.<category>.removed.globalwithdata.reason="missing_from_feed". Subtype beforeremovedper §8. -
Live example (verbatim from CT104):
{
"id": "EONET_10000",
"data": {
"id": "EONET_10000",
"geo": {
"bbox": null,
"regions": [
],
"centroid": [
18.993001445418138,
-9.441178609331619
],
"primary_region": null
},
"data": {
"url": "https://eonet.gsfc.nasa.gov/api/v3/events/EONET_10000",
"title": "Wildfire in Angola 1021215",
"closed": null,
"sources": [
{
"id": "GDACS",
"url": "https://www.gdacs.org/report.aspx?eventtype=WF&eventid=1021215"
}
],
"event_id": "EONET_10000",
"category_id": "wildfires",
"description": "",
"magnitudeUnit": null,
"magnitudeValue": null,
"latest_geometry_date": "2024-08-06T19:00:00Z"
},
"time": "2024-08-06T19:00:00Z",
"adapter": "eonet",
"expires": null,
"category": "disaster.eonet.wildfires",
"severity": 0
},
"time": "2024-08-06T19:00:00+00:00",
"type": "central.disaster.eonet.wildfires.v1",
"source": "central.echo6.co",
"specversion": "1.0",
"centralcategory": "disaster.eonet.wildfires",
"centralseverity": 0,
"datacontenttype": "application/json",
"centralschemaversion": "1.0"
}
nwis — USGS NWIS streamflow / gage height / water-temperature gauges
Real-time water-data observations via the USGS NWIS OGC API v0 latest-continuous
collection. Each event is one parameter reading at one gauge: discharge (ft³/s)
at parameter 00060, gage height (ft) at 00065, water temperature (°C) at
00010, plus whatever else the operator's parameter_codes setting includes.
-
Stream:
CENTRAL_HYDRO -
Subject pattern:
central.hydro.<parameter_code>.<agency>.<bare_site_no><parameter_code>is the 5-digit zero-padded USGS pcode<agency>is the lowercased agency prefix frommonitoring_location_id(typicallyusgs; can be cooperator codes likemo005for Missouri Geological Survey wells)<bare_site_no>is the site number with the agency prefix stripped
-
Cadence default: 900s (15 min)
-
Dedup key shape: composite
nwis:<monitoring_location_id>:<parameter_code>:<time_iso>- Site identifier kept agency-prefixed in the dedup key for cross-agency uniqueness
- Same shape is reused as the inner
Event.id
-
Event.data fields:
key type nullable description monitoring_location_idstr no Agency-prefixed site id (e.g. "USGS-06898000")parameter_codestr no 5-digit USGS pcode timestr (ISO 8601 UTC) no Measurement timestamp valuefloat no Measurement value unit_of_measurestr yes e.g. "ft^3/s","ft","deg C"statistic_idstr yes USGS statistic code; "00011"= instantaneousapproval_statusstr yes "Provisional"or"Approved"qualifierlist[str] or null yes Quality flags (e.g. ["ESTIMATED"])time_series_idstr yes Stable per (site, parameter)pairlast_modifiedstr (ISO 8601) yes Upstream record-modification timestamp -
Decipherable as-is: partial.
value+unit_of_measure+approval_statusqualifierare user-ready. Site name + parameter description require upstream lookup.
-
Upstream lookup endpoints (consumer convenience):
- Site name + state + lat/lon:
https://api.waterdata.usgs.gov/ogcapi/v0/collections/monitoring-locations/items?monitoring_location_id=<id> - Parameter description + units:
https://api.waterdata.usgs.gov/ogcapi/v0/collections/parameter-codes/items?id=<code> - Statistic code description:
https://api.waterdata.usgs.gov/ogcapi/v0/collections/statistic-codes/items?id=<code> - Central does NOT dereference these. Consumers MAY pre-cache the small parameter / statistic enumerations and resolve site names on demand.
- Site name + state + lat/lon:
-
Removal semantics: none in v1. Sites are static; missing data is the signal if a gauge breaks. Consumers should watch for stale
timevalues, not subscribe to a removal subject. -
Live example (verbatim from CT104):
{
"id": "USGS-06898000:00060:2026-05-19T16:00:00+00:00",
"data": {
"id": "USGS-06898000:00060:2026-05-19T16:00:00+00:00",
"geo": {
"bbox": null,
"regions": [
],
"centroid": [
-93.8080555555556,
40.6402777777778
],
"primary_region": null
},
"data": {
"time": "2026-05-19T16:00:00+00:00",
"value": 11100.0,
"qualifier": null,
"statistic_id": "00011",
"last_modified": "2026-05-19T16:18:24.328435+00:00",
"parameter_code": "00060",
"time_series_id": "dc651a5e73174625a7ec4243d25b6154",
"approval_status": "Provisional",
"unit_of_measure": "ft^3/s",
"monitoring_location_id": "USGS-06898000"
},
"time": "2026-05-19T16:00:00Z",
"adapter": "nwis",
"expires": null,
"category": "hydro.00060.usgs.06898000",
"severity": 0
},
"time": "2026-05-19T16:00:00+00:00",
"type": "central.hydro.00060.usgs.06898000.v1",
"source": "central.echo6.co",
"specversion": "1.0",
"centralcategory": "hydro.00060.usgs.06898000",
"centralseverity": 0,
"datacontenttype": "application/json",
"centralschemaversion": "1.0"
}
\
7. Fall-off / removal semantics
Central adapters fall into three buckets for handling upstream events that disappear:
7a. Explicit removal subjects (consumer must subscribe to handle cleanly)
| Adapter | Removal subject | data.reason values |
Notes |
|---|---|---|---|
gdacs |
central.disaster.<eventtype>.removed.<country> |
iscurrent_false, missing_from_feed |
Two flavors: upstream explicit retirement (iscurrent: false), or simply absent from the next poll. Subscriber filtering on central.disaster.wf.> catches central.disaster.wf.removed.<country> automatically (subtype before removed). |
eonet |
central.disaster.eonet.<category>.removed.global |
missing_from_feed |
Emitted when an event present in a prior poll is absent in the current poll. EONET treats closure as removal. |
wfigs_incidents |
central.fire.incident.removed.<state> |
fallen_off_current_service |
Emitted when an incident leaves WFIGS Current FeatureServer. |
wfigs_perimeters |
central.fire.perimeter.removed.<state> |
fallen_off_current_service |
Same flow as incidents. |
Canonical removal subject shape: <subtype>.removed.<region>. Subtype appears
BEFORE removed so subscribers filtering on <prefix>.<subtype>.> receive both
live and removal events without a second subscription.
7b. Absence-as-signal (no removal subject — consumer infers from time gap)
| Adapter | Inference |
|---|---|
nwis |
A site stops publishing if its sensor goes offline. Watch for stale Event.data.time values. |
swpc_kindex |
Skipping a 3-hour bin means SWPC didn't publish in time. Re-poll catches up. |
swpc_protons |
Same as kindex but on the configured cadence. |
nws |
Alert expiry signaled by Event.data.expires; cancellation by messageType: Cancel. |
inciweb |
Incidents persist in the RSS feed indefinitely; filter on raw.pubDate recency client-side. |
firms |
Each hotspot detection is a one-shot pixel observation; subsequent overpasses publish new pixels. No "this fire is now out" signal. |
usgs_quake |
Events persist in the catalog; status automatic → reviewed is an in-place update, not a removal. |
swpc_alerts |
Alert supersession is conveyed by message text (THIS SUPERSEDES ANY/ALL PRIOR WATCHES IN EFFECT). |
7c. Removal-event payload schema (cross-cutting)
Removal events ship with an empty geo block and a sparse data payload
(schema shape — placeholder values in <angle-brackets>):
{
"id": "<original_id>:removed[:<iso_now>]",
"adapter": "<adapter_name>",
"category": "<subtype>.removed",
"time": "<removal-detection-time>",
"severity": 0,
"geo": { "centroid": null, "bbox": null, "regions": [], "primary_region": null },
"data": {
"<id_field>": "<original_id>",
"reason": "iscurrent_false | missing_from_feed | fallen_off_current_service",
"last_observed_at": "<iso 8601>",
"<adapter-specific context, e.g. country, state, county>": "..."
}
}
Consumers MUST handle removal events even when their primary subject filter includes them implicitly — otherwise stale entries accumulate in the consumer's local mirror.
8. Consumer patterns
8a. Durable vs ephemeral consumers
Durable (persistent, recoverable across reconnects — recommended for almost every use case):
sub = await js.subscribe(
"central.fire.>",
durable="my-fire-watcher",
manual_ack=True,
config=ConsumerConfig(
ack_policy=AckPolicy.EXPLICIT,
max_deliver=5,
ack_wait=30, # seconds before nack-and-redeliver
max_ack_pending=1000, # cap on in-flight unacked messages
),
)
JetStream remembers the durable consumer's position; restarts resume from the last ack'd message.
Ephemeral (consumer goes away when subscription closes — useful for one-off catch-up or admin tooling):
sub = await js.subscribe("central.>") # no durable=
8b. Ack / nack / term semantics
| Operation | Effect | When to use |
|---|---|---|
await msg.ack() |
Confirm successful processing | After your handler runs without error |
await msg.nak() |
Negative ack — JetStream redelivers immediately | Transient failure (DB connection blip) |
await msg.nak(delay=N) |
Nak with backoff | Repeated transient failure — combine with exponential backoff |
await msg.term() |
Terminate — JetStream does NOT redeliver | Permanently bad message (poison-pill JSON, malformed schema) |
(no ack within ack_wait) |
JetStream redelivers after timeout | Crash / hang — let JetStream catch you |
8c. Worked example — durable consumer with manual ack, parse failure handling, reconnect
import asyncio
import json
import logging
import nats
from nats.errors import TimeoutError
from nats.js.api import AckPolicy, ConsumerConfig
logger = logging.getLogger("fire-consumer")
async def handle(msg) -> None:
try:
envelope = json.loads(msg.data)
except json.JSONDecodeError:
logger.exception("malformed envelope; terminating", extra={"subject": msg.subject})
await msg.term()
return
try:
# Inner Event payload — see §5b
event = envelope["data"]
# ... your business logic ...
await store_in_local_db(event)
except Exception:
logger.exception("handler failure; nak for redelivery", extra={"id": envelope.get("id")})
await msg.nak(delay=10)
return
await msg.ack()
async def run() -> None:
nc = await nats.connect(
"nats://central.local:4222",
max_reconnect_attempts=-1, # reconnect forever
reconnect_time_wait=2,
disconnected_cb=lambda: logger.warning("disconnected"),
reconnected_cb=lambda: logger.info("reconnected"),
)
js = nc.jetstream()
sub = await js.subscribe(
"central.fire.>",
durable="my-fire-watcher",
manual_ack=True,
config=ConsumerConfig(
ack_policy=AckPolicy.EXPLICIT,
max_deliver=5,
ack_wait=30,
max_ack_pending=1000,
),
)
while True:
try:
msg = await sub.next_msg(timeout=10)
except TimeoutError:
continue
await handle(msg)
asyncio.run(run())
8d. Choosing a subject filter
| Goal | Subject filter |
|---|---|
| All Central events | central.> |
| One stream domain | central.fire.> |
| One adapter family | central.fire.hotspot.> (firms only) |
| Cross-adapter, same hazard | central.disaster.> (gdacs + eonet) |
| One specific subtype across countries | central.disaster.wf.> (gdacs WF active + removed) |
| One specific subtype on EONET only | central.disaster.eonet.wildfires.> |
| Active events only (suppress removals) | Subscribe to the broad filter and filter client-side on Event.category not ending in .removed — JetStream subject wildcards don't have negation |
9. Dedup implementation guide
Central guarantees an Event.id that is stable across re-publishes. Use that as
the primary dedup key. JetStream also sets a Nats-Msg-Id header on every
published message (same value as Event.id); the JetStream server has its own
dedup window for at-least-once delivery, but consumers SHOULD NOT rely on that
alone — implement application-level dedup keyed on Event.id.
9a. Single-token stable IDs (most adapters)
Adapters whose Event.id is itself a stable single token:
| Adapter | Event.id example | Source |
|---|---|---|
firms |
VIIRS_NOAA20_NRT:2026-05-17:2008:33.593:-105.314 |
Composite of (satellite, date, time, rounded lat/lon) |
nws |
https://api.weather.gov/alerts/urn:oid:… |
NWS alert URL |
usgs_quake |
ci10240102, nc75361901 |
USGS feature id |
gdacs |
WF1028708, FL1103786 |
GDACS RSS guid |
inciweb |
312546 |
InciWeb numeric incident id |
wfigs_incidents |
{01AAC875-…} |
IRWIN UUID |
wfigs_perimeters |
{01AAC875-…} |
IRWIN UUID |
swpc_alerts |
`A20F | 2026-04-24 23:50:43.280` |
swpc_kindex |
2026-05-12T00:00:00 |
Bin timestamp |
swpc_protons |
`2026-05-18T05:55:00Z | >=100 MeV` |
Implementation pattern: hash Event.id into a TTL-bounded set. In-memory LRU
is fine for low-volume consumers; Redis SETEX for distributed; Postgres unique
constraint for ingest pipelines.
9b. Composite-key adapters (timeline-aware)
| Adapter | Event.id shape | Why composite |
|---|---|---|
eonet |
EONET_NNNNN (stable across timeline updates); composite dedup tracked in cursors.db as eonet:<event_id>:<latest_geometry_date_iso> |
Consumers see a stable id; Central's own dedup advances when the upstream timeline (geometry-point dates) advances. Consumers usually want to treat re-published events as updates, not duplicates — index by Event.id and overwrite, OR key on Event.id + Event.data.latest_geometry_date if you want to preserve every timeline point |
nwis |
<monitoring_location_id>:<parameter_code>:<time_iso> |
Each (site, param, measurement-time) triple is distinct — dedup on the full triple to avoid collapsing distinct measurements |
wfigs_incidents / wfigs_perimeters removals |
<IrwinID>:removed:<iso_now> |
The same incident can have multiple removal events over time (re-enter / re-fall-off); dedup must include the removal-detection timestamp |
9c. Recommended implementation
from collections import OrderedDict
class TTLDedup:
"""Bounded LRU dedup with TTL — appropriate for single-process consumers."""
def __init__(self, max_entries: int = 100_000, ttl_seconds: int = 86400):
self._seen: OrderedDict[str, float] = OrderedDict()
self._max = max_entries
self._ttl = ttl_seconds
def is_dup(self, event_id: str, now: float) -> bool:
# Evict expired
while self._seen and (next(iter(self._seen.values())) + self._ttl) < now:
self._seen.popitem(last=False)
if event_id in self._seen:
self._seen.move_to_end(event_id)
return True
# Evict oldest if over cap
if len(self._seen) >= self._max:
self._seen.popitem(last=False)
self._seen[event_id] = now
return False
For multi-process / multi-host consumers, swap the in-memory dict for Redis with
SET <key> 1 EX <ttl> NX (returns nil if the key already exists).
10. Writing a new consumer — checklist
- Pick subject filters. What's the smallest set of subjects that gives you what you need? Narrower = less work for your consumer. See §8d.
- Pick a durable consumer name. It must be unique per consumer instance; re-using a name from a different instance silently shares position state.
- Choose your ack policy. Default to
EXPLICIT+ manual ack — gives you redelivery control on failures. - Configure delivery limits.
max_deliver=5andmax_ack_pending=1000are reasonable starting points; tune based on throughput and handler latency. - Pick a dedup strategy. See §9. Key on
Event.idunless you have a specific reason to use the composite shape. - Implement parse-failure handling. Malformed JSON →
msg.term()(poison message); handler exception →msg.nak(delay=N)with exponential backoff; crash → letack_waitexpire and JetStream redelivers. - Implement reconnect logic.
nats-pyreconnects automatically; configuremax_reconnect_attempts=-1to retry forever. Adddisconnected_cb/reconnected_cbfor logging. - Handle removal events for adapters in §7a. Clear local state when you see them.
- Handle the
[lon, lat]axis order when persisting geometries to PostGIS or rendering on a map. Test with a known location (e.g.[-93.72, 43.11]= Iowa, NOT off the coast of West Africa). - Test with a frozen sample. Pin one or two real CloudEvents JSON payloads in your test fixtures; assert your deserializer + business logic handle them end-to-end.
11. Troubleshooting
No events arriving
- Stream filter typo.
central.fires.>(plural) silently matches nothing. Compare your filter against §3. - JetStream not enabled.
nats stream lsfrom the operator's box should list seven streams. If empty, Central's supervisor hasn't created them yet. - Auth fail.
nats-pyraisesnats.errors.NoServersErrororAuthorizationError. Checktoken/nkeys_seed/user+password. - No producer events yet. Some adapters (e.g. EONET) have low cadence;
others depend on upstream activity.
nats stream view CENTRAL_WXshows the last message timestamp — if it's hours stale, the adapter may be misconfigured or the upstream is quiet. - Wrong durable name. A durable consumer with no recent activity may have exceeded the server's inactive-threshold and been deleted. Re-create.
Duplicate events
- Dedup not running. Verify your
Event.id-keyed dedup set is wired up. - Wrong key shape. EONET / NWIS use composite keys — see §9b.
- Consumer ack timeout. If your handler is slow, JetStream may redeliver
before you finish. Bump
ack_waitor move slow work to a background task and ack immediately. - Re-published timeline updates (EONET). New
latest_geometry_date→ Central re-publishes under the sameEvent.id. Treat as an UPDATE, not a duplicate; dedup on the innerEvent.idand overwrite local state.
Geometry confused
[lon, lat]axis order. Single most common bug.Event.geo.centroidis[lon, lat]per GeoJSON, NOT[lat, lon]. Test with a known western / northern hemisphere location.- Polygon coordinates in
wfigs_perimeters.data.geometry.coordinatesare GeoJSON-nested:Polygonis[[[lon, lat], ...]](three brackets);MultiPolygonis[[[[lon, lat], ...]]](four). PostGIS / Shapely expect this — most consumers just pass through.
Timestamps
Event.timeis the upstream event time, NOT the time Central ingested or published. For ingest time, seeEvent.data.last_modifiedorreceivedon the events DB row (Central-internal, not in the envelope).- All Central-emitted timestamps are UTC. Upstream timestamps may include
offsets (e.g. NWS
sent: "2026-05-19T11:53:00-07:00"); theEvent.timefield normalizes to UTC ISO 8601, butEvent.data.*fields preserve the original upstream form.
Schema changes
- The CloudEvents extension
centralschemaversion(currently"1.0") bumps when Central's innerEventshape changes in a non-backwards-compatible way. Consumers should pin against a schema version they've tested with and upgrade deliberately. - Adapter-specific field additions (new keys in
Event.data) are non-breaking and do NOT bump the schema version — consumers should tolerate unknown keys.
Generated from real artifacts on CT104 — evidence.txt (12 adapter JSON blocks
pulled via psql) + wfigs_perimeter_small.json (smallest active perimeter polygon
on CT104 at the time of writing). Every JSON example is byte-identical to what
arrived on NATS. If this doc and the code disagree, the code is the truth — but
tests/test_consumer_doc.py should catch the drift before merge.