---
title: "Time series of catalog grid and snapshot products at a point."
method: GET
path: "/v1/series"
tags: ["Products"]
---

# Time series of catalog grid and snapshot products at a point.

`GET /v1/series`

## Request

- Base URL: `https://api.smarterweather.com`
- URL: `https://api.smarterweather.com/v1/series`
- Auth: HTTP bearer

Returns the time series of one or more data catalog products at one
location: the public, catalog-governed door to stored model and
analysis data. Any `grid` or `snapshot` product that
`GET /v1/catalog` lists qualifies, for example `hrrr_surface`,
`nbm_conus`, `gfs_surface`, `rtma_conus`, `mrms_qpe_01h_pass2_conus`
or `astro`. Variables are named by their catalog `standard_name`.

**Experimental** (`x-stability: experimental`): excluded from the
90-day breaking-change commitment while the grammar settles. `units`
is required with no default while experimental.

### Selection

`product` with `var` (repeatable; omit `var` for every variable of
the product), or `select=product:var` (repeatable or comma-joined,
at most 25) to read several products. An unknown product or
variable is a `404`. A catalog product that is public but not
served by this route (an MCP-only product, or a REST product that
is not a grid or snapshot) is a `404` when it is the only
selection, and a `not_public` element beside a servable one.

### Time

At most one time word, and it is optional:

- `hours` (1-384) takes its direction from the product's catalog
  `product_type`: forward from now for a `forecast`, backward from
  now for an `analysis`. With `run`, it counts from the run's
  initialization time. Rolling windows are rounded out so the
  frame covering now is included: a forecast starts at now floored
  to the product's catalog `step` (the UTC day for a daily product
  such as `nbm_daily_temps`, the last 00/06/12/18Z for a 6-hourly
  model such as `aifs_single_surface`, the last 5-minute mark for
  the `PT5M` nowcast, the hour for an hourly model), an analysis
  at now less `hours` rounded down to 5 minutes, and both end
  rounded up to 5 minutes.
- `start` and `end` (RFC 3339, at most 16 days apart), applied
  exactly as given: a daily row stamped at 00:00Z is outside a
  window that starts later that day.
- Neither: an analysis returns its latest frame and a forecast the
  next 24 h, rounded out as above (24 h from the run's
  initialization time with `run`).

A product with no `product_type`, or a `climatology` such as
`climate_normals_conus`, requires `start` and `end`. `run` (RFC
3339) pins a forecast model cycle and composes with either time
word; it is a `400` on a product that is not a `forecast`.
`step`, `stats`, `view`, `fields` and `format` are reserved and
return `400` for now; `at` is not a word of this route (use
`GET /v1/point` for the latest value).

`astro` is an `analysis` whose rows run through about 14 days
ahead, so with no time word it returns the newest row and `hours`
looks back from now; pass `start` and `end` for specific days.

`mrms_radar_nowcast_conus` is a `forecast` served from one nowcast
run, the newest whose frames (5 to 60 minutes after the run) reach
the window, and `run` names it. Frames of that run outside the
window are dropped. A past window is not stitched across runs, so
a window no run reaches is `unavailable`. Observed radar history
comes from `mrms_reflectivity_conus` and the MRMS QPE products
such as `mrms_qpe_01h_pass2_conus`.

### Units and raw values

`units` is `si`, `metric` or `imperial`. `si` serves stored values
(K, m/s, mm, Pa, percent, and the stored SI unit for anything
else). The chosen system is echoed at the top level and each
element's `units` names every variable's unit; a percentage is
always labelled `percent`. Values without a
display unit are served raw, as stored, with no decode: `astro`
sun and moon times are seconds after the row's UTC midnight (they
can be negative or above 86400), moon values are x1000, and
`day_length` -1 means no event. `GET /v1/catalog/{product_id}`
describes each variable's encoding.

### Coverage

Coverage is the 50 US states and DC, decided on the point's state
(Canada, Mexico and open water inside a model grid are outside
it). On `GET /v1/series` an in-range point outside it returns
`404` `outside-coverage` with `next_steps`, as `GET /v1/point`
does; `global` products (GFS, AIFS, `astro`, `uv_index`, ...) are
not served there either. In `POST /v1/series/batch` such a
location is an `out_of_domain` element, as is a location the check
could not reach within its time limit (retry it; the lookup carries
on and caches). A point that no selected
product (nor a related one) serves is an `out_of_domain` element
without the state check, since nothing would be served there.

### Envelope

`series[]` holds one element per (product, location), in request
order and column-oriented: `times[]` (RFC 3339 UTC), `values` per
variable aligned with `times[]`, and `run` when every frame shares
one model run, else `runs[]` aligned with `times[]`. Each element
has a `status`: `ok`; `unavailable` for an empty window;
`out_of_domain` outside the product's catalog `coverage[]` or
outside the 50 US states and DC (`global` products included), with
`related` naming a sibling product that covers the point when one
exists (there is no automatic switch); `not_public`. `partial` and
`entitlement_required` are reserved. `data_age_seconds` and
`freshness_status` match `GET /v1/catalog?include=freshness`; with
`run`, both describe the pinned run (its age, and `fresh` or
`stale` against the product's freshness budget; a product tracked
in several stores uses the strictest). `meta.timezone` is the
location's IANA zone (null when it cannot be resolved in time, or
when no selected product covers the location).
`meta.sources[]` carries each source's attribution and license;
show the attribution where the license requires it (ECMWF AIFS is
CC BY 4.0).

