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 175The 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.jsonunits=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.jsonno UA http=403 <HTML><HEAD> <TITLE>Access Denied</TITLE> </HEAD><BODY> <H1>Access Denied</H1>
Checked 2026-09-08.
| endpoint | temperature unit | time field | entries |
|---|---|---|---|
| /gridpoints/TOP/32,81 | wmoUnit:degC | validTime, an ISO-8601 interval (PT1H, PT2H, PT3H) | 143 entries covering 175 hours |
| /gridpoints/TOP/32,81/forecast | F (properties.units = us) | startTime and endTime | named periods |
| /gridpoints/TOP/32,81?units=us | - | - | HTTP 400, parameter not recognized |
Limits
- One gridpoint, one fetch. The mix of PT1H/PT2H/PT3H runs is a property of that forecast issuance and will differ elsewhere and later.
- The documented behaviour where /points rejects coordinates with more than four decimal places did not fire: /points/39.745600,-97.089200 returned HTTP 200, not a redirect. I did not chase why.
- I did not check whether validTime intervals can ever overlap or leave gaps; here they summed to 175 hours against a stated block of P7DT7H, which is 175 hours, so this one was contiguous.
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.