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_...
Get an API key
No meeting and no sales call. POST your email to /v1/keys, confirm by email, and your live key is issued on the spot, on the free tier, no card.
Step 1. Request the key.
# request a key by email
curl -X POST "https://stormstream.io/v1/keys" \
-H "Content-Type: application/json" \
-d '{"email":"you@company.com","label":"my app"}'
Step 2. Click the confirm link we email you. Your sk_live_ key is minted on the free tier (1,000 calls a month, no card) and shown exactly once, right there.
Step 3. Make your first call.
# your first query, by ZIP
curl "https://stormstream.io/v1/query?zip=80202" \
-H "Authorization: Bearer sk_live_..."
Insurance and reinsurance
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.
Solar and renewables
Watch every site. The moment a swath crosses one, your webhook fires with the event record for the claim file.
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 a watched site { "event": "storm.detected", "type": "storm.detected", "product": "storm", "watchId": "wat_9k1f", "zone": { "id": "zon_7d2", "name": "El Paso sites" }, "storm": { "id": "stm_9k1f", "type": "hail", "maxHail": 1.75, "severity": "severe", "confidence": 88, "at": 1783312200000 } }
# the record for one date: exact=true pulls the NOAA radar-hail record for that day
curl "https://stormstream.io/v1/history?lat=31.84&lng=-106.43&date=2026-07-02&exact=true" \
-H "Authorization: Bearer sk_live_..."
Each event carries dateMs, the time window (startMin, durationMin), maxHail, severity, confidence, and a source: noaa for a radar-derived record, demo for the modeled reconstruction. Use exact=true with a date for the on-the-ground record.
Agriculture
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.
Energy and utilities
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.
Logistics and fleet
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.
News desks
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.
Developers
Mint a key, make one call, read one shape. That is the whole integration.
# the quickstart: a query in, normalized events out curl "https://stormstream.io/v1/query?zip=80202" \ -H "Authorization: Bearer sk_live_..." # each event comes back in this shape: { "ok": true, "count": 1, "storms": [{ "id": "stm_84h2", "name": "Front Range Hail", "type": "hail", // hail | wind "maxHail": 2.5, // inches "gust": 68, // mph "severity": "extreme", // moderate | severe | extreme "confidence": 91, "at": 1783300000000, // ms "swath": [[39.76, -104.99], [39.79, -104.93], ...], "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, all nationwide: ?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.
Roofing and restoration
Pull the storm history for a neighborhood and use the swaths to prioritize which doors matter.
# the date the homeowner remembers (exact=true = the NOAA radar-hail record) curl "https://stormstream.io/v1/history?lat=32.78&lng=-96.80&date=2026-06-28&exact=true" \ -H "Authorization: Bearer sk_live_..." # the swath in each event is the canvass boundary for that storm # check the source field: noaa (radar-derived) or demo (modeled)