Community. Driven. Weather. Data. | Always Ad-Free | Developer FriendlyChecking statusDiscord
Become a supporter
Sign in
API

SPC outlooks

The Storm Prediction Center's convective outlooks as GeoJSON in SPC's own colors, with a summary that tells no risk from no data.


Base URL: https://api.wxalerts.org

The Storm Prediction Center’s convective outlooks: Day 1 to Day 3 categorical, and the Day 1 tornado, hail and wind probabilities. One route lists what is issued, the other returns one outlook’s risk areas.

GET /outlooks
GET /outlooks/day1-categorical/geojson
GET /outlooks/day1-tornado/geojson

No key needed. Browsers block calls from other websites (CORS allows only wxalerts.org), so call it from a server, script or app. Responses carry Cache-Control: public, max-age=300, and SPC is polled about every five minutes, so there is nothing to gain from asking more often. Limits are on the overview.

Which outlooks are issued

GET /outlooks lists the six map products without geometry. Build a product picker from it rather than from a hard-coded list: a product that has never been fetched is absent, and one whose polling is failing says so.

{
  "server_time": "2026-11-09T19:06:41.749666Z",
  "outlooks": [
    {
      "product": "day1-categorical",
      "day": 1,
      "hazard": "categorical",
      "label": "Day 1 categorical",
      "note": "Today's severe risk in the five named categories, from General Thunderstorms through High. Valid to 12Z tomorrow.",
      "issued_at": "2026-11-09T16:09:00Z",
      "valid_from": "2026-11-09T16:30:00Z",
      "valid_to": "2026-11-10T12:00:00Z",
      "forecaster": "Mosier/Moore",
      "areas": 1,
      "empty_label": null,
      "fetched_at": "2026-11-09T19:03:05.075558Z",
      "changed_at": "2026-11-09T16:20:25.151796Z",
      "expired": false,
      "stale": false,
      "last_error": null
    },
    {
      "product": "day1-tornado",
      "day": 1,
      "hazard": "tornado",
      "label": "Day 1 tornado probability",
      "note": "The chance of a tornado within 25 miles of a point, today. The hatched area is where 10% or more of those would be EF2 or stronger.",
      "issued_at": "2026-11-09T16:09:00Z",
      "valid_from": "2026-11-09T16:30:00Z",
      "valid_to": "2026-11-10T12:00:00Z",
      "forecaster": "Gleason",
      "areas": 0,
      "empty_label": "Less Than 2% All Areas",
      "fetched_at": "2026-11-09T19:03:05.075558Z",
      "changed_at": "2026-11-09T16:20:25.151796Z",
      "expired": false,
      "stale": false,
      "last_error": null
    },
    "..."
  ]
}

Outlooks come back ordered by day, then hazard alphabetically.

field
product the slug to pass to /outlooks/{product}/geojson
day, hazard which day (1 to 3) and which outlook: categorical, tornado, hail or wind
label, note a short name, and one sentence on what the outlook actually claims. Show the note; it is the difference between “15% wind” and “a 15% chance of 58 mph gusts within 25 miles”
issued_at when the forecaster published this version
valid_from, valid_to the period the outlook is about. Day 1 runs to 12Z tomorrow whatever time it was issued, so valid_to is not when it goes stale
forecaster as SPC signs it
areas how many risk areas there are to draw. Zero is a normal, common answer
empty_label SPC’s own phrasing when there is nothing to draw, usually "Less Than 5% All Areas" ("Less Than 2% All Areas" for tornado). null when there are areas
fetched_at when wxalerts last reached SPC for this product
changed_at when the content last actually changed. It lags fetched_at by design, since most polls find nothing new
expired valid_to is in the past. Say so on the map rather than drawing it silently
stale polling has failed for more than three of its usual intervals (about 15 minutes for these products). What is stored may still be right; it is no longer confirmed
last_error why the last poll failed, or null after a success

One outlook’s areas

GET /outlooks/{product}/geojson returns a GeoJSON FeatureCollection with two extra members: server_time, and outlook, the same summary object as above. There is no bounding box parameter: an outlook is a handful of national polygons totalling a few kilobytes, and the point of the layer is the shape of the day across the country.

