GET /climate/aggregate

Returns period aggregates for one GPS point over a date range: mean temperature with its daily extremes, total precipitation and mean wind, computed from ERA5 reanalysis daily values decoded from GRIB into a local grid store. Ask for a month or a season and get the summary in a single call, instead of assembling 30 to 90 individual point lookups yourself. The lookup is local, so a covered window resolves in milliseconds without an account or API key.

This is the period-summary signal: the climate balance of a month or a season at a point, for analytics and energy operations. 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 over a covered window is a successful answer -> 200, even when some days inside the window have a grid gap or a requested family has no data: those cases are reported through days_counted, a null family and coverage. Requests the service cannot answer - invalid coordinates, an invalid period, an unknown variable, or a window with no covered day at all - 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. Use variables to reduce the payload when the agent only needs one family.

GET /climate/aggregate?lat=48.8566&lon=2.3522&from=2024-06-01&to=2024-08-31
GET /climate/aggregate?lat=48.8566&lon=2.3522&from=2024-06-01&to=2024-08-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
days_countednumberDays covered by at least one requested family
temperatureobject | nullTemperature aggregate; omitted when not requested, null when no data
precipitationobject | nullPrecipitation aggregate; omitted when not requested, null when no data
windobject | nullWind aggregate; omitted when not requested, null when no data
gridobjectRepresentative grid cell used for the answer; omitted if no cell served
coverageobjectCompleteness marker for the requested window

A family that was not requested via variables is omitted. A family that was requested but has no value over the window is serialized as null - the same honesty convention as GET /climate/point.

temperature

FieldTypeDescription
mean_celsiusnumberMean of the daily mean temperatures over the covered days
min_celsiusnumberLowest daily-mean temperature over the covered days
max_celsiusnumberHighest daily-mean temperature over the covered days

precipitation

FieldTypeDescription
total_mmnumberSum of daily precipitation over the covered days

wind

FieldTypeDescription
mean_speed_msnumberMean 10 m wind speed in metres per second
mean_direction_degnumberMean meteorological direction in degrees: 0 = north, 90 = east

grid

FieldTypeDescription
latnumberLatitude of the representative grid cell
lonnumberLongitude of the representative grid cell
distance_kmnumberDistance from the requested point to that cell

coverage

FieldTypeDescription
completebooltrue when every day in the window had a usable value for each family
reasonstringPresent when complete: false, explaining the gap

Aggregation honesty

Each family is aggregated with the convention that fits it, and these conventions matter:

  • Precipitation is a total (sum), not a mean: total_mm adds the daily totals over the covered days.
  • Temperature and wind are means over the covered days.
  • min_celsius / max_celsius are extremes of the daily-mean values, not hourly Tmin/Tmax. They tell you the coldest and warmest days in the window, not the lowest and highest instantaneous readings. For true daily Tmin/Tmax extremes, use GET /climate/indices.
  • ERA5 is a gridded reanalysis with a typical global mesh of about 0.25 degrees, not a weather-station reading.

A window that is only partly covered is reported honestly: uncovered days are dropped, days_counted reflects the covered days and a sum like total_mm is the total over those days only, with coverage.complete: false. The historical window is finite and moves with the ingested store (provenance.freshness.as_of names the dump). A window with no covered day at all returns 400 OUT_OF_RANGE.

Example - summer aggregate, all families

{
  "data": {
    "lat": 48.8566,
    "lon": 2.3522,
    "from": "2024-06-01",
    "to": "2024-08-31",
    "days_counted": 92,
    "temperature": { "mean_celsius": 21.3, "min_celsius": 13.8, "max_celsius": 29.6 },
    "precipitation": { "total_mm": 148.7 },
    "wind": { "mean_speed_ms": 3.4, "mean_direction_deg": 241.5 },
    "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 - partial window, one family with no data

A window covered only in part still returns 200: days_counted reflects the covered days, the precipitation sum is the total over those days, and a requested family with no value comes back null with coverage.complete: false.

{
  "data": {
    "lat": 48.8566,
    "lon": 2.3522,
    "from": "2024-06-01",
    "to": "2024-06-30",
    "days_counted": 28,
    "temperature": { "mean_celsius": 19.7, "min_celsius": 12.1, "max_celsius": 27.0 },
    "precipitation": { "total_mm": 41.2 },
    "wind": null,
    "grid": { "lat": 48.75, "lon": 2.25, "distance_km": 13.42 },
    "coverage": {
      "complete": false,
      "reason": "no wind data at this grid cell; 2 days in the window have no value"
    }
  },
  "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 no day in it is covered by the store
500INTERNALInternal error (detail logged, not exposed)
{ "error": "invalid from '01-06-2024', expected YYYY-MM-DD", "code": "INVALID_PERIOD" }
{ "error": "requested window has no covered day within 2021-01-01..2026-06-20", "code": "OUT_OF_RANGE" }

See also