GET /climate/return-period
Places a target year’s seasonal climate value in the historical
distribution at one GPS point: how a chosen season (a month, or a custom
month-day window) of a given year ranks against the same season across the ingested
ERA5 record - currently a rolling ~5-year window, so today every verdict carries
a short sample flagged via coverage until deeper history is ingested. You get the
season value, its empirical percentile and rank, the
corresponding return period in years, and - always - the size of the sample it was
ranked against. The lookup is local, so a covered point resolves in milliseconds
without an account or API key.
This is the rarest and most defensible climate derivative: reinsurance, climate
risk and climate finance need to answer “is this July in the top 5% over the
record?” - a ranking, not a raw reading. 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 with a usable historical sample is a successful answer ->
200, even when the sample is short or the target year is not covered: those
cases are served with null ranking fields and an honest coverage. Requests the
service cannot answer - invalid coordinates, an invalid season or year, an unknown
variable or statistic, or a point with no historical sample 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 |
year | number | yes | Target year to place in the distribution, from 1940 to 2100 |
month | number | season | Whole calendar month 1..12 as the season; or use from_md/to_md |
from_md | string | season | Season start as MM-DD; requires to_md, on or before it (no year wrap) |
to_md | string | season | Season end as MM-DD; requires from_md |
variable | string | no | temperature or precipitation; defaults to temperature |
stat | string | no | Seasonal reduction: mean, sum or max; defaults per variable |
Define the season either with a whole month or with both from_md and
to_md - never a mix, and the custom window must not wrap across the year boundary.
The statistic must be physically consistent with the variable: temperature
accepts mean (default) or max; precipitation accepts sum (default) or
max.
GET /climate/return-period?lat=48.8566&lon=2.3522&month=7&year=2024&variable=temperature&stat=mean
GET /climate/return-period?lat=48.8566&lon=2.3522&from_md=06-01&to_md=08-31&year=2024&variable=precipitation&stat=sum
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 |
season | string | Resolved season label, for example 07-01..07-31 |
year | number | Target year echoed from the request |
variable | string | temperature or precipitation |
stat | string | Seasonal reduction applied: mean, sum or max |
unit | string | Unit of value: celsius or millimetre |
value | number | null | The target year’s seasonal value; null when the year is not covered |
percentile | number | null | Empirical percentile (rank / sample_years, in %); null when uncovered |
rank | number | null | Empirical rank of the value within the sample; null when uncovered |
sample_years | number | Number of historical years in the sample - always present |
return_period_years | number | null | Empirical return period in years; null when uncovered |
method | string | Always empirical - see below |
grid | object | Representative grid cell used for the answer; omitted if no cell served |
coverage | object | Completeness marker for the sample and the target year |
coverage
| Field | Type | Description |
|---|---|---|
complete | bool | true when the target year is covered and the sample is large enough |
reason | string | Present when complete: false: target year not covered, insufficient history: N years, or partial seasonal years excluded |
Method honesty
The ranking is empirical: the target year’s value is placed by its rank in the
observed sample, and method is always empirical. There is no GEV or other
parametric fit and no confidence interval in this version - the percentile and
return period are exactly the rank-based figures, nothing extrapolated.
Two honesty guarantees:
sample_yearsis always returned. A percentile over 12 years and one over 60 years are not the same claim; the sample size is exposed so the agent can judge it.- A short sample is not hidden. Below a floor of 20 sample years, the figures are
still served but
coverage.completeisfalsewith a reason such asinsufficient history: 14 years. Likewise, if the target year is not covered by the record,value,percentile,rankandreturn_period_yearsarenullwhilesample_yearsandmethodremain present.
ERA5 is a gridded reanalysis with a typical global mesh of about 0.25 degrees, not a
weather-station reading. A point with no historical sample at all returns
400 OUT_OF_RANGE.
Example - a hot July ranked over the record
{
"data": {
"lat": 48.8566,
"lon": 2.3522,
"season": "07-01..07-31",
"year": 2024,
"variable": "temperature",
"stat": "mean",
"unit": "celsius",
"value": 23.4,
"percentile": 97.1,
"rank": 34,
"sample_years": 35,
"return_period_years": 35.0,
"method": "empirical",
"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 - insufficient history
When fewer than 20 years are available, the figures are still returned, but the
sample size is exposed and coverage flags the limitation. The agent decides
whether the ranking is strong enough to act on.
{
"data": {
"lat": 12.3456,
"lon": 98.7654,
"season": "07-01..07-31",
"year": 2024,
"variable": "precipitation",
"stat": "sum",
"unit": "millimetre",
"value": 612.8,
"percentile": 92.9,
"rank": 13,
"sample_years": 14,
"return_period_years": 14.0,
"method": "empirical",
"grid": { "lat": 12.25, "lon": 98.75, "distance_km": 11.07 },
"coverage": {
"complete": false,
"reason": "insufficient history: 14 years"
}
},
"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 | Missing/invalid year (or out of 1940..2100), or an invalid/mixed/wrapping season window |
| 400 | INVALID_VARIABLE | Unknown variable, unknown stat, or a stat inconsistent with the variable |
| 400 | OUT_OF_RANGE | The request is well formed but the point has no historical sample at all |
| 500 | INTERNAL | Internal error (detail logged, not exposed) |
{ "error": "year 1899 out of range [1940, 2100]", "code": "INVALID_PERIOD" }
{ "error": "provide either 'month' or both 'from_md' and 'to_md', not a mix", "code": "INVALID_PERIOD" }
{ "error": "stat is not valid for the requested variable (temperature accepts mean/max, precipitation accepts sum/max)", "code": "INVALID_VARIABLE" }
See also
GET /climate/anomaly- observed value versus the 1991-2020 climate normal.GET /climate/point- single-date climate variables at a GPS point.- For agents - discovery surfaces, the live
/catalogand how settlement works.