{
  "type": "FeatureCollection",
  "server_time": "2026-11-09T19:06:50.484971Z",
  "outlook": {
    "product": "day1-categorical", "day": 1, "hazard": "categorical",
    "label": "Day 1 categorical",
    "note": "Today's severe risk in the five named categories, from General Thunderstorms through High. Valid to 12Z tomorrow.",
    "issued_at": "2026-11-09T16:09:00Z", "valid_from": "2026-11-09T16:30:00Z", "valid_to": "2026-11-10T12:00:00Z",
    "forecaster": "Mosier/Moore", "areas": 1, "empty_label": null,
    "fetched_at": "2026-11-09T19:03:05.075558Z", "changed_at": "2026-11-09T16:20:25.151796Z",
    "expired": false, "stale": false, "last_error": null
  },
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "MultiPolygon",
        "coordinates": [[[[-101.06, 30.7], [-99.82, 31.12], [-98.8, 31.09], "...", [-101.06, 30.7]]], "..."]
      },
      "properties": {
        "product": "day1-categorical",
        "level": "TSTM",
        "rank": 2,
        "label": "General Thunderstorms Risk",
        "stroke": "#55BB55",
        "fill": "#C1E9C1",
        "significant": false
      }
    }
  ]
}
property
level TSTM, MRGL, SLGT, ENH, MDT or HIGH on a categorical outlook. On a probability outlook, the percentage as a string: "5%", "15%", "30%" and so on. "SIG" for the hatched significant area
rank the order the level sorts in. SPC’s own number for categorical levels (2, 3, 4, 5, 6, 8), the percentage itself for probabilities, and 100 for SIG
label SPC’s wording for the level, such as "Slight Risk"
stroke, fill SPC’s own colors, as hex
significant true on the hatched significant-severe area

Coordinates are rounded to four decimal places (about 11 m) and are not simplified, so a boundary matches the one SPC drew.

Drawing it

Use stroke and fill. The colors of a convective outlook are the product, and readers already know how to read them.

Features come back lowest risk first, which is also the order to draw them: a Slight Risk area sits inside the General Thunderstorms area it is part of, and drawing them the other way round hides it.

On the tornado, hail and wind outlooks, significant: true marks the hatched 10% significant-severe area, and it is always the last feature. Hatch it rather than filling it. It is a second statement about ground the probabilities have already colored (“of those storms, 10% or more would be EF2 or stronger”), and a solid fill reads as a higher probability than SPC issued.

An outlook with nothing to draw returns zero features, with a 200. That is not an error. The summary’s empty_label carries the forecaster’s phrasing, and is how a client tells no risk (areas: 0 with an empty_label) from no data.

Every product

The six map products, the ones /outlooks lists:

{product}
day1-categorical today’s categorical risk, to 12Z tomorrow
day2-categorical tomorrow, 12Z to 12Z
day3-categorical the day after tomorrow, 12Z to 12Z
day1-tornado chance of a tornado within 25 miles, with EF2+ hatching
day1-hail chance of 1 inch or larger hail within 25 miles, with 2 inch hatching
day1-wind chance of 58 mph or stronger gusts within 25 miles, with 75 mph hatching

The route also serves SPC’s other products, which /outlooks does not list. Read their summary from the outlook member of their own response.

{product}
severe-day4 to severe-day8 Day 4 to 8 severe probability. SPC draws only 15% and 30%
fire-day1, fire-day2 fire weather from wind and low humidity: levels ELEV, CRIT, EXTM
drythunder-day1, drythunder-day2 dry thunderstorms: levels IDRT, SDRT
fire-day3 to fire-day8 probability of critical fire weather
drythunder-day3 to drythunder-day8 probability of dry thunderstorms
md active mesoscale discussions: level and rank are the discussion number, label its “concerning” line. empty_label is "No active mesoscale discussions" when there are none

Errors

status
404 not a product slug. The detail lists every valid one
503 a real product that has never been fetched on this deployment. Retry later rather than dropping it from a menu

May we use Microsoft Clarity to see how pages are used (clicks, scrolling and replays of a visit, with anything you type hidden), plus a random ID linking that to crash reports? Only to find bugs and confusing screens. Never advertising, never sold.