DEV Community

Devil Scrapes
Devil Scrapes

Posted on

The NWS API makes you resolve a grid before it tells you the weather

Quick answer

The US National Weather Service publishes forecasts and active alerts through a free, keyless API. There is no signup and no rate-limit key β€” just send an identifying User-Agent, which NWS asks for and which is entirely reasonable.

The part that surprises people: you cannot ask it for the weather at a coordinate. You ask it which forecast office and grid cell that coordinate falls in, and then you ask that grid for a forecast.

That is the NOAA Weather Forecast & Alerts Scraper. Give it coordinates; get one flat row per location with the 7-day forecast and any active alerts already joined.

Two calls minimum, and the first one is a lookup πŸ—ΊοΈ

GET https://api.weather.gov/points/39.7456,-97.0892
    β†’ { gridId: "TOP", gridX: 31, gridY: 80, ... }

GET https://api.weather.gov/gridpoints/TOP/31,80/forecast
    β†’ ~14 forecast periods

GET https://api.weather.gov/alerts/active?point=39.7456,-97.0892
    β†’ 0+ active alerts
Enter fullscreen mode Exit fullscreen mode

The /points response is the routing step. It resolves a lat/lon to a Weather Forecast Office and an integer grid cell, and everything else hangs off that. Skip it and guess a grid, and you are forecasting somewhere else.

Everything comes back as application/geo+json β€” GeoJSON features with the payload in properties, not a flat records array.

The 404 that is not an error 🚧

/points returns HTTP 404 with application/problem+json for a coordinate outside NWS coverage. Not a malformed request, not a server fault β€” the API correctly telling you it does not forecast that place. The Atlantic, most of Canada, anywhere outside the US and its territories.

If your client treats every non-200 as fatal, one offshore coordinate in a list of two hundred kills the run and the customer pays the start fee for nothing. We skip that location with a clear warning and keep going.

This is the single most common defect shape we find in our own fleet: a recoverable per-item error crashing the whole run instead of skipping one item. It is worth checking for before anything else.

Zero alerts is a success, not an empty result πŸ””

Ask for active alerts on a calm day and you get a valid response with an empty features array. That is the correct answer, and it must be recorded as one.

It matters more than it sounds. A run that returns rows with alert_count: 0 looks suspiciously like a run that failed to fetch alerts, and the temptation is to treat emptiness as failure. Our own smoke test pins the distinction: three locations, and the Kansas one carries a Heat Advisory while New York and Los Angeles return alert_count: 0 β€” all three successes, all three rows emitted.

Hourly forecasts are opt-in for a reason πŸ’Έ

The standard forecast is ~14 periods. The hourly forecast for the same grid is ~156 periods and about 162 KB, per location.

Bandwidth is the dominant cost in this kind of Actor, so hourly is off by default and capped client-side when you turn it on. A default that quietly multiplies your egress by an order of magnitude is a pricing bug wearing a feature's clothes.

We probe every endpoint properly now πŸ”

Two recent lessons changed how we validate a target before building on it.

First: an endpoint we recorded as "HTTP 200, 20 KB" turned out to be a 301 redirect to a client-rendered SPA shell that was itself 200 and about 20 KB of HTML. The probe had followed the redirect and never checked Content-Type. So a probe now records the status without following redirects, the Content-Type, and whether the body actually parses as JSON.

Second: we found an institutional API returning 503 to every Chrome TLS fingerprint while serving Firefox and Safari normally β€” the anti-blocking measure was the block signal.

NWS passes both checks cleanly: every endpoint 200 with application/geo+json and valid JSON, on chrome131 and firefox133 alike. No fingerprint sensitivity, no anti-bot behaviour. Measured, not assumed.

What a row looks like

One row per location: the input coordinate, resolved grid office and cell, the forecast periods (name, start/end, temperature and unit, wind speed and direction, short and detailed forecast), an active-alerts array with event, severity, urgency, headline and effective/expires timestamps, and an alert_count.

Who this is for 🎯

  • Logistics and routing β€” weather exposure along a route or across a depot network.
  • Agriculture β€” frost, heat and precipitation outlook by field location.
  • Insurance and risk β€” active severe-weather alerts against a portfolio of addresses.
  • Events and energy β€” demand and disruption forecasting by site.

The honest limitations 🚧

  • US coverage only. NWS forecasts the United States and its territories; anything else 404s, and we report that rather than inventing a row.
  • The 7-day forecast is periods, not hours, unless you opt into hourly.
  • Active alerts are a snapshot at run time, not a subscription β€” poll on the cadence your use case needs.

Pricing

$0.20 per run plus $0.005 per location β€” $5.20 per 1,000. A location bundles its forecast, its optional hourly forecast, and its alerts join.

β†’ NOAA Weather Forecast & Alerts Scraper on Apify


Built by Devil Scrapes. We handle the grid resolution, the 404 that means "not here", the empty alert list that means "all clear", and the hourly payload that would quietly cost you ten times as much.

Top comments (0)