Base URL: https://api.wxalerts.org
The full forecast for a point: the National Blend of Models hour by hour, and
the HRRR nowcast beside it, with the model run behind every number.
GET /weather is the same data reduced for a screen;
this is the unreduced version.
GET /forecast?lat=35.2&lon=-97.4&units=si
GET /forecast?q=norman+ok&units=us
GET /forecast?lat=35.2&lon=-97.4&units=si&include=severe,upper_air
No key needed. Browsers block calls from other websites (CORS allows only
wxalerts.org), so call it from a server, script or app. The
public rate limit applies. The data changes
hourly and responses carry Cache-Control: max-age=300, so cache.
| parameter | |
|---|---|
lat, lon |
the point |
q |
instead of lat/lon: a place name, ZIP or state (1 to 120 characters). The best match is used; no match is a 404 |
location_id |
instead of lat/lon: a place id. Unknown is a 404 |
units |
si, metric, us, uk or auto (default). See Units |
include |
comma separated: severe, upper_air. Anything else is a 422 |
explain |
true also returns facts, the derived facts the narrative was written from. Default false |
With none of lat/lon, q or location_id the answer is a 422.
Two models, never blended
| resolution | horizon | updated | carries | |
|---|---|---|---|---|
hourly, daily |
NBM, about 10 km | about 11 days | hourly | the forecast: temperature, wind, sky, precipitation and its probabilities |
nowcast, current |
HRRR, about 6 km | 18 hours | hourly | the near term: radar reflectivity, pressure, and on request the severe and upper air fields below |
They are kept apart on purpose. When they disagree about the timing of rain,
that disagreement is information, and a blend would hide which model said
what. models names each run, its age and how far it reaches.
Both series start at the hour in progress. hourly is one step an hour for
about the first two days, then every 3 hours, then every 6 hours past about
day 8. current is the nowcast step nearest to now, not simply the first.
What a response holds
{
"server_time": "2026-11-09T19:07:20Z",
"location": {
"name": "Norman", "display_name": "Norman, OK", "state": "OK",
"lat": 35.2, "lon": -97.4, "timezone": "America/Chicago",
"approximate": false, "distance_km": 6.7
},
"cell": { "cell_id": 81168, "domain": "co", "lat": 35.196527, "lon": -97.378552, "resolution_km": 10.2 },
"models": {
"nbm": { "resolution_km": 10.2, "cycle": "2026-11-09T18:00:00Z", "age_seconds": 4040, "steps": 104,
"first_valid": "2026-11-09T19:00:00Z", "last_valid": "2026-11-20T12:00:00Z", "horizon_hours": 256.9 },
"hrrr": { "resolution_km": 6.0, "cycle": "2026-11-09T18:00:00Z", "age_seconds": 4040, "steps": 17,
"first_valid": "2026-11-09T19:00:00Z", "last_valid": "2026-11-10T11:00:00Z", "horizon_hours": 15.9 }
},
"units": {
"system": "si", "source": "explicit", "country": null,
"hourly": { "temperature": { "unit": "K", "label": "K", "description": "2 m air temperature." }, "...": "..." },
"nowcast": { "pressure": { "unit": "Pa", "label": "Pa", "description": "Mean sea level pressure. ..." }, "...": "..." }
},
"coverage": "full",
"current": { "valid_time": "2026-11-09T19:00:00Z", "...": "..." },
"nowcast": [
{
"valid_time": "2026-11-09T19:00:00Z", "local_time": "2026-11-09T13:00:00-06:00",
"utc_offset_seconds": -21600, "forecast_hour": 1,
"wind_direction_cardinal": "SW", "visibility_unrestricted": null,
"temperature": 293.9, "dewpoint": 283.7, "wind_speed": 4.783, "wind_direction": 218.0,
"precip_amount": 0.0, "reflectivity": -10.0, "cape": 0.0,
"pressure": 102000.0, "surface_pressure": 97920.0
},
"..."
],
"hourly": [
{
"valid_time": "2026-11-09T19:00:00Z", "local_time": "2026-11-09T13:00:00-06:00",
"utc_offset_seconds": -21600, "forecast_hour": 2,
"wind_direction_cardinal": "SSW", "visibility_unrestricted": true,
"temperature": 293.2, "dewpoint": 285.1, "relative_humidity": 59.6,
"wind_speed": 3.931, "wind_gust": 6.9, "wind_direction": 206.0,
"cloud_cover": 2.9, "visibility": 16210.0, "ceiling": null,
"precip_amount": 0.0, "precip_probability": 0.0,
"rain_probability": 0.0, "snow_probability": 0.0,
"freezing_rain_probability": 0.0, "ice_pellet_probability": 0.0,
"thunder_probability": 0.0, "severe_probability": 0.0, "cape": 0.0,
"sky": "Clear", "precip_type": null, "icon": "clear-day", "is_day": true
},
"..."
],
"daily": [
{
"date": "2026-11-09", "hours": 11,
"temperature_min": 285.8, "temperature_max": 294.1,
"dewpoint_min": 284.6, "dewpoint_max": 285.8,
"relative_humidity_min": 54.4, "relative_humidity_max": 99.6,
"wind_speed_max": 3.931, "wind_gust_max": 7.1,
"wind_direction_prevailing": 198.0, "wind_direction_cardinal": "SSW",
"cloud_cover_mean": 41.6, "visibility_min": 16210.0, "ceiling_min": 3960.0,
"precip_amount_total": 0.0, "precip_probability_max": 1.0,
"rain_probability_max": 0.0, "snow_probability_max": 0.0,
"freezing_rain_probability_max": 0.0, "ice_pellet_probability_max": 0.0,
"thunder_probability_max": 0.0, "severe_probability_max": 0.0, "cape_max": 0.0,
"sky": "Partly cloudy", "precip_type": null, "icon": "partly-cloudy-day"
},
"..."
],
"narrative": {
"now": { "headline": "A comfortable afternoon, clear", "body": ["..."], "hazard": false },
"next_36h": { "headline": "Quiet through the next day and a half", "body": ["..."], "hazard": false },
"week": { "headline": null, "body": ["..."], "hazard": false }
},
"facts": null
}
locationis the place the forecast is for. Givenlat/lon, it is the nearest named place:approximate: truemeans the point lies outside that place (say “near Norman”), anddistance_kmis how far its centre is. Where nothing is close enough to name,name,display_nameandstatearenull. Givenqorlocation_id, it is that place and its coordinates, withdistance_km: null.timezoneis the IANA zone the local times are in.cellis the NBM grid cell the point fell in.lat/lonhere are its centre, not your point.domainisco(the lower 48),ak,hi,prorgu.models:cycleis when the run started andage_secondshow long ago that was, which is where staleness lives.horizon_hoursis from now tolast_valid.hrrrisnulloutside the HRRR grid.- Every step carries
valid_time(UTC),local_timeandutc_offset_secondsfor the point’s zone,forecast_hour,wind_direction_cardinal(16 point), andvisibility_unrestricted:truewhen visibility is at the top of the model’s range (16 km, about 10 mi), so you can write “10+ mi”;nullon the nowcast, which has no visibility.forecast_hourcounts from the run that produced that step. Most steps come from the newest run, but where it skipped an hour an earlier run’s step stays, so order byvalid_time, never byforecast_hour. hourlyanddailyalso carrysky,precip_typeandicon, decided on the server exactly as on/weather; hourly steps addis_day. A day’s icon is always the daytime one.dailyrollshourlyinto local calendar days. Each field becomes_min/_max(temperature, dewpoint, humidity),_max(wind, gust, probabilities,cape),_min(visibility, ceiling),_mean(cloud cover),_total(precipitation) or_prevailing(wind direction, a vector mean).hoursis how many steps the day was built from: today and the last day are partial, and days past the hourly range have fewer steps by design.- Nulls.
ceiling: nullmeans no ceiling (unlimited).reflectivityof-10dBZ is the model’s floor and means clear air;nullthere only means no value is stored for that hour.
Coverage
coverage says what you got:
coverage |
where | what is filled |
|---|---|---|
full |
the lower 48 and its surroundings | everything |
nbm_only |
Alaska, Hawaii, Puerto Rico, Guam | hourly and daily; nowcast is empty, current and models.hrrr are null |
outside_domain |
anywhere else | every series empty, cell.cell_id is -1 |
A field a domain does not publish is left out of its steps and of the units
block entirely, rather than sent as null. Hawaii, for example, has no
precipitation type or severe_probability.
Units
units is si (exactly what the model published: K, m/s, Pa, kg/m²),
metric, us, uk, or auto (the default) from your country. Every
response carries a units block naming the unit, a display label and a
description of every field in hourly and nowcast. Read it rather than
assuming. units.source is explicit when you named a system, geo when it
came from your country, or default.
auto varies by caller, so it is sent Cache-Control: private with
Vary: CF-IPCountry. Pass a system explicitly for a response a CDN can cache
(public, max-age=300).
Probabilities (NBM, hourly)
| field | meaning |
|---|---|
precip_probability |
chance of more than 0.01 in (0.254 mm) in the hour |
thunder_probability |
chance of a thunderstorm. Not published for Guam |
rain_probability, snow_probability, freezing_rain_probability, ice_pellet_probability |
chance the precipitation is each type. Four independent numbers, not a split of 100: an uncertain hour can be plausibly both rain and snow. CONUS and Alaska only. |
severe_probability |
chance the Craven-Wiedenfeld severe parameter exceeds 65, a significant severe signal. CONUS only. |
All are percentages. Probabilities are the highest value anywhere in the 10 km cell, so they answer “anywhere near here”, not “on average across it”.
include=severe
Added to each nowcast step (and so to current). From the HRRR at native
resolution, averaged to the 6 km cell; helicity and updraft helicity keep the
cell’s peak instead. Surface based cape is in every nowcast step already.
| field | unit (si) | meaning |
|---|---|---|
cin |
J/kg | surface based convective inhibition. Zero or negative; more negative is a stronger cap |
mlcape, mlcin |
J/kg | mixed layer, the lowest 90 hPa |
mucape, mucin |
J/kg | most unstable parcel in the lowest 255 hPa |
cape_0_3km |
J/kg | low-level CAPE |
lcl_height |
m above ground | lifted condensation level of a surface parcel |
lifted_index |
K | negative is unstable |
precipitable_water |
kg/m² (mm) | |
srh_0_1km, srh_0_3km |
m²/s² | storm-relative helicity, Bunkers right mover |
storm_motion_speed, storm_motion_direction |
m/s, degrees | HRRR’s Bunkers right mover, direction FROM |
bulk_shear_0_1km, bulk_shear_0_6km, bulk_shear_0_6km_direction |
m/s, degrees | bulk wind difference |
updraft_helicity |
m²/s² | hourly maximum 2 to 5 km; 0 at the analysis hour |
CAPE, CIN and helicity stay in J/kg and m²/s² in every system.
include=upper_air
Temperature, dewpoint and wind at 925, 850, 700 and 500 hPa, heights at 850 to
500 hPa, the 250 hPa jet, the 80 m wind and the boundary layer height:
temperature_850mb, dewpoint_850mb, height_850mb, wind_speed_850mb,
wind_direction_850mb and so on, plus wind_speed_250mb,
wind_direction_250mb, wind_speed_80m, wind_direction_80m and
boundary_layer_height (above ground).
A level below the ground at that point (850 hPa over Denver, say) comes back
null: the model extrapolates under terrain and that number describes no air.
Winds aloft, the 80 m wind, shear and storm motion are in knots in every
system but si, because that is how soundings and hodographs read. Heights of
pressure levels stay in metres in every system.
All directions are degrees from true north, reported as the direction a wind blows FROM. HRRR publishes its vectors relative to its own grid; they are turned to true north before they are stored.
Narrative and explain
narrative is the forecast in sentences, in three blocks: now, next_36h
and week, each a headline (or null) and body paragraphs. It is written
by rules from the numbers in the same response, so it never states anything
the series do not. An empty body means nothing was worth saying. Outside
every model domain narrative is null, unless an alert covers the point,
in which case only the alert is said. When an
active alert covers the point it leads, and that block’s hazard is true:
style the lead paragraph as a warning on that flag, not by matching words.
explain=true fills facts with the derived values the sentences were
chosen from (high today, precipitation onset, pressure trend and so on). It is
for seeing why a sentence appeared, not a stable interface: its keys can
change without notice.

