GEFS ensemble exceedance: how many members cross your threshold?

Averaging 31 GEFS members destroys a threshold signal weeks ahead. One paid call counts them member by member and returns a counted fraction per day, with the member count behind it.

By Matthias Begot · · Updated

An agent that has to commit weeks ahead — a parametric payout trigger, a shipping window, a harvest slot, a concrete pour — asks a threshold question: will this day see at least 30 mm of rain? Past roughly two weeks, the usual answer is an ensemble mean, and averaging is exactly the operation that destroys a threshold signal. One paid call — GET /weather/forecast/exceedance — counts the NOAA GEFS members one by one, before any averaging, and returns one row per UTC calendar day: members crossing, members counted, and the fraction of the two. The whole series settles as one x402 payment.

The number it returns is a counted fraction, not a calibrated probability. That distinction is the substance of this article, and the endpoint states it in its own response.

The problem: an average cannot answer a threshold question

Past the deterministic window (about 384 h, or J+16), GET /weather/forecast and GET /weather/forecast/daily serve the average of ~31 ensemble members. That is what an ensemble mean is, not a defect: ECMWF’s Forecast User Guide states plainly that the mean “smooths the flow more in areas of large uncertainty (large spread)” and that with large spread it “can be a rather weak pattern and may not represent any of the possible states” (ECMWF FUG §8.1.2). An extreme produced by a subset of members survives the average as a fraction of itself.

Measured in production on 2026-07-25 for Paris, on /weather/forecast/daily?lat=48.85&lon=2.35&days=35:

SegmentRegisterDaily precipitationDaily max temperature
J+0 → J+15 (16 days)deterministic GFS9 days at 0.0 mm, peak 13.12 mm21.2 → 40.5 °C, a 19.3 °C spread
J+16 → J+34 (19 days)GEFS ensemble meanno day below 0.40 mm, none above 1.82 mm25.3 → 27.9 °C, a 2.6 °C spread

Read the second row as an agent would: a rule like “more than 30 mm in a day” placed on that tail will essentially never fire, whatever weather actually occurs. Nineteen days of a 35-day series collapse into a band 1.4 mm wide. An agent polling the mean and comparing it to a contractual threshold is not getting a cautious answer — it is getting a structurally dead one.

What counting the members instead looks like

The endpoint reads the member grids rather than their mean, reduces each member to one value per day, and compares that value to your threshold. Three parameters carry the question:

ParameterRole
variableThe quantity tested: precipitation (mm), wind (m/s) or temperature (°C)
thresholdThe threshold value, in that variable’s unit — echoed back as threshold_unit
comparisonat_least (default) or at_most; both bounds inclusive

variable and threshold are required together: a threshold without a quantity has no unit, and the unit applied is never implicit. The point is located either by coordinates or by place name (city=Manila, narrowed by country=), the same gazetteer contract as the other forecast routes.

GET /weather/forecast/exceedance?city=Manila&variable=precipitation&threshold=30&days=3
{
  "date": "2026-08-25",
  "statistic": "daily_sum",
  "members_total": 31,
  "members_exceeding": 9,
  "probability": 0.2903,
  "steps_used": 4,
  "coverage": { "complete": true }
}

Captured in production on 2026-08-09. Read that row as “9 of the 31 members put at least 30 mm of rain on 2026-08-25”. Not “a 29 % chance of rain”.

The honesty line: a counted fraction is not a probability

This is the part an agent must not paper over. 0.2903 is members_exceeding / members_total and nothing else. No model output statistics, no bias correction against observations, no reliability calibration has been applied. Turning a raw ensemble frequency into a calibrated probability is a whole discipline — the statistical literature on tail calibration exists precisely because raw ensemble frequencies are poorly behaved at high thresholds (Allen et al., Tail calibration of probabilistic forecasts). This endpoint does not pretend to have done that work.

