GET /weather/forecast/daily
Returns a day-by-day forecast series for one point - the point given as GPS
coordinates or as a city name (city=Zurich) - as one row per UTC calendar
day from today (J+0) to the end of the covered window: min and max 2 m
temperature, total precipitation and max 10 m wind per day. It is the
whole-series companion to GET /weather/forecast,
which answers a single instant: here the agent buys the entire local window in
one call instead of polling lead time by lead time.
Each day is summarised from the same NOAA GRIB data decoded into a local grid
store, so a covered series resolves in milliseconds without an account or API
key. The series is settled as one x402 payment for the whole call. See the
live /catalog for the authoritative endpoint listing and
price.
Use it when an agent needs a operational outlook rather than a single reading: energy load and dispatch planning across the week, logistics and site scheduling, agricultural work windows or short-term risk screening.
x402 golden rule: the agent pays for the answer to its question. A
well-formed request that returns at least one usable day is a successful answer
-> 200, even when the first or last day is partial or a grid cell has a data
gap: those are reported through per-day and top-level coverage, never invented.
Requests the service cannot answer - invalid coordinates, an unresolvable place
name, an out-of-range days, or a range with no covered day at all - leave the
200 range.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
lat | number | coordinate mode | Latitude in decimal degrees, from -90 to 90 |
lon | number | coordinate mode | Longitude in decimal degrees, from -180 to 180 |
city | string | place-name mode | Place name instead of coordinates, for example city=Zurich |
location | string | no | Strict alias of city; if both are present they must be identical |
country | string | no | ISO-3166 alpha-2 code narrowing an ambiguous place name, for example country=US |
days | number | no | Length of the series in calendar days, integer 1 to 35; defaults to 16 |
variables | string | no | Comma-separated subset of temperature, precipitation, wind; all by default |
Locate the point either with lat/lon coordinates or with a place
name via city (alias location) - never both. Place names resolve against an
embedded GeoNames cities500 gazetteer (exact match, case- and
accent-insensitive, no fuzzy matching; ambiguity resolved by population, narrowed
by country=), and the resolved place is echoed back in data.location - see
GET /weather/forecast for the full
place-name contract.
The series always starts at the current UTC day and runs forward for days
days: it is not a historical range and takes no from/to. days defaults to
16 - the deterministic GFS window - and is capped at 35, the end of the GEFS
ensemble extension. Days beyond the deterministic window come from the ensemble
mean and read as a trend, not a threshold - see
the ensemble tail. Asking for more days than are actually covered is never an
error: uncovered days are simply omitted from the series and noted in
coverage, so you never need to shrink days to avoid a 400.
GET /weather/forecast/daily?lat=48.8566&lon=2.3522&days=7
Locate the point by city name, or restrict the payload to one family:
GET /weather/forecast/daily?city=Zurich&days=7
GET /weather/forecast/daily?lat=48.8566&lon=2.3522&days=10&variables=temperature,precipitation
200 response - UnifiedResponse
{
"data": { ... },
"provenance": {
"source": "gfs-noaa + NOAA GEFS ensemble mean (35-day, 0.5°)",
"dataset": "gfs+gefs",
"fetched_at": "2026-07-04T12:00:00Z",
"freshness": { "kind": "live" }
}
}
provenance.source: the distinct model sources that actually served days in the range, joined with+. A short range fully inside the deterministic window reads as a singlegfs-noaa; a longer range that crosses into the extended window also names the GEFS ensemble mean.provenance.dataset: the same set as machine codes, joined with+-gfs,gefsorgfs+gefs. It says which registers the series mixes; the register of a given day isdays[].dataset.freshness.kind:livewhen every served cycle is fresh, orcachedwithage_secs(the age of the oldest served cycle) when a stale but still covered cycle backed part of the series - never an optimistic mix.- GFS and GEFS data both come from NOAA and are public-domain U.S. government data.
Fields of data
| Field | Type | Description |
|---|---|---|
lat | number | Latitude in effect for the answer: echoed, or resolved from the place name |
lon | number | Longitude in effect for the answer: echoed, or resolved from the place name |
location | object | Resolved place echo; present only when the request used city/location |
from | string | First day of the series, YYYY-MM-DD - the current UTC day |
to | string | Last requested day, YYYY-MM-DD; days past the covered window are absent from days |
days | object[] | One summary per covered UTC calendar day, in ascending date order |
lead | object | Reference analysis cycle of the preferred window that served the range |
grid | object | Effective grid cell used for the answer; omitted if no cell served |
coverage | object | Whole-series completeness marker |
location
Present only in place-name mode - see
GET /weather/forecast for the field-level
contract (name, country, admin1, lat, lon, source). Calls made with
explicit lat/lon never carry this block.
days[] - a daily summary
Each element summarises one UTC calendar day. A family that was not requested
via variables is omitted; a requested family with no data at the served cell is
null (never invented).
| Field | Type | Description |
|---|---|---|
date | string | The UTC calendar day, YYYY-MM-DD |
source | string | Model that served this day - one dataset per day, never mixed intra-day |
dataset | string | Machine code of that model, gfs or gefs - the field to branch on, since source is a human label |
temperature_min_c | number | null | Minimum 2 m temperature over the day, Celsius; omitted if temperature not requested |
temperature_max_c | number | null | Maximum 2 m temperature over the day, Celsius; omitted if temperature not requested |
precipitation_sum_mm | number | null | Total precipitation over the day, millimetres; omitted if precipitation not requested |
wind_speed_max_ms | number | null | Maximum 10 m wind speed over the day, m/s; omitted if wind not requested |
steps_used | number | Distinct forecast steps that actually contributed to this day |
coverage | object | Per-day completeness (complete, reason) |
Source is chosen per day (never mixed within a day): the deterministic GFS is
preferred when it covers the whole day, otherwise the GEFS ensemble mean, whose
source names it explicitly as an ensemble mean and whose dataset is gefs.
Rows served that way have smoothed extremes and must be read as a trend - see
the ensemble tail.
lead
| Field | Type | Description |
|---|---|---|
cycle | string | Analysis cycle (RFC 3339 UTC) of the preferred window that served the range; each day also names its own source |
grid and coverage
grid (lat, lon, distance_km) is the representative grid cell used, as in
GET /weather/forecast. Top-level coverage
is complete: true only when every requested day is present and every served
day is itself complete; otherwise reason combines how many requested days fell
outside the covered window and how many served days are partial.
Example - a 3-day series
J+0 starts at the analysis cycle rather than at midnight, so the first day is
partial (complete: false) while later full days are complete.
{
"data": {
"lat": 48.8566,
"lon": 2.3522,
"from": "2026-07-04",
"to": "2026-07-06",
"days": [
{
"date": "2026-07-04",
"source": "gfs-noaa",
"dataset": "gfs",
"temperature_min_c": 17.2,
"temperature_max_c": 24.8,
"precipitation_sum_mm": 0.6,
"wind_speed_max_ms": 6.4,
"steps_used": 5,
"coverage": {
"complete": false,
"reason": "day starts before the forecast window (covered from 2026-07-04T06:00:00+00:00)"
}
},
{
"date": "2026-07-05",
"source": "gfs-noaa",
"dataset": "gfs",
"temperature_min_c": 15.9,
"temperature_max_c": 26.1,
"precipitation_sum_mm": 0.0,
"wind_speed_max_ms": 5.1,
"steps_used": 8,
"coverage": { "complete": true }
},
{
"date": "2026-07-06",
"source": "gfs-noaa",
"dataset": "gfs",
"temperature_min_c": 16.4,
"temperature_max_c": 27.3,
"precipitation_sum_mm": 2.4,
"wind_speed_max_ms": 7.0,
"steps_used": 8,
"coverage": { "complete": true }
}
],
"lead": { "cycle": "2026-07-04T00:00:00Z" },
"grid": { "lat": 48.75, "lon": 2.25, "distance_km": 13.42 },
"coverage": {
"complete": false,
"reason": "1 served day(s) have partial coverage"
}
},
"provenance": {
"source": "gfs-noaa",
"dataset": "gfs",
"fetched_at": "2026-07-04T06:40:00Z",
"freshness": { "kind": "live" }
}
}
Example - long range crossing into the ensemble window
A range longer than the deterministic window is served day by day: early days
come from GFS, later days from the GEFS ensemble mean (source names it and
dataset is gefs, on a coarser 0.5-degree mesh). Treat ensemble-mean days as a
smoothed central estimate, not a deterministic run: their precipitation_sum_mm
and temperature_max_c barely move, so
they carry a trend rather than a threshold. Days past the far end of the covered
window are simply absent from days, and the top-level coverage says how many.
{
"data": {
"lat": 48.8566,
"lon": 2.3522,
"from": "2026-07-04",
"to": "2026-08-07",
"days": [
{
"date": "2026-07-20",
"source": "gfs-noaa",
"dataset": "gfs",
"temperature_min_c": 17.0,
"temperature_max_c": 28.9,
"precipitation_sum_mm": 0.2,
"wind_speed_max_ms": 5.8,
"steps_used": 8,
"coverage": { "complete": true }
},
{
"date": "2026-07-21",
"source": "NOAA GEFS ensemble mean (35-day, 0.5°)",
"dataset": "gefs",
"temperature_min_c": 16.3,
"temperature_max_c": 26.5,
"precipitation_sum_mm": 1.1,
"wind_speed_max_ms": 4.9,
"steps_used": 4,
"coverage": { "complete": true }
}
],
"lead": { "cycle": "2026-07-04T00:00:00Z" },
"grid": { "lat": 48.75, "lon": 2.25, "distance_km": 13.42 },
"coverage": {
"complete": false,
"reason": "3 of 35 requested day(s) fall outside the covered forecast window"
}
},
"provenance": {
"source": "gfs-noaa + NOAA GEFS ensemble mean (35-day, 0.5°)",
"dataset": "gfs+gefs",
"fetched_at": "2026-07-04T06:40:00Z",
"freshness": { "kind": "live" }
}
}
Method and coverage honesty
The daily rows are derived, not GRIB daily fields - which the response makes explicit rather than overselling:
- Min/max temperature are sub-sampled. They are the extremes of the ingested 3-hourly steps (about eight samples a day), not the model’s own TMAX/TMIN fields. The true daily extreme can be missed by a few tenths of a degree.
- Precipitation is summed over disjoint increments. NOAA publishes accumulated precipitation in cycle-aligned 6-hour buckets; the service reconstructs non-overlapping increments before summing so a step is never double-counted. Each increment is attributed to the UTC day its period ends in.
- Max wind is the largest per-step 10 m wind speed of the day.
- Grid, not station. Values are the nearest grid cell - about 0.25 degrees in the GFS register, 0.5 degrees in the GEFS ensemble register - not a weather-station reading.
The ensemble tail smooths extremes
Days past the deterministic window (typically from about J+16, dataset: "gefs")
are the average of ~31 ensemble members on every variable. Averaging removes
exactly what a threshold rule looks for: an extreme produced by only part of the
members survives the mean as a fraction of itself. That is what an ensemble mean
is, not a defect - but it changes what the row can be used for.
Measured in production on 2026-07-25 for Paris, on this very call
(?lat=48.85&lon=2.35&days=35):
| Segment | dataset | precipitation_sum_mm | temperature_max_c |
|---|---|---|---|
| J+0 → J+15 (16 days) | gfs | 9 days at 0.0, peak 13.12 | 21.2 → 40.5, a 19.3 °C spread |
| J+16 → J+34 (19 days) | gefs | no day below 0.40, none above 1.82 | 25.3 → 27.9, a 2.6 °C spread |
So a threshold trigger applied to those rows - “more than 30 mm in a day”, “a
dry day”, a wind cut-off - will essentially never fire, whatever weather
actually occurs. Use the gefs rows for trend (is the period leaning warm,
wet, windy?) and keep parametric or threshold decisions on the gfs rows, which
are the ones that resolve extremes. Asking for days=35 remains legitimate: the
tail answers a different question, it is not a worse answer to the same one.
Branch on days[].dataset rather than parsing the source label.
Partial days are marked, never hidden. The first day starts at the analysis cycle
(not at midnight) and the last covered day can be cut at the window end; a day
with a missing family value or missing steps is complete: false with a reason,
and steps_used shows how many steps really backed it. A range where no day
is covered returns 400 OUT_OF_RANGE citing the window actually covered, and
if no forecast cycle has been ingested yet the service returns 503
NO_FORECAST_AVAILABLE - a transient no-charge state, not a paid partial answer.
Settlement
The gateway settles the whole series as one x402 payment for the call, at a
flat price. The authoritative price is published in the live
/catalog; this page never hardcodes it.
Errors
Only requests the service cannot answer leave the 200 range.
| Status | code | Case |
|---|---|---|
| 400 | INVALID_COORDS | lat/lon non-numeric or outside valid bounds, or a point-mode conflict (coordinates and a place name together, city differing from location, or no point at all) |
| 400 | UNKNOWN_LOCATION | city/location names a place the gazetteer cannot resolve (or place-name resolution is unavailable on the deployment) |
| 400 | INVALID_PERIOD | days is non-numeric or outside 1..=35 |
| 400 | INVALID_VARIABLE | variables contains a family outside the supported set |
| 400 | OUT_OF_RANGE | No requested day is covered by any forecast window |
| 503 | NO_FORECAST_AVAILABLE | No forecast cycle is currently available in the store |
| 500 | INTERNAL | Internal error (detail logged, not exposed) |
Error messages guide the retry, and a 4xx is never charged:
{ "error": "days 40 out of range 1..=35 (default 16); days beyond the covered forecast window are simply omitted from the response", "code": "INVALID_PERIOD" }
{ "error": "unknown place 'Xyzzy'; check the spelling, narrow with 'country' (ISO-3166 alpha-2), or provide lat/lon instead", "code": "UNKNOWN_LOCATION" }
{ "error": "requested days are outside the available forecast window 2026-07-04T00:00:00+00:00..2026-08-08T00:00:00+00:00", "code": "OUT_OF_RANGE" }
See also
GET /weather/forecast- single instant, lead time or calendar date at one point.POST /weather/forecast/batch- many point/target forecasts in one settlement.- For agents - discovery surfaces, the live
/catalogand how settlement works.