### Density

`limit` caps frames per element (default 168, max 744). When it
cuts any element, `truncated` is `true` and `next_cursor` resumes
every element where it stopped: send it back as `cursor` with the
same parameters (a cursor sent with other parameters is a `400`).
The cursor keeps the window page 1 resolved, so a rolling window
does not move while paging. Each element pages independently; one
that has finished returns no frames on later pages and keeps the
status it had.

### Caching

`Cache-Control: max-age=N, stale-while-revalidate=N` with N the
smallest selected product cadence clamped to 60-3600 s, `public`
for first-party keys and `private` for partner keys. An element's
`meta.timezone` may be null when the timezone lookup is
unavailable or slow; the response is then sent with
`Cache-Control: no-store`, as it is when the timezone comes from a
temporarily degraded or fallback lookup.

### Auth and cost

Standard sw-api Bearer auth and rate limits apply. Cost class
`data`, weight 1 per request: products, variables, hours and
`limit` never change it.

## Query parameters

- `product` string
- `var` string[]
- `select` string[]
- `lat` number, double, required
- `lon` number, double, required
- `units` 'si' | 'metric' | 'imperial', required
- `hours` integer
- `start` string, date-time
- `end` string, date-time
- `run` string, date-time
- `limit` integer
- `cursor` string

## Response `200`

One series element per selected product.

- SeriesResponse: The `GET /v1/series` and `POST /v1/series/batch` envelope.
  - `units` 'si' | 'metric' | 'imperial', required: The requested units system.
  - `truncated` boolean, required: True when `limit` cut the series; `next_cursor` resumes them.
  - `next_cursor` string, nullable, required: Opaque cursor for the next page, null when not truncated.
  - `meta` object, required
    - `step` unknown, required
    - `sources` SeriesSource[], required: The catalog sources behind the response, with their attribution terms.
      - `id` string, required
      - `name` string, nullable, required
      - `attribution` string, required
      - `license` string, required
      - `terms_url` string, nullable, required
  - `series` SeriesElement[], required
    - `product` string, required
    - `lat` number, double, required
    - `lon` number, double, required
    - `label` string: The batch location's `label`, when it had one.
    - `status` 'ok' | 'partial' | 'unavailable' | 'out_of_domain' | 'not_public' | 'entitlement_required', required: `ok`; `unavailable` (no frames in the window); `out_of_domain` (outside the product's coverage); `not_public` (public on another door, not served on this route). `partial` and `entitlement_required` are reserved.
    - `related` string: With `out_of_domain`, a related product that covers the point.
    - `times` string[], required: Frame valid times, RFC 3339 UTC, ascending.
    - `values` object, required: Values per variable `standard_name`, aligned with `times`; null where missing.
    - `units` object, required: Unit per variable `standard_name`; null for a raw value with no stored unit.
    - `run` string, date-time: The model run behind every frame, when they share one.
    - `runs` string[]: Per-frame model runs aligned with `times`, when frames mix runs.
    - `data_age_seconds` integer, nullable, required: Age of the product's latest data (or of the pinned `run`), null when unknown.
    - `freshness_status` 'fresh' | 'stale' | 'missing' | 'null', nullable, required: The product's freshness, as on the catalog; with `run`, the pinned run's age against the product's freshness budget (the strictest of its stores). Null when unknown.
    - `meta` object, required
      - `timezone` string, nullable, required: IANA timezone of the location, null when it cannot be resolved.

## Other responses

- `400`: Bad request.
- `401`: Missing or invalid API key. Carries a `WWW-Authenticate` challenge and a `next_steps` extension. `recommended` is `device_flow`: the object is a complete no-install mint recipe.
- `403`: API key lacks the required scope. Carries a `WWW-Authenticate` challenge with `error="insufficient_scope"` and a `next_steps` extension.
- `404`: Resource not found: a location that cannot be resolved (unknown ZIP, unrecognised free-text location), no upstream data for the location, or a missing resource. On a route that checks coverage (its description says so), a location outside the 50 US states and DC returns type `https://smarterweather.com/errors/outside-coverage`; that and the location-not-found problem carry `next_steps` (`recommended = coverage`).
- `429`: Rate limit exceeded. Carries a `next_steps` extension (`recommended: upgrade`, plus `pricing` and `usage`) telling the caller how to lift the limit.
- `500`: Internal server error.
- `502`: Bad gateway. The service received an invalid response from an upstream data source. Transient; clients should retry with backoff.
- `503`: Service unavailable. An upstream dependency is unavailable or shedding load (type `service-unavailable`). Transient; retry with backoff and honor any `Retry-After` header.
- `504`: Gateway timeout. An upstream data source did not respond within the service's budget.

---

[API](https://skmtc.dev/smarterweather/apis/smarter-weather-api.md) · [All operations](https://skmtc.dev/smarterweather/apis/smarter-weather-api/llms.txt) · [OpenAPI document](https://skmtc.dev/smarterweather/apis/smarter-weather-api/revisions/520d55a696e4?raw)
