api.weather.gov gridpoints: validTime is an interval, values are Celsius, and units=us is rejected

Checked 2026-09-08 · Health, environment and transport datasets

The raw /gridpoints endpoint returns each variable as a run-length list: every entry is {validTime, value} where validTime is an ISO-8601 interval like "2026-09-07T20:00:00+00:00/PT3H", so one entry can stand for three hours. Temperature is degC there whatever your locale, ?units=us is rejected with HTTP 400, and the first entry can be in the past. The /gridpoints/.../forecast child endpoint is the one that gives Fahrenheit.

What we saw

Temperature on the raw gridpoint is wmoUnit:degC and entries carry variable durations. Source (properties.temperature.uom and .values, 2026-09-08).

uom: wmoUnit:degC
{'validTime': '2026-09-07T18:00:00+00:00/PT1H', 'value': 33.888888888888886}
{'validTime': '2026-09-07T19:00:00+00:00/PT1H', 'value': 34.44444444444444}
{'validTime': '2026-09-07T20:00:00+00:00/PT3H', 'value': 36.666666666666664}
{'validTime': '2026-09-07T23:00:00+00:00/PT1H', 'value': 35.55555555555556}

143 entries cover 175 hours, so treating the list as hourly undercounts the horizon by a fifth. Source (properties.temperature.values, count and summed durations, 2026-09-08).

n values: 143
durations: [('PT1H', 122), ('PT3H', 11), ('PT2H', 10)]
sum hours 175

The array starts before the moment you fetch it: properties.validTimes says the block began 2026-09-07T18:00Z, fetched on 2026-09-08. values[0] is not "now". Source (properties.updateTime and .validTimes, 2026-09-08).

updateTime 2026-09-08T00:41:26+00:00 validTimes 2026-09-07T18:00:00+00:00/P7DT7H
first 2026-09-07T18:00:00+00:00/PT1H last 2026-09-15T00:00:00+00:00/PT1H

?units=us is not accepted on the raw gridpoint — HTTP 400 naming the parameter. Source (parameterErrors, 2026-09-08).

{
    "correlationId": "582efd95",
    "parameterErrors": [
        {
            "parameter": "query.units",
            "message": "Query parameter \"units\" is not recognized"
        }
    ],
    "title": "Bad Request",
    "type": "https://api.weather.gov/problems/BadRequest",
    "status": 400
}

The /forecast child of the same gridpoint defaults to US units and reports temperature as an integer with temperatureUnit F. Source (properties.units and properties.periods[0], 2026-09-08).

units us
{'name': 'Overnight', 'temperature': 76, 'temperatureUnit': 'F', 'probabilityOfPrecipitation': {'unitCode': 'wmoUnit:percent', 'value': 1}}

Other variables in the same document carry their own units — precipitation is millimetres, probability is percent — so there is no single unit for the payload. Source (properties.quantitativePrecipitation.uom and .probabilityOfPrecipitation.uom, 2026-09-08).

probabilityOfPrecipitation uom: wmoUnit:percent
quantitativePrecipitation uom: wmoUnit:mm

A request with no User-Agent is refused with HTTP 403 and an HTML page, so the header is mandatory here. Source (response with User-Agent suppressed, 2026-09-08).

http=403
<HTML><HEAD>
<TITLE>Access Denied</TITLE>
</HEAD><BODY>
<H1>Access Denied</H1>

The command

UA='opendatanotes.org maintainer@opendatanotes.org'; curl -sS -A "$UA" -o grid.json 'https://api.weather.gov/gridpoints/TOP/32,81'; python3 -c "import json,collections;d=json.load(open('grid.json'))['properties'];t=d['temperature'];print(t['uom']);[print(v) for v in t['values'][:4]];print('n',len(t['values']));print(collections.Counter(v['validTime'].split('/')[1] for v in t['values']).most_common())"
uom: wmoUnit:degC
{'validTime': '2026-09-07T18:00:00+00:00/PT1H', 'value': 33.888888888888886}
{'validTime': '2026-09-07T19:00:00+00:00/PT1H', 'value': 34.44444444444444}
{'validTime': '2026-09-07T20:00:00+00:00/PT3H', 'value': 36.666666666666664}
{'validTime': '2026-09-07T23:00:00+00:00/PT1H', 'value': 35.55555555555556}
n values: 143
durations: [('PT1H', 122), ('PT3H', 11), ('PT2H', 10)]

Checked 2026-09-08.

curl -sS -A "$UA" -o gridus.json -w 'units=us http=%{http_code}\n' 'https://api.weather.gov/gridpoints/TOP/32,81?units=us'; cat gridus.json
units=us http=400
{
    "correlationId": "582efd95",
    "parameterErrors": [
        {
            "parameter": "query.units",
            "message": "Query parameter \"units\" is not recognized"
        }
    ],
    "title": "Bad Request",
    "status": 400
}

Checked 2026-09-08.

curl -sS -A "$UA" -o fc.json 'https://api.weather.gov/gridpoints/TOP/32,81/forecast'; python3 -c "import json;p=json.load(open('fc.json'))['properties'];print('units',p.get('units'));x=p['periods'][0];print({k:x[k] for k in ('name','temperature','temperatureUnit','probabilityOfPrecipitation')})"
units us
{'name': 'Overnight', 'temperature': 76, 'temperatureUnit': 'F', 'probabilityOfPrecipitation': {'unitCode': 'wmoUnit:percent', 'value': 1}}

Checked 2026-09-08.

curl -sS -H 'User-Agent:' -o nua.json -w 'no UA http=%{http_code}\n' 'https://api.weather.gov/points/39.7456,-97.0892'; head -c 100 nua.json
no UA http=403
<HTML><HEAD>
<TITLE>Access Denied</TITLE>
</HEAD><BODY>
<H1>Access Denied</H1>

Checked 2026-09-08.

endpointtemperature unittime fieldentries
/gridpoints/TOP/32,81wmoUnit:degCvalidTime, an ISO-8601 interval (PT1H, PT2H, PT3H)143 entries covering 175 hours
/gridpoints/TOP/32,81/forecastF (properties.units = us)startTime and endTimenamed periods
/gridpoints/TOP/32,81?units=us--HTTP 400, parameter not recognized

Limits

Open question

Are the run-length durations always whole hours? A PT30M or a P1D entry would break code that parses the duration with a fixed pattern.

Plain text