Two design consequences follow, and both are worth copying in any system that serves derived numbers:

  • members_total is served on every single day, never assumed equal to the nominal 31. The fraction can be divided back out and audited. A 0.5 counted on 4 members and a 0.5 counted on 31 are not the same statement, and the response hands you both numbers so you can tell them apart.
  • The caveat travels with the number. Every response carries a method object with kind: "counted_member_fraction", calibrated: false and a note saying so in words. An agent that never reads a documentation page still receives the caveat attached to the value it is about to act on.

probability is null, not 0, when no member carried a value for that day — an empty sample has no fraction, and none is invented.

The reduction is named, and the direction flips it

Each member is reduced to one value per day before the threshold test, and the response names which reduction was applied in statistic:

variablecomparisonstatisticReduction
precipitationeitherdaily_sumSum over the day, rebuilt per member from disjoint accumulation buckets
windeitherdaily_maxLargest 10 m scalar speed sqrt(u² + v²), per member — never a vector mean of components
temperatureat_leastdaily_maxWarmest value of the day
temperatureat_mostdaily_minColdest value of the day

That last row is the one that saves a real trigger. A frost threshold (variable=temperature&threshold=0&comparison=at_most) tested against the daily maximum would never fire; the direction of the comparison flips the reduction to daily_min. And a 0.0 on such a query is a counted zero on 31 members — an honest answer, not a missing one.

Degraded days are served and marked, never dropped

Ensemble tails are ragged at the edges. Four failure modes can appear in a day’s coverage.reason, and none of them removes the day from the series:

  • Clipped by the window start or end — the first day of the tail usually begins mid-day; the day is served with the instant it is covered from.
  • Below the member floorcounted on N of 31 ensemble members. Under the 21-member floor the day is still served with its real members_total, and nothing is extrapolated back up to 31.
  • Members with missing steps — such a member is counted, not dropped. Dropping it would silently shrink the denominator and inflate the fraction of the members that remain.

Days that fall outside the covered window are simply omitted, never an error. Only a window lying entirely outside the ensemble tail is a 400 OUT_OF_RANGE — and that error names the deterministic routes to use instead, so an agent that aimed at the wrong horizon finds its way back without a human.

Per the x402 golden rule, a well-formed request returning at least one counted day is a successful answer — 200 — even when an edge day is clipped: that is reported through coverage, never hidden. And when a deployment holds no member grids at all, the response is a 200 with an empty series flagged as carrying no usable result, which the gateway reads as a signal not to settle: the agent asked nothing wrong and is not charged for an empty answer.

Scope, stated plainly

  • It is a grid cell, not a weather station. The member grids are a ~0.5° mesh; grid.distance_km exposes how far the served cell sits from your point.
  • It is the tail, not the near term. The window is capped at 20 days, the physical span of the tail the members cover. Inside the deterministic window, this is the wrong route and it says so.
  • The member layer runs one cycle behind. GEFS runs 4 cycles a day out to 16 days, except 35 days at 00 UTC — that extension publishes the following day, so a healthy response here is normally cached, not live (NOAA EMC, GEFS v12).
  • Omit days and the default window is budget-capped, flagged with window_capped: true; ask days= explicitly and it is served in full.

Where it fits in the x402 loop

The call follows the same pattern as every Invoket endpoint: discover, take the 402, pay, replay. The Quickstart walks that cycle with runnable snippets and For agents covers the discovery surfaces. The whole day series — however many days it counts — settles as one payment for the call. The price is served live by the catalog and is not pinned in this article.

Among the forecast routes, pick by the shape of the question:

A trigger agent usually needs two of these: the historical index that defines the contract, and the counted fraction that says how close the ensemble is to crossing it. Used for what it is — a raw counted member fraction, with the denominator exposed and the caveat attached — the exceedance route gives an agent something it can act on weeks ahead without inventing a probability nobody computed. For the full parameter list, coverage rules and error codes, see the GET /weather/forecast/exceedance documentation.

GEFS data comes from NOAA and is public-domain U.S. government data.