Building the Weather-NWS Skill for OpenClaw

Feb 27, 2026

I wanted snow accumulation forecasts from OpenClaw, but the wttr.in skill only returned “Heavy snow” text. I needed: how much snow, when it starts. I discovered the National Weather Service API—free, no API key, US-only—and built a unified skill with AirNow AQI and global fallback.

Discovery: NWS API

The National Weather Service API returns structured data:

1
2
3
4
"snowfallAmount": {
  "values": [{"validTime": "2026-02-27T18:00:00/PT24H", "value": 13}],
  "unitCode": "wmoUnit:mm"
}

No API key required. Requests against api.weather.gov just work. But it’s US-only—international locations need wttr.in fallback.

Gridpoint API (/gridpoints/{wfo}/{x},{y}) has structured snow/ice/rain values. Zone forecast API is less detailed. Grid lookup requires geocoding first via /points/{lat},{lon}.

Later I discovered AirNow AQI—also no API key. Useful when deciding if post-storm shoveling is safe.

API Hierarchy

The skill auto-detects source based on location:

1
2
3
if is_us_location(location):
    return get_nws_data(location)  # Structured accumulations
return get_wttr_data(location)     # Text fallback

Temporal Query Detection

wttr.in returns day summaries. I wanted hourly breakdowns for “What time does the rain stop?”

The skill detects time qualifiers:

1
2
3
4
5
6
7
def strip_temporal_qualifiers(text):
    patterns = [
        r'\s+at\s+\d{1,2}\s*(?::\d{2})?\s*[APap][Mm]?',  # "at 8 PM"
        r'\s+around\s+\d{1,2}',                            # "around 3"
        r'\s+this\s+(?:morning|afternoon|evening)',
        r'\s+tonight',
    ]

When detected, it queries /gridpoints/{wfo}/{x},{y}/forecast/hourly instead—156 hourly periods (~7 days).

Phase Implementation

Phase 1: Core with Grid Data

get_weather.py orchestrates the unified call:

1
./scripts/get_weather.py "Seattle"

Script flow:

  1. Geocode via NWS /points endpoint for grid ID and forecast URLs
  2. Fetch structured forecast or hourly if temporal detected
  3. If --aqi flag, fetch AirNow current + forecast
  4. For non-US locations, hit wttr.in

Phase 2: Station Observations

NWS has observed conditions at weather stations. Added --current flag to compare forecast vs reality:

1
2
3
4
5
// /stations/{stationId}/observations/latest
"properties": {
  "temperature": {"value": 43, "unitCode": "wmoUnit:degC"},
  "windSpeed": {"value": 5.14, "unitCode": "wmoUnit:km_h-1"}
}

Phase 3: Edge Cases

  • Aviation TAFs: Terminal forecasts via /stations/{stationId}/tafs. Most civilian stations lack TAF—use --taf explicitly
  • Astronomical: Sunrise/sunset via skyfield library with --astro
  • Fire weather: Red flag warnings from NWS alerts API with --fire

Declaring Environment Variables

Clawdhub.ai validates skills before listing. Skills using environment variables must declare them in SKILL.md metadata or get flagged as suspicious.

Added the clawdis block to register AIRNOW_API_KEY:

1
metadata: {"clawdhub":{"emoji":"🌦️"},"clawdis":{"envVars":[{"name":"AIRNOW_API_KEY","required":false,"description":"AirNow API Key for AQI lookup"}]}}

This tells the validation system:

  • The skill accepts an optional AIRNOW_API_KEY environment variable
  • It’s not required—AirNow works unauthenticated with stricter rate limits
  • OpenClaw injects the variable if configured in clawdbot.json

Usage

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Standard forecast
./scripts/get_weather.py "Boston"

# Hourly with AQI
./scripts/get_weather.py "Boston at 8 PM" --aqi

# Current observations vs forecast
./scripts/get_weather.py "Boston" --current

# Aviation TAF (if available)
./scripts/get_weather.py "JFK" --taf

Available on clawdhub.ai/patelhiren/weather-nws.

AI AutomationProgrammingOpenClawPythonWeatherNWSAPIs

Using macOS newsyslog to Rotate Service Logs