# How rare was this season? Percentile and return period from ERA5 > Reinsurance and climate risk need a ranking, not a reading — where a season ranks in the historical record. One paid call returns the empirical percentile, return period and the sample size it used. Published 2026-06-28 · Updated 2026-08-16 · HTML version: https://invoket.com/blog/how-rare-was-this-season --- "Was this July hot?" is the wrong question for a risk model. The right one is "where does this July rank over the record, and over how many years?" — a *ranking*, not a reading. An agent in reinsurance, climate risk or climate finance needs the season's value placed in the historical distribution at the point, with the sample size exposed so it can judge the strength of the claim. One paid call — [`GET /climate/return-period`](/docs/api/climate-return-period) — returns the seasonal value, its empirical percentile and rank, the corresponding return period in years, and always the size of the sample it was ranked against. ## The problem: a percentile is meaningless without its sample A return period is a probability statement, and probability statements lie when the method and the sample are hidden. "Top 3% / a 1-in-35-year event" means one thing over 35 years of record and something much weaker over 12. Worse, a parametric fit (GEV and friends) can manufacture a confident-looking 1-in-100 figure from a short series — extrapolation dressed as measurement. `GET /climate/return-period` does the honest version: it ranks the target year **empirically** against the observed sample and tells you exactly how many years that sample holds. ## The call: a point, a season, a target year Define the season **either** as a whole calendar `month` **or** as a custom `from_md`/`to_md` window (no mix, no year-boundary wrap), pick the `variable` and the seasonal statistic, and name the `year` to place: ```http 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 ``` The statistic must be physically consistent with the variable: **temperature** accepts `mean` (default) or `max`; **precipitation** accepts `sum` (default) or `max`. The response places the year in the distribution: | Field | What the agent learns | |-----------------------|------------------------------------------------------------| | `value` | the target year's seasonal value (`null` if the year is uncovered) | | `percentile` | empirical percentile (`rank / sample_years`, %) | | `rank` | the value's rank within the sample | | `return_period_years` | the empirical return period in years | | `sample_years` | how many historical years it was ranked against — **always present** | | `method` | always `empirical` | ## Honesty: empirical, with the sample always on the table This is the scope that makes the figure defensible — and the reason it is the most defensible derivative in the climate family: - **The method is empirical, full stop.** The value is placed by its **rank in the observed sample**; `method` is always `empirical`. There is **no GEV or other parametric fit** and no confidence interval in this version — nothing is extrapolated beyond the record. - **`sample_years` is always returned.** A percentile over 12 years and one over 60 are not the same claim, so the sample size is never hidden — the agent decides whether it is strong enough to act on. - **A short sample is flagged, not faked.** Below a floor of **20 sample years** the figures are still served, but `coverage.complete` is `false` with a reason such as `insufficient history: 14 years`. - **It is a grid value, not a station.** ERA5 is a gridded reanalysis on a mesh of about 0.25 degrees; `grid.distance_km` exposes how far the served cell sits from the requested point. ```json { "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", "freshness": { "kind": "snapshot", "as_of": "2026-06-19T00:00:00Z" } } } ``` If the **target year is not covered** by the record, the ranking fields (`value`, `percentile`, `rank`, `return_period_years`) come back `null` while `sample_years` and `method` remain present — you still learn what the sample is. Per the x402 golden rule, only requests the service genuinely cannot answer leave the 200 range: invalid coordinates (`INVALID_COORDS`), an invalid year or season (`INVALID_PERIOD`), an unknown or inconsistent variable/stat (`INVALID_VARIABLE`), or a point with **no** historical sample at all (`OUT_OF_RANGE`). ## Where it fits in the x402 loop Each paid call follows the same pattern as every Invoket endpoint. The [Quickstart](/docs/quickstart) walks the discover → `402` → pay → replay cycle with runnable snippets, and [For agents](/docs/for-agents) covers the discovery surfaces and the live `/catalog`. Price and accepted rails are not pinned in this article — they are served live by the [catalog](https://api.invoket.com/catalog); see the [endpoint reference](/docs/api/climate-return-period) for the current figure. ## The rarity question, after the balance and the departure Return period is the *rarity* layer — the question you ask once you have the season's value and know it was unusual: - [`GET /climate/aggregate`](/docs/api/climate-aggregate) — the seasonal value itself, the balance you then rank (see [Monthly and seasonal climate summaries for any GPS point](/blog/season-summaries-for-any-gps-point)). - [`GET /climate/anomaly`](/docs/api/climate-anomaly) — how far that value sat from the 1991–2020 normal (see [How far from normal was this month? The ERA5 climate anomaly](/blog/how-far-from-normal-was-this-month)). - [`GET /climate/point`](/docs/api/climate-point) — the raw daily values behind it all. Aggregate tells you *what* the season was, anomaly tells you it was *unusual*, and return-period tells you *how unusual*, empirically. Used for what it is — an empirical rank in the ERA5 record, with the method fixed, the sample size always exposed and short histories flagged — `GET /climate/return-period` gives a risk agent a ranking it can defend, without an account and without building a climatology from GRIB. For the full field reference, season semantics and error codes, see the [`GET /climate/return-period` documentation](/docs/api/climate-return-period). ERA5 values are derived from Copernicus Climate Change Service (C3S) information.