Base URL: https://api.wxalerts.org
Every NWS watch, warning, advisory and statement in effect, as the ingest merges them from NWWS-OI, EMWIN and the api.weather.gov CAP feed. Ask what is in effect at a point, everything inside a map view, the few most serious alerts in the country, or a view chosen for you.
GET /alerts/at?lat=35.2&lon=-97.4
GET /alerts/in-bounds?west=-100&south=30&east=-90&north=40
GET /alerts/ended-in-bounds?west=-100&south=30&east=-90&north=40&minutes=70
GET /alerts/highest-severity?limit=3
GET /alerts/focus?limit=25&strategy=auto
GET /alerts/{identifier}
GET /alerts/page/{short_id}
GET /alerts/page/{short_id}/rundown
No key needed. Browsers block calls from other websites (CORS allows only wxalerts.org), so call it from a server, script or app. These routes are limited per address as described under rate limits.
The whole national feed is the exception:
GET /v1/alerts/active?event=&office=&severity=&limit=500&offset=0
GET /v1/alerts/active/geojson?event=&severity=
Needs an API key (alerts:read). On the bare path these
two answer 401 with code api_key_required. Without a key, an in-bounds
box around the area you care about gives you the same alerts.
This is polling. To have alerts pushed to you the moment they are issued, and told when they end, use the MQTT alerts feed.
Color by significance, not severity
Each alert carries a CAP severity and a VTEC significance. Color and rank by
significance: it is the axis the NWS asks people to read. CAP severity is too
coarse to sort on. A Tornado Warning, a Flood Warning and a Winter Storm Watch
can all be Severe, plenty of warnings are only Moderate, and a Special
Weather Statement is Moderate too, which is not a watch.
significance |
Meaning |
|---|---|
W |
Warning: occurring or imminent |
A |
Watch: conditions favourable |
Y |
Advisory: less serious |
S |
Statement |
significance and phenomena are null on products with no VTEC, chiefly
Special Weather Statements and Marine Weather Statements. Fall back to CAP
severity for those (Extreme reads as a warning, Severe as a watch,
Moderate as an advisory), which is what the API’s own ranking does.
The ranking. Routes that return alerts “most serious first” sort by
significance, then by hazard (tornado and tsunami, then storm surge, then
flash flood, severe thunderstorm and hurricane, down through winter, fire and
flood to marine and beach hazards), then by CAP urgency, then newest. A Flood
Warning outranks a Tornado Watch on purpose: a warning is something happening.
The alert object
/alerts/at, /alerts/{identifier} and /v1/alerts/active return alerts
without geometry, in this shape. Times are ISO 8601, UTC.
{
"id": 28449246,
"vtec_key": "PAFC.BZ.W.0014.2026",
"cap_id": null,
"event": "Blizzard Warning",
"office": "PAFC",
"phenomena": "BZ",
"significance": "W",
"etn": 14,
"status": "active",
"severity": "Severe",
"urgency": "Immediate",
"certainty": "Observed",
"issued_at": "2026-11-09T14:15:00Z",
"onset": "2026-11-09T20:00:00Z",
"expires": "2026-11-10T01:00:00Z",
"ends": "2026-11-10T21:00:00Z",
"ugc": ["AKZ732"],
"same": ["002063"],
"headline": "BLIZZARD WARNING REMAINS IN EFFECT FROM 11 AM THIS MORNING TO NOON AKST TUESDAY",
"description": "WWAK41 PAFC 091415\n\nWSWAER\n\nURGENT - WINTER WEATHER MESSAGE ...",
"instruction": "A Blizzard Warning means severe winter weather conditions are ...",
"geom_source": "ugc",
"sources": ["api", "emwin", "nwws"],
"updated_at": "2026-11-09T19:06:52.767357Z",
"short_id": "8k3wWa9l"
}
| field | |
|---|---|
id |
the API’s numeric id for the alert |
vtec_key |
office.phenomena.significance.ETN.year, the identity a warning keeps through every update. null for products with no VTEC |
cap_id |
the CAP identifier, when the alert came from the CAP feed alone. Usually null when VTEC identifies it |
event |
the NWS event name, e.g. Tornado Warning |
office |
issuing office, four letters (KBRO, PAFC) |
phenomena |
VTEC hazard code: TO tornado, SV severe thunderstorm, FF flash flood, WS winter storm and so on |
significance |
VTEC W, A, Y or S, as above |
etn |
VTEC event tracking number |
status |
active, expired or cancelled (spelled that way, as on the MQTT feed) |
severity, urgency, certainty |
the CAP fields as the NWS sent them |
issued_at |
issuance of the newest product in the alert’s chain, so a follow-up statement moves it |
onset |
when the hazard begins |
expires |
CAP “expect an update by”. Not the end of the hazard: above, the update is due at 01:00 while the blizzard runs to 21:00 the next day |
ends |
when the hazard actually ends, when the product says |
ugc |
NWS zone and county codes covered |
same |
SAME (FIPS) county codes covered, as NOAA Weather Radio uses them |
headline, description, instruction |
the product text, unedited |
geom_source |
where the shape came from: polygon (the storm-based polygon the office drew), ugc (built from the zone or county outlines) or none |
sources |
which feeds delivered it: nwws, emwin, api. See feed latency |
updated_at |
when the row was last seen by the ingest. It moves every minute while an alert is live, so do not read it as “last changed” |
short_id |
eight characters, the alert’s public page: https://wxalerts.org/alerts/{short_id}/ |
Alerts with geom_source: none have no shape, so they never appear on the
routes that search by location (at, in-bounds, ended-in-bounds,
focus, /v1/alerts/active/geojson). They are still in /v1/alerts/active.
As GeoJSON
in-bounds, ended-in-bounds, focus and /v1/alerts/active/geojson return
features. Each feature’s properties is the object above, plus area (a
readable place name built from the zones) on every route except
/v1/alerts/active/geojson, which leaves it off to stay small. Geometry is a
minified MultiPolygon in longitude, latitude order.
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": 28589429,
"geometry": {
"type": "MultiPolygon",
"coordinates": [[[[-97.6, 26.54], [-97.32, 26.6], [-97.29, 26.6], [-97.16, 26.15],
[-97.52, 26.15], [-97.69, 26.42], [-97.6, 26.54]]]]
},
"properties": {
"id": 28589429,
"vtec_key": "KBRO.FA.Y.0065.2026",
"event": "Flood Advisory",
"office": "KBRO",
"phenomena": "FA",
"significance": "Y",
"status": "active",
"severity": "Minor",
"expires": "2026-11-09T21:30:00Z",
"ends": "2026-11-09T21:30:00Z",
"geom_source": "polygon",
"short_id": "38fIRcsb",
"area": "Cameron, Willacy (TX)",
"...": "..."
}
}
],
"server_time": "2026-11-09T19:10:39.619349Z",
"count": 3
}
server_time is the database clock. Use it, not your own clock, to work out
how long ago something was issued or how long it has left.
GET /alerts/at
Every active alert covering a point, without geometry: the call for “is anything in effect here”. Newest issued first. An empty list means nothing is in effect at that point.
| parameter | |
|---|---|
lat |
latitude, -90 to 90. Required |
lon |
longitude, -180 to 180. Required |
GET /alerts/in-bounds
Active alerts whose shape touches a box, as a FeatureCollection, most serious
first: the order a sidebar list is read in. This is what the live map polls.
| parameter | |
|---|---|
west, east |
longitude, -180 to 180. Required. west must be less than east; boxes crossing the antimeridian are not supported |
south, north |
latitude, -90 to 90. Required. south must be less than north |
A bad box is 422 with a message that says which rule it broke. Responses
carry Cache-Control: max-age=30 and an ETag. Send the tag back as
If-None-Match and you get 304 Not Modified with no body until something in
the active set changes.
GET /alerts/ended-in-bounds
Alerts in a box that ended within the last minutes, for a radar loop. The
live set only holds what is in effect now, so a warning that expired twenty
minutes ago would be missing from every frame it was in force for. Merge this
with in-bounds and a frame at time T can show what was out at T.
| parameter | |
|---|---|
west, south, east, north |
the box, with the same rules as in-bounds |
minutes |
how far back, 1 to 180. Default 70 |
Same FeatureCollection shape, most serious first, at most 500 features.
status is expired or cancelled, and each feature adds ended_at: the
earlier of ends and expires, or for a cancellation the time the cancelling
product arrived. An alert was in force at time T when it was issued before T
and ended_at is after T.
GET /alerts/highest-severity
The most serious alerts active anywhere, ranked as above. Built for a “what matters right now” strip: no geometry and no text, just enough to label a row and frame a map on it.
| parameter | |
|---|---|
limit |
1 to 25. Default 3 |
{
"server_time": "2026-11-09T19:07:19.446710Z",
"alerts": [
{
"id": 28449246,
"vtec_key": "PAFC.BZ.W.0014.2026",
"bbox": [-145.958206, 60.798342, -144.617057, 61.210759],
"event": "Blizzard Warning",
"area": "Thompson Pass (AK)",
"office": "PAFC",
"phenomena": "BZ",
"significance": "W",
"severity": "Severe",
"urgency": "Immediate",
"issued_at": "2026-11-09T14:15:00Z",
"expires": "2026-11-10T01:00:00Z",
"ends": "2026-11-10T21:00:00Z",
"short_id": "8k3wWa9l"
}
]
}
bbox is [west, south, east, north] of the alert’s shape, ready for a map’s
fit to bounds call, and null for an alert with no shape.
GET /alerts/focus
Picks a map view for you when you do not have one, and returns only the alerts inside it, most serious first. Useful for a kiosk, a small teaser map or a screenshot bot.
| parameter | |
|---|---|
limit |
most features to return, 1 to 100. Default 25 |
strategy |
auto (default), highest-severity or most-alerts |
highest-severityframes the top-ranked active warning. Advisories and watches are never the anchor.most-alertsframes the office with the most active warnings and watches (all alerts, if nothing but advisories is out anywhere). Busy Alaska marine offices with a hundred Small Craft Advisories do not win this.autotrieshighest-severityand falls back tomost-alertswhen no warning is in effect.
{
"server_time": "2026-11-09T19:07:19Z",
"focus": {
"reason": "highest-severity",
"center_lat": 61.0045505,
"center_lon": -145.28763149999997,
"bbox": [-146.42760815, 60.0045505, -144.14765484999998, 62.0045505],
"anchor_event": "Blizzard Warning",
"anchor_office": "PAFC",
"anchor_area": "Thompson Pass (AK)"
},
"features": ["..."],
"count": 11,
"truncated": false
}
focus.reason says which rule chose the view: highest-severity,
most-alerts, or none when nothing with a shape is active anywhere (or
nothing qualifies for the strategy you asked for). none returns the lower 48
with no features rather than an error, so the map still draws. Use reason
and anchor_area to caption the view (“Blizzard Warning, Thompson Pass”); for
most-alerts, anchor_event is null and anchor_area names the office’s
most serious alert. The bbox is padded around the anchor and at least two
degrees on a side, so a small polygon does not fill the screen.
truncated: true means more alerts were in the view than limit, and the
least serious were dropped.
GET /alerts/{identifier}
One alert, in the alert object shape, by VTEC key (PAFC.BZ.W.0014.2026),
CAP id (urn:oid:...) or numeric id. Special Weather Statements have no VTEC
key, so look those up by CAP id. Unlike the search routes this also answers for
alerts that have ended, while they are retained. No match is 404.
GET /alerts/page/{short_id}
One alert by its eight-character short_id, live or ended: what the alert
pages on this site are built from. The alert object, plus:
| field | |
|---|---|
area |
readable place name, e.g. Maricopa (AZ) |
slug |
event-place-state-date, decoration only. The short id alone identifies the alert |
path |
the canonical page on this site, /alerts/{short_id}/{slug}/ |
bbox |
[west, south, east, north] of the shape, or null |
geometry |
the shape as minified GeoJSON, or null |
Ended alerts are kept for 30 days and say so in status. A short id that never
existed is 404. One whose alert has passed the retention window is 410 Gone,
so you can tell “this link was always wrong” from “this alert is over and
deleted”. An active alert’s page is cached for 30 seconds, an ended one for a
day.
GET /alerts/page/{short_id}/rundown
For a storm-based warning, the storm cell behind it and the places in its path.
{
"short_id": "9FNqeXAD",
"status": "cancelled",
"storm_id": 292687,
"match": "issue-time",
"reason": null,
"cell": {
"kind": "cell",
"storm_id": 292687,
"prob_tor": 1,
"prob_severe": 2,
"speed_kmh": 50.9,
"bearing_deg": 78,
"valid_time": "2026-11-07T01:12:37Z",
"lon": -69.381572,
"lat": 42.483522,
"...": "..."
},
"storm_active": false,
"valid_time": "2026-11-07T01:12:37Z",
"as_of": "issue",
"lead_minutes": null,
"speed_kmh": null,
"bearing_deg": null,
"places": [],
"pois": []
}
Only Tornado, Severe Thunderstorm, Extreme Wind and Special Marine warnings
with a shape get a cell. Anything else answers 200 with reason: "not-a-cell-warning" and everything empty: a flood or winter alert has no
storm cell behind it.
| field | |
|---|---|
cell |
the storm, in exactly the shape of a cell’s properties on the storm cells feed. null when no cell matched |
storm_id |
the storm’s id, for /storms/{storm_id}/track and the rest of the storm cells API |
match |
how the cell was found: issue-time (the ProbSevere cell over the polygon when the warning was issued) or overlap-now (no scan kept for that moment, so the active storm over the polygon now) |
as_of |
latest (the storm’s newest scan) or issue (the scan the warning was matched on, used once the warning has ended or the storm has dissipated) |
valid_time |
the radar scan cell describes |
storm_active |
whether the storm is still being tracked |
reason |
why cell or places is empty: not-a-cell-warning, no-cell-matched, or no-motion (the storm has no track to project yet) |
lead_minutes |
how far ahead places reaches: the warning’s remaining time, held between 15 and 60 |
speed_kmh, bearing_deg |
the motion the arrival times use |
places |
up to 30 towns inside the storm’s cone, in arrival order |
pois |
up to 50 schools, hospitals, nursing homes, mobile home parks and other points of interest in the cone, grouped by kind |
places and pois are filled only while the warning is active and the storm
is still tracked. Each has name, lat, lon, minutes (from valid_time
until the storm’s leading edge arrives, 0 if it is already overhead), eta
(the same as a time), distance_km down the track, and turn: true when only
the turn lobe of the cone reaches it. A place adds display_name, state,
county and population; a point of interest adds kind, city and
state. The numbers move every scan, so this is cached for 30 seconds even for
an ended warning. A short id with no alert is 404.
GET /v1/alerts/active
Every active alert in the country, without geometry, newest issued first.
Needs a key (alerts:read).
| parameter | |
|---|---|
event |
exact event name, case sensitive: Tornado Warning, not tornado warning |
office |
issuing office, exact: KBRO, not BRO |
severity |
CAP severity, exact: Extreme, Severe, Moderate or Minor |
limit |
1 to 2000. Default 500 |
offset |
rows to skip. Default 0 |
The filters match exactly, so a typo returns an empty list rather than an
error. Pagination is limit and offset over a stable order (issue time, then
id), with no total count: keep going until a page comes back shorter than
limit. The active set is a few hundred alerts on a typical day, so
limit=2000 usually takes it in one request, which also avoids the set
changing between pages.
GET /v1/alerts/active/geojson
Every active alert that has a shape, as one FeatureCollection, newest issued
first, with no area property. Takes event and severity with the same
exact matching as above; no paging. Needs a key (alerts:read).
This is the whole national geometry set, often a few megabytes, so it has a
tighter limit of its own: plan on 30 requests a minute at most, and past it you
get 429 with a Retry-After. You should need far fewer. Responses carry
Cache-Control: max-age=30 and an ETag, and a request with
If-None-Match gets an empty 304 until the set actually changes. For live
updates, MQTT costs nothing between alerts.
Errors
| status | when |
|---|---|
401 |
a key-only route on the bare path (api_key_required), or a /v1 route without a valid key |
404 |
no alert for that identifier or short id |
410 |
/alerts/page/{short_id} for an alert past the 30 day retention window |
422 |
a parameter out of range or missing, or a box with west >= east or south >= north |
429 |
over a rate limit; wait for Retry-After |

