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

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
indicesstringnoComma-separated subset of the index selectors below; all by default
hot_thresholdnumbernoSummer-day temperature threshold in Celsius; defaults to 25
dry_mmnumbernoDry-day precipitation ceiling in millimetres; defaults to 1
heavy_mmnumbernoHeavy-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)

Selectordata.indices fieldUnitDefinition
frost_daysfrost_daysdaysDays with daily minimum temperature below 0 C (fixed threshold)
summer_dayssummer_daysdaysDays with daily maximum temperature above hot_threshold (default 25 C)
tropical_nightstropical_nightsdaysDays with daily minimum temperature above 20 C (fixed threshold)
max_dry_spellmax_dry_spell_daysdaysLongest run of consecutive days with precipitation below dry_mm (default 1 mm)
heavy_rain_daysheavy_rain_daysdaysDays with precipitation at or above heavy_mm (default 10 mm)
rx1dayrx1day_mmmmMaximum single-day precipitation total in the window
prcptotprcptot_mmmmTotal precipitation over the window
max_gustmax_gust_msm/sMaximum 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, 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 in the window covered by the store
indicesobjectRequested index values; see the table above
thresholdsobjectThreshold values actually applied: hot_celsius, dry_mm, heavy_mm
gridobjectRepresentative grid cell used for the answer; omitted if no cell served
coverageobjectCompleteness 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

FieldTypeDescription
completebooltrue when every day was covered and every requested index could be computed
reasonstringPresent 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.

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_INDEXindices contains a selector outside the supported set
400INVALID_THRESHOLDhot_threshold, dry_mm or heavy_mm is non-numeric or out of range
400OUT_OF_RANGEThe window is well formed but no day in it is covered by the store
500INTERNALInternal 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