Recipes: one query, every job.
There is one query surface. A point, a zip, an address, a bbox, or a polygon goes in. A normalized StormEvent comes out, with severity, confidence, and geometry. Every vertical below runs the same call and just asks a different question with it. Each recipe is runnable as written, once you swap in your key.
// the whole SDK, honestly async function stormstream(path, key) { const r = await fetch("https://stormstream.io/v1" + path, { headers: { authorization: "Bearer " + key } }); if (!r.ok) throw new Error((await r.json()).error); return r.json(); }
Every JS example below assumes this function. Auth is one header: Authorization: Bearer sk_live_...
Post a book of coordinates, get back the exposed locations, ranked by severity and confidence, before the first claim lands.
One call per insured location, scoped to last night's window (from and to are ms timestamps). A location with count 0 is clean. Anything else is exposure.
# one insured location against last night's window
curl "https://stormstream.io/v1/query?lat=32.78&lng=-96.80&from=1783260000000&to=1783346400000" \
-H "Authorization: Bearer sk_live_..."
{ "ok": true, "count": 1, "storms": [ { "id": "stm_84h2", "severity": "extreme", "confidence": 91, ... } ] }
// sweep the whole book, then rank the exposure const book = [ { policy: "pol_4811", lat: 32.78, lng: -96.80 }, { policy: "pol_4812", lat: 32.91, lng: -96.62 }, // ...the rest of the book ]; const from = Date.now() - 86400000, to = Date.now(); const exposed = []; for (const loc of book) { const res = await stormstream(`/query?lat=${loc.lat}&lng=${loc.lng}&from=${from}&to=${to}`, key); for (const s of res.storms) exposed.push({ ...loc, storm: s.id, severity: s.severity, confidence: s.confidence, maxHail: s.maxHail }); } const band = { extreme: 3, severe: 2, moderate: 1 }; exposed.sort((a, b) => band[b.severity] - band[a.severity] || b.confidence - a.confidence); // exposed[0] is the location your adjusters call first
Every StormEvent carries sources, the public URLs each field was read from. Your auditors can check the record against the feed it came from.
Watch every site. The moment a swath crosses one, your webhook fires, and the timestamped record is ready for the insurer.
Register the zips your sites sit in once. When a new event intersects a watched zip, we POST a storm.detected payload to your webhook with the full StormEvent inside.
# watch the fleet of sites, one call curl -X POST "https://stormstream.io/v1/watch" \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "webhookUrl": "https://ops.yourfleet.com/hooks/storm", "zips": ["79936", "79912", "88011"] }' # what arrives at your webhook when a swath crosses site 79936 { "event": "storm.detected", "zip": "79936", "storm": { "id": "stm_9k1f", "type": "hail", "maxHail": 1.75, "severity": "severe", "confidence": 88, "at": 1783312200000, ... } }
# the timestamped record for the claim file
curl "https://stormstream.io/v1/history?lat=31.84&lng=-106.43&date=2026-07-02" \
-H "Authorization: Bearer sk_live_..."
The history record carries at (ms), maxHail, severity, confidence, and the source URLs. That is the document an insurer accepts.
Submit each field as a polygon, get the hazards that actually crossed it, scored per field.
A field is a polygon, not a point. POST the boundary and a window, and only the events whose swath intersects that exact geometry come back.
# one field boundary, one growing-season window
curl -X POST "https://stormstream.io/v1/query" \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"polygon": [[40.112, -98.523], [40.112, -98.471], [40.068, -98.471], [40.068, -98.523]],
"window": { "from": 1780600000000, "to": 1783346400000 }
}'
// score every field in the operation async function fieldHazards(field, window, key) { const r = await fetch("https://stormstream.io/v1/query", { method: "POST", headers: { authorization: "Bearer " + key, "content-type": "application/json" }, body: JSON.stringify({ polygon: field.boundary, window }) }); const res = await r.json(); return { field: field.name, hits: res.count, worst: res.storms[0] || null }; }
Polygons are [lat, lng] pairs, closed automatically. Results are newest-first, so storms[0] is the most recent hit.
Subscribe the service territory once. Read the live exposure crossing the grid whenever you need it.
The watch covers the territory's zips and fires the moment an event intersects any of them. The bbox query is the on-demand read: everything live inside the territory's bounding box, right now.
# the standing subscription for the whole territory
curl -X POST "https://stormstream.io/v1/watch" \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "webhookUrl": "https://scada.yourutility.com/hooks/severe", "zips": ["75201", "75204", "75218", "75228", "75243"] }'
# the on-demand read: what is inside the territory box right now # bbox is minLng,minLat,maxLng,maxLat curl "https://stormstream.io/v1/query?bbox=-97.05,32.55,-96.45,33.05" \ -H "Authorization: Bearer sk_live_..."
Each event carries gust (mph) and areaKm2, so a crew dispatcher can size the response before the first outage call.
Test a route corridor against live storm polygons and learn which segments are exposed before a truck enters one.
Sample the route every few kilometers and query each waypoint. A waypoint with count 0 is clear road. The exposed list is your reroute plan.
// route waypoints in, exposed segments out const route = [ [32.78, -96.80], [32.61, -96.55], [32.44, -96.31], [32.27, -96.08], [32.10, -95.84] // Dallas toward Tyler, ~20 km steps ]; const exposed = []; for (const [lat, lng] of route) { const res = await stormstream(`/query?lat=${lat}&lng=${lng}`, key); if (res.count) exposed.push({ lat, lng, worst: res.storms[0].severity }); } // exposed.length === 0 means the corridor is clear right now
# or sweep the whole corridor box in one call
curl "https://stormstream.io/v1/query?bbox=-96.90,32.00,-95.70,32.90" \
-H "Authorization: Bearer sk_live_..."
Pull /v1/storms/:id/overlay.geojson for any hit and draw the swath on the dispatch map. The geometry is standard GeoJSON, any map library renders it.
A webhook the moment a warning drops in your market, and clean map geometry ready for the broadcast graphic.
# the market watch: fires before the competition's crawl updates
curl -X POST "https://stormstream.io/v1/watch" \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "webhookUrl": "https://newsroom.yourstation.com/hooks/wx", "zips": ["73101", "73102", "73103", "73110"] }'
// the on-air map layer, three lines (Leaflet shown, any library works) const geo = await stormstream("/storms/stm_9k1f/overlay.geojson", key); L.geoJSON(geo).addTo(map); // features carry severity and the size label, ready to style and caption
The payload's at timestamp and source URLs let the desk verify the event against the wire before it airs.
Mint a key, make one call, read one shape. That is the whole integration.
# the quickstart: a zip in, normalized events out curl "https://stormstream.io/v1/query?zip=75201" \ -H "Authorization: Bearer sk_live_..." { "ok": true, "count": 1, "storms": [{ "id": "stm_84h2", "name": "Dallas Supercell", "type": "hail", // hail | wind "maxHail": 2.5, // inches "gust": 68, // mph "severity": "extreme", // moderate | severe | extreme "confidence": 91, "at": 1783300000000, // ms "swath": [[32.78, -96.80], [32.81, -96.74], ...], "areaKm2": 412, "source": "nws", "sources": ["https://api.weather.gov/alerts/..."] }] }
// one event by id, same shape const storm = await stormstream("/storms/stm_84h2", key);
Same query, four ways: ?lat=&lng= · ?zip= · ?address= · ?bbox=, each with optional &from= and &to= in ms. The full contract lives at /openapi.json and the event shape at /schema/stormevent.json.
Pull the storm history for a neighborhood and use the swaths to prioritize which doors matter.
# what hit this neighborhood on the date the homeowner remembers curl "https://stormstream.io/v1/history?lat=32.78&lng=-96.80&date=2026-06-28" \ -H "Authorization: Bearer sk_live_..." # the swath in each event is the canvass boundary: # inside it, confirmed hail; outside it, wasted knocking