# NWS Weather Scraper: US Forecasts, Alerts & Observations (`arman-bd/nws-weather-scraper`) Actor

Scrape US National Weather Service forecasts, hourly outlooks, active alerts and station observations by coordinate or zone. No API key, no proxy, no browser.

- **URL**: https://apify.com/arman-bd/nws-weather-scraper.md
- **Developed by:** [Arman Hossain](https://apify.com/arman-bd) (community)
- **Categories:** Business, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.37 / 1,000 weather scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## NWS Weather Scraper: US Forecasts, Alerts & Observations

![NWS Weather Scraper: Twelve-hour and hourly forecasts, active watches and warnings, and the latest reading from the nearest station, for any US location](https://api.apify.com/v2/key-value-stores/ZQOcNAOHrIgTacAmy/records/nws-weather-scraper.jpg)

Pulls National Weather Service data for any US location: twelve-hour and hourly forecasts, active watches and warnings, and the latest reading from the nearest observation station.

The NWS publishes everything through `api.weather.gov`, a public API built for programmatic use. There's no key, no quota and, so there's nothing for a browser or a proxy to solve. A four-location forecast run finishes in about two seconds. The one thing NWS asks for is a User-Agent that identifies you, which this Actor sends on every request.

**Agent skill: [SKILL.md](https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/nws-weather-scraper.md)**

```
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/nws-weather-scraper.md
```

### What you get

| Field | What it holds |
|---|---|
| `location`, `locationType` | The location as normalised, and whether it resolved as a `point` or a `zone` |
| `latitude`, `longitude` | Coordinates as sent to NWS (null for zone codes) |
| `city`, `state` | Nearest named place for points; zone name and state for zones |
| `gridId`, `gridX`, `gridY` | The NWS forecast grid cell the point resolved to |
| `forecastOffice`, `timeZone`, `radarStation` | Issuing WFO, IANA timezone, covering radar |
| `dataType`, `units` | Which product was fetched, and in which unit system |
| `temperature`, `temperatureUnit`, `windSpeed`, `windDirection` | Headline conditions from the first forecast period |
| `shortForecast`, `detailedForecast` | The one-line and full narrative for that period |
| `forecastPeriods`, `periodCount` | Every period returned, capped by `maxPeriods` |
| `observationStation` | Nearest station plus its latest temperature, dewpoint, humidity, wind, pressure and visibility |
| `alerts`, `alertCount` | Active alerts covering the location, with event, severity, urgency, headline, area, onset, expiry and full text |
| `forecastUrl`, `updatedAt` | Source URL and the timestamp NWS stamped on the product |
| `scrapedAt` | Run timestamp |

`RUN_SUMMARY` in the key-value store holds per-run counts, the settings you used, and any location that failed.

### Use cases

- **Demand forecasting.** Join daily highs against store-level sales history.
- **Field operations.** Check tomorrow's conditions across every depot before dispatch.
- **Event alerting.** Poll `dataType: "alerts"` on a schedule and page on any Severe or Extreme event.
- **Risk and insurance.** Build a warning history for a portfolio of insured sites.
- **Newsroom monitoring.** Watch heat, flood and winter-storm advisories across a set of counties.

### Quick start

Two cities, standard forecast:

```json
{
 "locations": ["39.7456,-97.0892", "40.7128,-74.0060"]
}
```

Hourly detail in metric, no alerts, one day of periods:

```json
{
 "locations": ["47.6062,-122.3321", "37.7749,-122.4194"],
 "dataType": "hourly",
 "units": "si",
 "includeAlerts": false,
 "maxPeriods": 24
}
```

Alert monitoring across a set of forecast zones:

```json
{
 "locations": ["KSZ009", "TXZ248", "FLZ173"],
 "dataType": "alerts",
 "contactEmail": "ops@example.com"
}
```

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `locations` | array | required | `"lat,lon"` strings or NWS zone codes such as `KSZ009` or `KSC201`. Mixed input is fine. |
| `dataType` | string | `forecast` | `forecast`, `hourly`, `alerts` or `observations`. |
| `units` | string | `us` | `us` for F, mph and inHg; `si` for C, km/h and Pa. |
| `includeAlerts` | boolean | `true` | Attach active alerts to every record. Always on when `dataType` is `alerts`. |
| `maxPeriods` | integer | `24` | Cap stored forecast periods per location. 0 means keep them all. |
| `contactEmail` | string | `""` | Added to the User-Agent so NWS can contact you about heavy usage. |

`units` only affects gridpoint products. Zone-code forecasts are text only: NWS rejects a `units` parameter on them and returns no numeric temperature, so `temperature` is null and the narrative arrives in the forecaster's own units.

### Output example

```json
{
 "location": "40.7128,-74.006",
 "locationType": "point",
 "latitude": 40.7128,
 "longitude": -74.006,
 "city": "New York",
 "state": "NY",
 "gridId": "OKX",
 "gridX": 33,
 "gridY": 42,
 "forecastOffice": "OKX",
 "timeZone": "America/New_York",
 "radarStation": "KDIX",
 "dataType": "forecast",
 "units": "us",
 "temperature": 90,
 "temperatureUnit": "F",
 "windSpeed": "6 to 10 mph",
 "windDirection": "SW",
 "shortForecast": "Mostly Sunny",
 "detailedForecast": "Mostly sunny, with a high near 90. Heat index values as high as 102. Southwest wind 6 to 10 mph.",
 "forecastPeriods": [
 {
 "number": 1,
 "name": "Today",
 "startTime": "2026-08-06T07:00:00-04:00",
 "endTime": "2026-08-06T18:00:00-04:00",
 "isDaytime": true,
 "temperature": 90,
 "temperatureUnit": "F",
 "probabilityOfPrecipitation": { "unitCode": "wmoUnit:percent", "value": 9 },
 "windSpeed": "6 to 10 mph",
 "windDirection": "SW",
 "shortForecast": "Mostly Sunny",
 "detailedForecast": "Mostly sunny, with a high near 90."
 }
 ],
 "periodCount": 4,
 "observationStation": null,
 "alerts": [
 {
 "id": "urn:oid:2.49.0.1.840.0.c1834a44913d904db6ce91b95b70d126406790b2.004.1",
 "event": "Heat Advisory",
 "severity": "Moderate",
 "urgency": "Expected",
 "certainty": "Likely",
 "headline": "Heat Advisory issued August 6 at 2:26AM EDT until August 7 at 8:00PM EDT by NWS Upton NY",
 "areaDesc": "New York (Manhattan); Bronx; Richmond (Staten Is.); Kings (Brooklyn); Northern Queens; Southern Queens",
 "onset": "2026-08-06T11:00:00-04:00",
 "expires": "2026-08-06T10:30:00-04:00",
 "senderName": "NWS Upton NY",
 "description": "* WHAT.Heat index values up to 104 expected."
 }
 ],
 "alertCount": 1,
 "forecastUrl": "https://api.weather.gov/gridpoints/OKX/33,42/forecast?units=us",
 "updatedAt": "2026-08-06T09:41:52+00:00",
 "scrapedAt": "2026-08-06T11:35:21.410Z"
}
```

With `dataType: "observations"` the `observationStation` object is populated instead of `forecastPeriods`:

```json
{
 "stationId": "KMYZ",
 "stationName": "Marysville Municipal Airport",
 "timestamp": "2026-08-06T10:55:00+00:00",
 "textDescription": "Fog/Mist",
 "temperature": { "value": 68, "unit": "F" },
 "dewpoint": { "value": 64.4, "unit": "F" },
 "relativeHumidity": { "value": 88.27, "unit": "%" },
 "windSpeed": { "value": 4.7, "unit": "mph" },
 "windDirection": { "value": 80, "unit": "deg" },
 "windGust": null,
 "barometricPressure": { "value": 30.03, "unit": "inHg" },
 "visibility": { "value": 3, "unit": "mi" }
}
```

### Finding a location code

Coordinates are the simplest input, and anything you can read off a map works. Zone codes are useful when you want an official NWS area rather than a single point.

| Code shape | Meaning | Example |
|---|---|---|
| `SSZnnn` | Public forecast zone | `KSZ009` (Washington County, KS) |
| `SSCnnn` | County zone | `KSC201` |
| `SSZnnn` (fire) | Fire weather zone | `KSZ009` |

To find the zone for a point, run the Actor once with coordinates and read `forecastUrl`, or open `https://api.weather.gov/points/{lat},{lon}` and look at `properties.forecastZone`. Full zone URLs are accepted too, because the Actor takes the last path segment.

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/arman-bd~nws-weather-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
 "locations": ["40.7128,-74.0060", "41.8781,-87.6298"],
 "dataType": "forecast",
 "units": "us",
 "maxPeriods": 8
 }'
```

### JavaScript example

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('arman-bd/nws-weather-scraper').call({
 locations: ['40.7128,-74.0060', '47.6062,-122.3321'],
 dataType: 'forecast',
 includeAlerts: true,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const r of items) {
 console.log(`${r.city}, ${r.state}: ${r.temperature}${r.temperatureUnit}, ${r.shortForecast} (${r.alertCount} alerts)`);
}
```

### Limits and behaviour

- Every point costs two hops. `/points/{lat},{lon}` resolves a 2.5 km grid cell, and the forecast URL it returns is fetched second. The Actor follows whatever URL NWS hands back rather than building gridpoint URLs itself, so office re-gridding never breaks a run.
- Coordinates are rounded to four decimals. Anything finer gets a 301 redirect to the nearest supported point, and rounding up front saves the round trip.
- Coverage is US only. A point outside NWS coverage returns 404 with `InvalidPoint`, lands in `RUN_SUMMARY.failures`, and the run continues.
- A missing User-Agent is a 403. NWS refuses anonymous clients outright, so the Actor always identifies itself, and `contactEmail` puts your address in the header.
- Transient errors get retried. 429s and 5xx responses get three attempts with linear backoff, while 400 and 404 are treated as fatal for that location only.
- Public data only. NWS output is US government work in the public domain.

### FAQ

**Do I need an API key?** No. You supply no credentials.

**Do I need a proxy?** No. Proxy configuration is not required to run this Actor.

**Does it work outside the United States?** No. NWS covers US states, territories and coastal waters only, and other coordinates return 404. For worldwide coverage use the Open-Meteo Scraper below.

**Why is `temperature` null for a zone code?** Zone forecasts are the forecaster's narrative text with no structured values. Pass coordinates when you need numbers.

**How current is the data?** Gridpoint forecasts are re-issued several times a day, alerts are effectively real time, and station observations update roughly hourly. `updatedAt` carries the timestamp NWS stamped on the product it served.

**Can I schedule it?** Yes, and that is the intended use. There is no quota to exhaust, so hourly or even quarter-hourly alert sweeps are fine.

# Actor input Schema

## `locations` (type: `array`):

US coordinates as "lat,lon" (for example "39.7456,-97.0892") or NWS zone codes (for example "KSZ009"). Coordinates are rounded to four decimals, which is the finest precision api.weather.gov accepts. Non-US points are reported as failures.

## `dataType` (type: `string`):

Which product to fetch. 'forecast' returns twelve-hour periods, 'hourly' returns hour-by-hour periods, 'observations' returns the latest reading from the nearest station, and 'alerts' skips the forecast and returns only active warnings.

## `units` (type: `string`):

'us' returns Fahrenheit, mph and inches of mercury; 'si' returns Celsius, km/h and pascals. Zone-code forecasts are text-only and ignore this setting.

## `includeAlerts` (type: `boolean`):

Attach every active NWS alert covering the location to the record. Turn off for a smaller dataset when you only want the forecast.

## `maxPeriods` (type: `integer`):

Cap how many forecast periods are stored per location. Hourly forecasts return around 156 periods, so the default of 24 keeps records small. Set 0 to keep every period.

## `contactEmail` (type: `string`):

Optional. NWS asks API clients to identify themselves; when set, this address is added to the User-Agent header so NWS can reach you about heavy usage. Leave empty to send the Actor's Store URL instead.

## Actor input object example

```json
{
  "locations": [
    "47.6062,-122.3321",
    "KSZ009"
  ],
  "dataType": "forecast",
  "units": "us",
  "includeAlerts": true,
  "maxPeriods": 24,
  "contactEmail": "you@example.com"
}
```

# Actor output Schema

## `items` (type: `string`):

Every record the run produced.

## `runsummary` (type: `string`):

The RUN\_SUMMARY record from the run's key-value store.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "locations": [
        "39.7456,-97.0892",
        "40.7128,-74.0060"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("arman-bd/nws-weather-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "locations": [
        "39.7456,-97.0892",
        "40.7128,-74.0060",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("arman-bd/nws-weather-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "locations": [
    "39.7456,-97.0892",
    "40.7128,-74.0060"
  ]
}' |
apify call arman-bd/nws-weather-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,arman-bd/nws-weather-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/OgEh2ZhIJxZpakqYA/builds/1y4pLM9QhGPqWLzj1/openapi.json
