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
| 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. 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, 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 |
days_counted | number | Days covered by at least one requested family |
temperature | object | null | Temperature aggregate; omitted when not requested, null when no data |
precipitation | object | null | Precipitation aggregate; omitted when not requested, null when no data |
wind | object | null | Wind aggregate; omitted when not requested, null when no data |
grid | object | Representative grid cell used for the answer; omitted if no cell served |
coverage | object | Completeness 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
| Field | Type | Description |
|---|---|---|
mean_celsius | number | Mean of the daily mean temperatures over the covered days |
min_celsius | number | Lowest daily-mean temperature over the covered days |
max_celsius | number | Highest daily-mean temperature over the covered days |
precipitation
| Field | Type | Description |
|---|---|---|
total_mm | number | Sum of daily precipitation over the covered days |
wind
| Field | Type | Description |
|---|---|---|
mean_speed_ms | number | Mean 10 m wind speed in metres per second |
mean_direction_deg | number | Mean meteorological direction in degrees: 0 = north, 90 = east |
grid
| Field | Type | Description |
|---|---|---|
lat | number | Latitude of the representative grid cell |
lon | number | Longitude of the representative grid cell |
distance_km | number | Distance from the requested point to that cell |
coverage
| Field | Type | Description |
|---|---|---|
complete | bool | true when every day in the window had a usable value for each family |
reason | string | Present 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_mmadds the daily totals over the covered days. - Temperature and wind are means over the covered days.
min_celsius/max_celsiusare 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, useGET /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.
| 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 no day in it is covered by the store |
| 500 | INTERNAL | Internal 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
GET /climate/degree-days- heating, cooling and growing degree days over a date range.GET /climate/indices- threshold-based climate indices from true daily Tmin/Tmax.- For agents - discovery surfaces, the live
/catalogand how settlement works.