---
title: "Day-of-year climate normals for a point."
method: GET
path: "/v1/climate/normals"
tags: ["Climate"]
---

# Day-of-year climate normals for a point.

`GET /v1/climate/normals`

## Request

- Base URL: `https://api.smarterweather.com`
- URL: `https://api.smarterweather.com/v1/climate/normals`
- Auth: HTTP bearer

Returns the 1991-2020 NOAA/NCEI daily climate normals — the average
high, low and mean temperature for each calendar day, plus the average
daily precipitation across the contiguous US — for a US point.

**Normals are climatology, not forecast.** They describe a 30-year base
period, carry no valid time, and do not change day to day. Use them to
answer "is tomorrow warmer than usual here?", not "how warm will it
be?".

**Where the values come from depends on the point,** and `meta.source`
says which. Across the contiguous US they are read from NCEI's gridded
normals at the requested coordinates (the nClimGrid 1/24° mesh, roughly
5 km), so they describe that point directly: `source` is `grid`.
Outside CONUS — Alaska and Hawaii — and for any day the
grid has no value for, they come from the nearest station record, and
`source` is `station`, or `mixed` where one series draws on both.

**A station-sourced normal describes the station, not your point.** The
response always names the station it resolved and how far away it is
(`station.distance_km`), since the station lookup is what anchors the
request either way. Past 50 km, a series carrying station-sourced
values sets `meta.approximate`; past 150 km no station is considered
representative and the endpoint returns `404`. A purely gridded series
is never flagged however far that station happens to sit, because the
grid value is the point's own normal. Where the flag does apply, NOAA's
own use limitation is that representativeness degrades with distance,
and elevation differences matter more than distance alone suggests.

**Coverage.** The gridded normals cover the contiguous US; the station
normals add roughly 7,400 US stations, Alaska and Hawaii among them.
Remote points outside CONUS may have no station in range.

**Precipitation is gridded-only.** `normal_precip` has no station
equivalent — NCEI's station normals publish no daily precipitation
normal — so it appears only on days whose `source` is `grid` and is
omitted everywhere else, including for every point outside CONUS.
Temperature normals are unaffected.

Days inside the requested range with no normal available are still
returned, with the temperature fields omitted and counted in
`meta.missing_days`, so the series stays aligned with the dates you
asked for.

Pass one location: `lat` and `lon`, `zip`, or `location`. A request
without one returns `400`; a ZIP or name that does not resolve
returns `404`; while location lookup is unavailable, a `zip` or
`location` request returns `503` with `Retry-After`.
The resolved place is echoed as `location`, present when the place
was resolved.

A point outside the 50 US states and DC returns `404`
`outside-coverage` with `next_steps`.

## Query parameters

- `lat` number, double
- `lon` number, double
- `zip` string
- `location` string
- `start` string, date
- `end` string, date
- `days` integer
- `unit` 'imperial' | 'metric'

## Response `200`

Climate normals for the requested point.

