POST /weather/forecast/batch

Resolves many point-and-target forecasts in a single call. Each item uses the same local GRIB-decoded engine as GET /weather/forecast: 2 m temperature, precipitation and 10 m wind for one point and one forecast target, with grid, lead and coverage metadata that let an agent judge each answer. It is the many-points-at-once companion to the single endpoint - use it when an agent has a portfolio of locations or lead times to score in one shot: multi-site energy dispatch, logistics across a route, or a fan-out of horizons for one place.

The batch is settled as one x402 payment for the whole call and priced per query by the gateway. See the live /catalog for the authoritative endpoint listing, pricing model and batch cap.

x402 golden rule: the agent pays for the answer to its question. A well-formed batch is a successful answer -> 200, even when individual items are outside coverage and come back with null values and coverage.complete: false. Requests the service cannot answer - malformed body, empty batch, batch over the published cap, a malformed item, or a batch where no item is covered at all - leave the 200 range.

Request

POST with a JSON body. Set Content-Type: application/json.

POST /weather/forecast/batch
Content-Type: application/json
{
  "queries": [
    { "lat": 48.8566, "lon": 2.3522, "horizon_h": 24 },
    { "lat": 40.7128, "lon": -74.006, "date": "2026-07-06" },
    { "lat": 35.68, "lon": 139.69, "valid_time": "2026-07-05T12:00:00Z", "variables": ["temperature", "wind"] }
  ]
}
FieldTypeRequiredDescription
queriesobject[]yesForecast items to resolve, from 1 item up to the batch cap advertised in the live catalog
latnumberyesLatitude in decimal degrees, from -90 to 90
lonnumberyesLongitude in decimal degrees, from -180 to 180
horizon_hnumberone of target fieldsForecast lead in hours from the current GFS cycle, integer 0 to 840
valid_timestringone of target fieldsForecast target instant in RFC 3339 UTC form
datestringone of target fieldsForecast day as YYYY-MM-DD, served at 12:00 UTC of that day
variablesstring[]noSubset of temperature, precipitation, wind; all by default

Each item locates the point with explicit lat/lon (place-name city resolution is a single-endpoint convenience and is not offered in batch) and carries exactly one target field - horizon_h, valid_time or date, with the same forms, bounds and semantics as GET /weather/forecast. Items are independent: they may mix target forms and variable subsets freely.

200 response - UnifiedResponse

{
  "data": {
    "count": 3,
    "results": [ { ... }, { ... }, { ... } ]
  },
  "provenance": {
    "source": "gfs-noaa",
    "fetched_at": "2026-07-04T12:00:00Z",
    "freshness": { "kind": "live" }
  }
}
  • count: number of items resolved, equal to results.length and to the number of submitted items.
  • results: one verdict per query, in the same order as the input. Each element has the same shape as the data object returned by GET /weather/forecast, including grid, lead and coverage.
  • A single provenance block covers the whole batch, set from the first covered item (its served cycle and source).

Example - mixed coverage batch

A covered item and an out-of-window item can coexist in the same paid answer. The uncovered item stays a verdict in results with null values, coverage.complete: false and no lead (nothing was served); it does not make the whole batch fail.

{
  "data": {
    "count": 2,
    "results": [
      {
        "lat": 48.8566,
        "lon": 2.3522,
        "horizon_h": 24,
        "temperature": { "celsius": 18.6 },
        "precipitation": { "total_mm": 0.4 },
        "wind": { "speed_ms": 6.1, "direction_deg": 242.3 },
        "grid": { "lat": 48.75, "lon": 2.25, "distance_km": 13.42 },
        "lead": {
          "cycle": "2026-07-04T00:00:00Z",
          "valid_time": "2026-07-05T00:00:00Z",
          "horizon_h": 24,
          "step_time": "2026-07-05T00:00:00Z"
        },
        "coverage": { "complete": true }
      },
      {
        "lat": 48.8566,
        "lon": 2.3522,
        "horizon_h": 840,
        "temperature": { "celsius": null },
        "precipitation": { "total_mm": null },
        "wind": { "speed_ms": null, "direction_deg": null },
        "coverage": {
          "complete": false,
          "reason": "requested lead time is outside the available forecast window 2026-07-04T00:00:00+00:00..2026-08-07T06:00:00+00:00"
        }
      }
    ]
  },
  "provenance": {
    "source": "gfs-noaa",
    "fetched_at": "2026-07-04T12:00:00Z",
    "freshness": { "kind": "live" }
  }
}

Coverage honesty

Both registers are gridded forecast models, not weather station readings, and the two-register composite window (deterministic GFS then the GEFS ensemble mean) applies per item exactly as on the single endpoint - see Two forecast registers. Each result is the nearest or interpolated grid value served for that item, with the same coverage semantics.

A per-item failure is local: an item outside every covered window - or on a grid cell with a data gap - returns null values with coverage.complete: false inside a 200 batch. The whole call returns 400 OUT_OF_RANGE only when no item is covered at all (zero value delivered, so the agent does not pay); if no forecast cycle has been ingested, it returns 503 NO_FORECAST_AVAILABLE, a transient no-charge state.

Batch cap and settlement

The gateway prices this endpoint per query and settles the batch as one x402 payment for the call: one settlement, but the amount scales with the number of items. The current cap and per-query price are published in the live /catalog, including the unit field used for the batch size; the request body uses that same field, queries. This page never hardcodes the price.

An oversized batch is rejected before any lookup work is performed, so it is not a paid answer. Split larger fan-outs into multiple calls using the catalog’s published cap.

Errors

Only requests the service cannot answer leave the 200 range.

StatuscodeCase
400INVALID_BODYBody missing, not JSON, not an object, or queries missing
400EMPTY_BATCHqueries is an empty array ([])
400BATCH_TOO_LARGEMore items than the published batch cap
400INVALID_QUERYAn item is malformed, has invalid coordinates, has no single valid target, or names an unknown variable
400OUT_OF_RANGENo item in the batch is covered by any forecast window
503NO_FORECAST_AVAILABLENo forecast cycle is currently available in the store
500INTERNALInternal error (detail logged, not exposed)
{ "error": "missing 'queries' array in request body", "code": "INVALID_BODY" }
{ "error": "'queries' must contain at least one item", "code": "EMPTY_BATCH" }
{ "error": "query at index 1: provide exactly one of 'valid_time', 'horizon_h' or 'date'.", "code": "INVALID_QUERY" }
{ "error": "requested lead time is outside the available forecast window 2026-07-04T00:00:00+00:00..2026-08-07T06:00:00+00:00", "code": "OUT_OF_RANGE" }

See also