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

ParameterTypeRequiredDescription
latnumbercoordinate modeLatitude in decimal degrees, from -90 to 90
lonnumbercoordinate modeLongitude in decimal degrees, from -180 to 180
citystringplace-name modePlace name instead of coordinates, for example city=Zurich
locationstringnoStrict alias of city; if both are present they must be identical
countrystringnoISO-3166 alpha-2 code narrowing an ambiguous place name, for example country=US
daysnumbernoLength of the series in calendar days, integer 1 to 35; defaults to 16
variablesstringnoComma-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 single gfs-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, gefs or gfs+gefs. It says which registers the series mixes; the register of a given day is days[].dataset.
  • freshness.kind: live when every served cycle is fresh, or cached with age_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

FieldTypeDescription
latnumberLatitude in effect for the answer: echoed, or resolved from the place name
lonnumberLongitude in effect for the answer: echoed, or resolved from the place name
locationobjectResolved place echo; present only when the request used city/location
fromstringFirst day of the series, YYYY-MM-DD - the current UTC day
tostringLast requested day, YYYY-MM-DD; days past the covered window are absent from days
daysobject[]One summary per covered UTC calendar day, in ascending date order
leadobjectReference analysis cycle of the preferred window that served the range
gridobjectEffective grid cell used for the answer; omitted if no cell served
coverageobjectWhole-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).

FieldTypeDescription
datestringThe UTC calendar day, YYYY-MM-DD
sourcestringModel that served this day - one dataset per day, never mixed intra-day
datasetstringMachine code of that model, gfs or gefs - the field to branch on, since source is a human label
temperature_min_cnumber | nullMinimum 2 m temperature over the day, Celsius; omitted if temperature not requested
temperature_max_cnumber | nullMaximum 2 m temperature over the day, Celsius; omitted if temperature not requested
precipitation_sum_mmnumber | nullTotal precipitation over the day, millimetres; omitted if precipitation not requested
wind_speed_max_msnumber | nullMaximum 10 m wind speed over the day, m/s; omitted if wind not requested
steps_usednumberDistinct forecast steps that actually contributed to this day
coverageobjectPer-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

FieldTypeDescription
cyclestringAnalysis 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):

Segmentdatasetprecipitation_sum_mmtemperature_max_c
J+0 → J+15 (16 days)gfs9 days at 0.0, peak 13.1221.2 → 40.5, a 19.3 °C spread
J+16 → J+34 (19 days)gefsno day below 0.40, none above 1.8225.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.

StatuscodeCase
400INVALID_COORDSlat/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)
400UNKNOWN_LOCATIONcity/location names a place the gazetteer cannot resolve (or place-name resolution is unavailable on the deployment)
400INVALID_PERIODdays is non-numeric or outside 1..=35
400INVALID_VARIABLEvariables contains a family outside the supported set
400OUT_OF_RANGENo requested day is covered by any forecast window
503NO_FORECAST_AVAILABLENo forecast cycle is currently available in the store
500INTERNALInternal 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