GET /climate/anomaly
Returns the climate anomaly for one GPS point over a date range: the departure of the observed ERA5 value from the 1991-2020 WMO climate normal, for temperature, precipitation and wind. For each family you get the observed value, the normal and their difference, so an agent can answer “this month was X degrees above the normal here” in a single call. The lookup is local, so a covered window resolves in milliseconds without an account or API key.
This is the departure signal: parametric insurance and ESG / climate reporting
need the deviation from a stable baseline, not just a raw reading. The baseline is
the standard 30-year WMO normal, and it is the only reference the endpoint uses. See
the live /catalog for the authoritative endpoint listing and
price.
x402 golden rule: the agent pays for the answer to its question. A
well-formed request where the observed value and the normal are both available
is a successful answer -> 200, even when one family is null because its
observation or its normal is missing: that is reported through coverage. Requests
the service cannot answer - invalid coordinates, an invalid period, an unknown
variable, or a window where nothing can be paired - leave the 200 range.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
lat | number | yes | Latitude in decimal degrees, from -90 to 90 |
lon | number | yes | Longitude in decimal degrees, from -180 to 180 |
from | string | yes | Start date YYYY-MM-DD, inclusive |
to | string | yes | End date YYYY-MM-DD, inclusive; the window spans at most 366 days |
variables | string | no | Comma-separated subset of temperature, precipitation, wind; all by default |
The window [from, to] is the unit of the product and is capped at 366 days per
call.
GET /climate/anomaly?lat=48.8566&lon=2.3522&from=2024-07-01&to=2024-07-31
GET /climate/anomaly?lat=48.8566&lon=2.3522&from=2024-07-01&to=2024-07-31&variables=temperature
200 response - UnifiedResponse
{
"data": { ... },
"provenance": {
"source": "era5-copernicus",
"fetched_at": "2026-06-20T12:00:00Z",
"freshness": { "kind": "snapshot", "as_of": "2026-06-19T00:00:00Z" }
}
}
provenance.source: stable identifier of the served store source, typicallyera5-copernicus.freshness.kind:snapshotfor ERA5 reanalysis;as_ofis the store production date that backed the answer.- ERA5 values are derived from Copernicus Climate Change Service information.
Fields of data
| Field | Type | Description |
|---|---|---|
lat | number | Latitude exactly echoed from the request |
lon | number | Longitude exactly echoed from the request |
from | string | Start date echoed in YYYY-MM-DD form |
to | string | End date echoed in YYYY-MM-DD form |
reference_period | string | WMO normal baseline used, for example 1991-2020 |
days_counted | number | Days paired (observed and normal) for at least one requested family |
temperature | object | null | Temperature anomaly; omitted when not requested, null when unpaired |
precipitation | object | null | Precipitation anomaly; omitted when not requested, null when unpaired |
wind | object | null | Wind anomaly; omitted when not requested, null when unpaired |
grid | object | Representative grid cell used for the answer; omitted if no cell served |
coverage | object | Completeness marker for the requested window and families |
A family that was not requested via variables is omitted. A family that was
requested but cannot be paired - no observation, or no normal - is serialized as
null and explained through coverage.
temperature
| Field | Type | Description |
|---|---|---|
observed_celsius | number | Mean observed daily temperature over the paired days |
normal_celsius | number | Mean 1991-2020 normal over the same days |
anomaly_celsius | number | Observed minus normal, in Celsius |
precipitation
| Field | Type | Description |
|---|---|---|
observed_mm | number | Observed precipitation total over the paired days |
normal_mm | number | Sum of the daily normals over the same days |
anomaly_mm | number | Observed minus normal, in millimetres |
wind
| Field | Type | Description |
|---|---|---|
observed_speed_ms | number | Mean observed 10 m wind speed over the paired days |
normal_speed_ms | number | Mean normal wind speed over the same days |
anomaly_speed_ms | number | Observed minus normal, in metres per second |
coverage
| Field | Type | Description |
|---|---|---|
complete | bool | true when every requested family was paired across the window |
reason | string | Present when complete: false, distinguishing an observation gap from a climatological normal not available |
Baseline honesty
The reference is always the 1991-2020 WMO climate normal (a 30-year baseline),
made explicit in reference_period. The endpoint never invents an anomaly against a
short window dressed up as a normal: a value is only an anomaly when it is measured
against this 30-year reference.
- The normal is a monthly climatology. Each observed day is paired with the normal for its month; over a multi-month window the normals are therefore weighted by the number of days in each month. Temperature and wind are means of the paired observed and normal values; precipitation is the observed total versus the sum of the daily normals.
- A day counts only if it carries both an observation and a normal. A family
with no observation returns
nullwith anobservation gapreason; a family with an observation but no normal returnsnullwith aclimatological normal not availablereason. - ERA5 is a gridded reanalysis with a typical global mesh of about 0.25 degrees, not
a weather-station reading. A window where nothing can be paired - no observed
day, or no normal for any family - returns 400
OUT_OF_RANGE.
Example - a warm month
{
"data": {
"lat": 48.8566,
"lon": 2.3522,
"from": "2024-07-01",
"to": "2024-07-31",
"reference_period": "1991-2020",
"days_counted": 31,
"temperature": {
"observed_celsius": 22.8,
"normal_celsius": 20.1,
"anomaly_celsius": 2.7
},
"precipitation": {
"observed_mm": 18.4,
"normal_mm": 52.6,
"anomaly_mm": -34.2
},
"grid": { "lat": 48.75, "lon": 2.25, "distance_km": 13.42 },
"coverage": { "complete": true }
},
"provenance": {
"source": "era5-copernicus",
"fetched_at": "2026-06-20T12:00:00Z",
"freshness": { "kind": "snapshot", "as_of": "2026-06-19T00:00:00Z" }
}
}
Example - a family with no normal available
When a requested family has an observation but no climate normal at the grid cell,
the anomaly cannot be formed: the family is null and coverage says why. The
request still succeeds with 200 as long as another family was paired.
{
"data": {
"lat": 48.8566,
"lon": 2.3522,
"from": "2024-07-01",
"to": "2024-07-31",
"reference_period": "1991-2020",
"days_counted": 31,
"temperature": {
"observed_celsius": 22.8,
"normal_celsius": 20.1,
"anomaly_celsius": 2.7
},
"wind": null,
"grid": { "lat": 48.75, "lon": 2.25, "distance_km": 13.42 },
"coverage": {
"complete": false,
"reason": "wind: climatological normal not available"
}
},
"provenance": {
"source": "era5-copernicus",
"fetched_at": "2026-06-20T12:00:00Z",
"freshness": { "kind": "snapshot", "as_of": "2026-06-19T00:00:00Z" }
}
}
Errors
Only requests the service cannot answer leave the 200 range.
| Status | code | Case |
|---|---|---|
| 400 | INVALID_COORDS | lat/lon missing, non-numeric or outside valid bounds |
| 400 | INVALID_PERIOD | from/to missing or malformed, to before from, or window over 366 days |
| 400 | INVALID_VARIABLE | variables contains a family outside temperature, precipitation, wind |
| 400 | OUT_OF_RANGE | The window is well formed but nothing can be paired (no observed day, or no normal for any family) |
| 500 | INTERNAL | Internal error (detail logged, not exposed) |
{ "error": "invalid from '01-07-2024', expected YYYY-MM-DD", "code": "INVALID_PERIOD" }
{ "error": "requested window has no observed day or no normal within the store", "code": "OUT_OF_RANGE" }
See also
GET /climate/indices- threshold-based climate event indices over a date range.GET /climate/return-period- empirical return period of an observed value against the record.- For agents - discovery surfaces, the live
/catalogand how settlement works.