- ClimateNormals: Day-of-year climate normals for a point, read from the gridded normals across CONUS and otherwise from the nearest station with records. The resolved station is always named, whichever supplied the values.
  - `station` ClimateNormalsStation, required: The station a normals series was drawn from. Normals describe this station, not necessarily the point you asked about.
    - `station_id` string, required: GHCN station identifier.
    - `name` string: Station name as published by NCEI.
    - `state` string: Two-letter state or territory code.
    - `latitude` number, double, required: Station latitude.
    - `longitude` number, double, required: Station longitude.
    - `elevation_m` number, double: Station elevation in metres. Worth comparing against your point: an elevation difference biases normals more than distance does.
    - `distance_km` number, double, required: Great-circle distance from the requested point to the station, rounded to 0.1 km. The single number that says how much to trust this series.
  - `meta` ClimateNormalsMeta, required: Provenance for a normals response.
    - `normals_period` string: Base period the normals were computed over.
    - `source` string, required: Where the values came from: `station` for the NCEI station product, `grid` for gridded normals, `mixed` when a series draws on both.
    - `normals_method` 'official' | 'derived': How the normals were produced, which `source` does not answer. `official` means the provider publishes them as a normals product -- true of both the NCEI station normals and the NCEI gridded normals, so a station/grid mix is still wholly official. `derived` means they were computed from raw climate data, which is a weaker claim a client should qualify. Absent when upstream does not report it.
    - `unit` 'imperial' | 'metric', required: Unit system applied to the values: °F and inches for `imperial`, °C and millimetres for `metric`.
    - `approximate` boolean: True when a station more than 50 km away supplied values that are actually being served — `station.distance_km` says how far. It is not a plain distance test. A `grid` day is the requested point's own normal, so no station distance makes it approximate, and a series whose `source` is `grid` is never flagged however far the named station sits. A `mixed` series is flagged, because the station-sourced days in it genuinely are approximations.
    - `missing_days` integer: How many days in the requested range no normal is available for, from either the grid or the station. Those days are still present in `days` with their temperature fields omitted.
  - `days` ClimateNormalsDay[], required: Per-day normals series, in requested date order.
    - `date` string, date, required: Calendar date this slice was requested for.
    - `month` integer, required: Month of the day-of-year slice (1-12).
    - `day` integer, required: Day of month (1-31).
    - `normal_high` number, double: Average daily maximum temperature.
    - `normal_low` number, double: Average daily minimum temperature.
    - `normal_mean` number, double: Average daily mean temperature.
    - `normal_high_stddev` number, double: Standard deviation of the daily maximum. An interval, so it scales without the freezing-point offset: 3 °C of spread is 5.4 °F. Always from the nearest station, including on a day whose temperatures came from the grid — the gridded normals publish no standard deviations. On such a day the temperatures describe your point while this describes the named station, so read `station.distance_km` before narrating "unusually warm" off this spread. The same holds for `normal_low_stddev`, `normal_mean_stddev`, `years` and `completeness_flag`.
    - `normal_low_stddev` number, double: Standard deviation of the daily minimum. Station-sourced on the same terms as `normal_high_stddev`.
    - `normal_mean_stddev` number, double: Standard deviation of the daily mean. Station-sourced on the same terms as `normal_high_stddev`.
    - `normal_precip` number, double: Average daily precipitation amount — inches when `unit=imperial`, millimetres when `unit=metric`. The exact inverse of `normal_high_stddev`: that is always station-sourced, this is always grid-sourced. The station product publishes no daily precipitation normal at all, so this field comes only from the gridded CONUS product and is **omitted** on any day whose `source` is `station` — which includes every point outside CONUS. A series over Alaska or Hawaii carries temperatures with no precipitation until gridded normals exist there. `0` is a real value and is not the same as omitted: a daily normal is a 30-year mean spread across wet and dry days, so much of the arid West is a few hundredths of an inch. Do not treat a present zero as missing data.
    - `years` integer: Years of the base period contributing to this day, at the station. Station-sourced on the same terms as `normal_high_stddev`, so it does not describe the depth of a gridded value.
    - `completeness_flag` string: NCEI per-value completeness code, for the station. Station-sourced on the same terms as `normal_high_stddev`.
  - `location` Location: Resolved location.
    - `lat` number, double, required
    - `lon` number, double, required
    - `name` string, required: Human-readable place name, usually without the state; see `region`. The resolved place name, else the city, else the nearest city. Empty when none is known.
    - `region` string: State or region abbreviation.
    - `country` string: ISO 3166-1 alpha-2 country code.
    - `timezone` string, required: IANA timezone identifier.
    - `county` string: County name (US only).
    - `zip` string: ZIP code (US only).

## Other responses

- `400`: Bad request.
- `404`: Resource not found: a location that cannot be resolved (unknown ZIP, unrecognised free-text location), no upstream data for the location, or a missing resource. On a route that checks coverage (its description says so), a location outside the 50 US states and DC returns type `https://smarterweather.com/errors/outside-coverage`; that and the location-not-found problem carry `next_steps` (`recommended = coverage`).
- `502`: Bad gateway. The service received an invalid response from an upstream data source. Transient; clients should retry with backoff.
- `503`: Service unavailable. An upstream dependency is unavailable or shedding load (type `service-unavailable`). Transient; retry with backoff and honor any `Retry-After` header.
- `504`: Gateway timeout. An upstream data source did not respond within the service's budget.

---

[API](https://skmtc.dev/smarterweather/apis/smarter-weather-api.md) · [All operations](https://skmtc.dev/smarterweather/apis/smarter-weather-api/llms.txt) · [OpenAPI document](https://skmtc.dev/smarterweather/apis/smarter-weather-api/revisions/520d55a696e4?raw)
