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

ParameterTypeRequiredDescription
latnumberyesLatitude in decimal degrees, from -90 to 90
lonnumberyesLongitude in decimal degrees, from -180 to 180
fromstringyesStart date YYYY-MM-DD, inclusive
tostringyesEnd date YYYY-MM-DD, inclusive; the window spans at most 366 days
variablesstringnoComma-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, typically era5-copernicus.
  • freshness.kind: snapshot for ERA5 reanalysis; as_of is the store production date that backed the answer.
  • ERA5 values are derived from Copernicus Climate Change Service information.

Fields of data

FieldTypeDescription
latnumberLatitude exactly echoed from the request
lonnumberLongitude exactly echoed from the request
fromstringStart date echoed in YYYY-MM-DD form
tostringEnd date echoed in YYYY-MM-DD form
reference_periodstringWMO normal baseline used, for example 1991-2020
days_countednumberDays paired (observed and normal) for at least one requested family
temperatureobject | nullTemperature anomaly; omitted when not requested, null when unpaired
precipitationobject | nullPrecipitation anomaly; omitted when not requested, null when unpaired
windobject | nullWind anomaly; omitted when not requested, null when unpaired
gridobjectRepresentative grid cell used for the answer; omitted if no cell served
coverageobjectCompleteness 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

FieldTypeDescription
observed_celsiusnumberMean observed daily temperature over the paired days
normal_celsiusnumberMean 1991-2020 normal over the same days
anomaly_celsiusnumberObserved minus normal, in Celsius

precipitation

FieldTypeDescription
observed_mmnumberObserved precipitation total over the paired days
normal_mmnumberSum of the daily normals over the same days
anomaly_mmnumberObserved minus normal, in millimetres

wind

FieldTypeDescription
observed_speed_msnumberMean observed 10 m wind speed over the paired days
normal_speed_msnumberMean normal wind speed over the same days
anomaly_speed_msnumberObserved minus normal, in metres per second

coverage

FieldTypeDescription
completebooltrue when every requested family was paired across the window
reasonstringPresent 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 null with an observation gap reason; a family with an observation but no normal returns null with a climatological normal not available reason.
  • 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.

StatuscodeCase
400INVALID_COORDSlat/lon missing, non-numeric or outside valid bounds
400INVALID_PERIODfrom/to missing or malformed, to before from, or window over 366 days
400INVALID_VARIABLEvariables contains a family outside temperature, precipitation, wind
400OUT_OF_RANGEThe window is well formed but nothing can be paired (no observed day, or no normal for any family)
500INTERNALInternal 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