GET /climate/indices
Returns climate event indices for one GPS point over a date range: counts of frost days, summer days, tropical nights, the longest dry spell, heavy-rainfall days, the wettest single day, total rainfall and the strongest wind gust. They are computed from ERA5 reanalysis enriched with true daily minimum and maximum temperatures and daily wind gust, decoded from GRIB into a local grid store. The lookup is local, so a covered window resolves in milliseconds without an account or API key.
These indices are direct trigger signals: parametric insurance thresholds
(storm, frost, heat), agriculture and construction planning. The definitions follow
the standard ETCCDI / Climdex climate-index conventions. 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 an index is null because its source variable is missing from the store: that
is reported through the index value and coverage, not as an error. Requests the
service cannot answer - invalid coordinates, an invalid period, an unknown index, an
out-of-range threshold, 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 |
indices | string | no | Comma-separated subset of the index selectors below; all by default |
hot_threshold | number | no | Summer-day temperature threshold in Celsius; defaults to 25 |
dry_mm | number | no | Dry-day precipitation ceiling in millimetres; defaults to 1 |
heavy_mm | number | no | Heavy-rain precipitation floor in millimetres; defaults to 10 |
The window [from, to] is the unit of the product and is capped at 366 days per
call. Index selectors for the indices parameter are: frost_days,
summer_days, tropical_nights, max_dry_spell, heavy_rain_days, rx1day,
prcptot, max_gust.
GET /climate/indices?lat=48.8566&lon=2.3522&from=2024-01-01&to=2024-12-31&indices=frost_days,summer_days
Index definitions (ETCCDI)
| Selector | data.indices field | Unit | Definition |
|---|---|---|---|
frost_days | frost_days | days | Days with daily minimum temperature below 0 C (fixed threshold) |
summer_days | summer_days | days | Days with daily maximum temperature above hot_threshold (default 25 C) |
tropical_nights | tropical_nights | days | Days with daily minimum temperature above 20 C (fixed threshold) |
max_dry_spell | max_dry_spell_days | days | Longest run of consecutive days with precipitation below dry_mm (default 1 mm) |
heavy_rain_days | heavy_rain_days | days | Days with precipitation at or above heavy_mm (default 10 mm) |
rx1day | rx1day_mm | mm | Maximum single-day precipitation total in the window |
prcptot | prcptot_mm | mm | Total precipitation over the window |
max_gust | max_gust_ms | m/s | Maximum daily wind gust in the window |
The frost (0 C) and tropical-night (20 C) thresholds are fixed by the ETCCDI
definitions; only the summer-day temperature, the dry-day ceiling and the
heavy-rain floor are configurable, and the threshold values actually applied are
echoed back in data.thresholds.
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 in the window covered by the store |
indices | object | Requested index values; see the table above |
thresholds | object | Threshold values actually applied: hot_celsius, dry_mm, heavy_mm |
grid | object | Representative grid cell used for the answer; omitted if no cell served |
coverage | object | Completeness marker for the requested window and indices |
Each requested index appears in data.indices. An index that was not requested
is omitted. An index whose source variable is missing from the store (for
example a wind gust the store does not carry) is serialized as null and explained
through coverage, rather than failing the request.
coverage
| Field | Type | Description |
|---|---|---|
complete | bool | true when every day was covered and every requested index could be computed |
reason | string | Present when complete: false, naming the missing days or the index whose source variable is absent |
Source honesty
The temperature indices are computed on the true daily minimum and maximum
temperatures (t2m_min / t2m_max), never on a daily-mean approximation:
frost_days and tropical_nights use the daily minimum, summer_days uses the
daily maximum. The precipitation indices use the daily precipitation total (tp)
and max_gust uses the daily wind gust.
When the store does not carry the source variable an index needs, that index is
returned as null with coverage.complete: false and a reason naming the field
and its underlying variable (for example max_gust_ms (gust)). This is an honest
gap, not an error: the rest of the answer is still served. ERA5 is a gridded
reanalysis with a typical global mesh of about 0.25 degrees, not a weather-station
reading. A window with no covered day at all returns 400 OUT_OF_RANGE.
Example - a full year of temperature indices
{
"data": {
"lat": 48.8566,
"lon": 2.3522,
"from": "2024-01-01",
"to": "2024-12-31",
"days_counted": 366,
"indices": {
"frost_days": 24,
"summer_days": 41
},
"thresholds": { "hot_celsius": 25, "dry_mm": 1, "heavy_mm": 10 },
"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 - an index with a missing source variable
When a requested index has no source variable in the store, it comes back null
and coverage explains the gap. The request still succeeds with 200.
{
"data": {
"lat": 48.8566,
"lon": 2.3522,
"from": "2024-01-01",
"to": "2024-12-31",
"days_counted": 366,
"indices": {
"heavy_rain_days": 9,
"rx1day_mm": 38.4,
"max_gust_ms": null
},
"thresholds": { "hot_celsius": 25, "dry_mm": 1, "heavy_mm": 10 },
"grid": { "lat": 48.75, "lon": 2.25, "distance_km": 13.42 },
"coverage": {
"complete": false,
"reason": "no source data for: max_gust_ms (gust)"
}
},
"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_INDEX | indices contains a selector outside the supported set |
| 400 | INVALID_THRESHOLD | hot_threshold, dry_mm or heavy_mm is non-numeric or out of range |
| 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": "unknown index 'snow_days' (expected: frost_days, summer_days, tropical_nights, max_dry_spell, heavy_rain_days, rx1day, prcptot, max_gust)", "code": "INVALID_INDEX" }
{ "error": "hot_threshold 120 out of range [-50, 60]", "code": "INVALID_THRESHOLD" }
{ "error": "requested window has no covered day within 2021-01-01..2026-06-20", "code": "OUT_OF_RANGE" }
See also
GET /climate/aggregate- period mean, total and daily-mean extremes over a date range.GET /climate/anomaly- observed value versus the 1991-2020 climate normal.- For agents - discovery surfaces, the live
/catalogand how settlement works.