Base URL: https://api.wxalerts.org
Lightning flashes seen from orbit by the Geostationary Lightning Mapper (GLM) on GOES-19, the GOES-East satellite. GLM is an optical sensor: it watches for the flicker of lightning lighting up cloud tops, day and night, across the Americas and the Atlantic, at about 8 km resolution. It sees in-cloud and cloud-to-ground lightning alike and does not tell them apart.
GET /lightning/flashes?bbox=-98.5,32.0,-96.0,33.5
GET /lightning/flashes?bbox=-104,30,-90,40&minutes=30
GET /lightning/flashes?bbox=-104,30,-90,40&minutes=60&until=2026-11-09T18:00:00Z&limit=5000
No key needed. It shares the per address limit. Browsers block calls from other websites (CORS allows only wxalerts.org), so call it from a server, script or app.
Flashes arrive live over the MQTT lightning feed, and that
is the way to follow lightning as it happens. The broker keeps nothing, so a
client that has just connected has no flashes from before it did. This route
fills that gap: it reads the same flashes back from the database, so a radar
loop or a newly opened page can draw the last hour and then carry on from MQTT.
For daily counts rather than individual flashes, see
/v1/stats/lightning/daily.
| parameter | |
|---|---|
bbox |
required. minLon,minLat,maxLon,maxLat in degrees, west, south, east, north. Each must be inside -180 to 180 and -90 to 90, with min below max. A box crossing the antimeridian is not supported |
minutes |
how far back from until to reach, 1 to 180. Default 70 (a radar loop’s hour, plus the ten minutes its oldest frame draws behind it) |
until |
ISO 8601 end of the window. Default now. A time without a zone is read as UTC. Up to 5 minutes in the future is accepted for clock skew, and anything later is pulled back to now plus 5 minutes |
limit |
at most this many flashes, 1 to 60000. Default 30000 |
The window is since < t <= until. The database keeps about a day, so a window
that ends further back than that comes back empty, not as an error.
What a response holds
{
"since": "2026-11-09T18:58:26.935870Z",
"until": "2026-11-09T19:08:26.935870Z",
"truncated": false,
"flashes": [
[1794250713936, 24.4613, -95.9038, 6.42848946817709e-14],
[1794250715534, 39.6719, -67.0973, 9.428477557070838e-14],
"...",
[1794251248570, 39.214, -68.1464, 1.3128461805091834e-13]
]
}
flashesare rows, oldest first, not GeoJSON: an hour over a busy view is tens of thousands of flashes, and four key names on each would triple the payload. Each row is[t_ms, lat, lon, energy_j].t_msis the observation time in epoch milliseconds (truncated, not rounded). This is when GLM saw the flash, not when it reached us; flashes run about 20 seconds behind.lat,lonare rounded to four decimals, exactly as the MQTT feed publishes them. Note the order: latitude first, unlikebboxand GeoJSON.energy_jis the flash’s total optical energy as GLM measured it, in joules, ornull. The numbers are tiny (around 1e-14 to 1e-12) because it is the light reaching the sensor; use it to compare flashes, for size or brightness, not as the energy of the discharge.since,untilecho the window actually served, after defaults and the clamp onuntil.truncatedistruewhen there were more flashes thanlimit. The newest are kept and the oldest dropped, so the gap is at the start of the window. Narrow the box or shortenminutesrather than raisinglimitto the ceiling as a habit.
Merging with the live feed
The time to the millisecond and the position to four decimals are the identity
the MQTT feed uses, so a flash heard live and the same flash read here match
exactly. Key a set on t_ms,lat,lon, load the history, and drop live
messages already in it. Fade flashes on t_ms, not on when you received them.
Responses are cached for 20 seconds (Cache-Control: public, max-age=20): the
newest minute changes with every GLM granule, so poll no faster than that, and
prefer MQTT for anything live.
Errors
| status | when |
|---|---|
422 |
bbox missing, not four numbers, out of range or inverted, or minutes or limit out of range |
The shared error shapes are on the REST overview.

