# Super Tracker API

> Free, keyless, read-only JSON over every New Zealand supermarket price, daily.

Base URL: `https://api.supertracker.nz`. This file is generated from [https://api.supertracker.nz/openapi.json](https://api.supertracker.nz/openapi.json).

Super Tracker records the online shelf price of every product at every store of the
big New Zealand supermarket chains (Pak'nSave, New World, Four Square, Woolworths,
FreshChoice, SuperValue), every day, and keeps the history as price *spans*: one row
per price, from the day it appeared to the day it changed. On top of that sit
cross-chain product matching (the same item at each chain), store-level price
comparisons, and a daily food-price index weighted with the Stats NZ Food Price Index
basket. This API is how the website reads all of it, and it is open to everyone.

## Quick start

```
curl "https://api.supertracker.nz/api/v1/search?q=anchor+blue+milk+2l"
curl "https://api.supertracker.nz/api/v1/products/285"
curl "https://api.supertracker.nz/api/v1/products/285/history"
curl "https://api.supertracker.nz/api/v1/stores?town=Tokoroa"
```

Typical flow: **search** (`/api/v1/search`) to find a product id, then the **product**
(`/api/v1/products/{id}`) for today's price at every chain, its **history** for how
that moved, and its **stores** for the price at every shop in the country. A single
chain's own listing has the same trio under `/api/v1/listings/{id}`.

## Conventions

- **Money is integer cents** (NZD). `price` is the shelf price. When `promo_price`
  is set, `promo_kind` says who gets it: `1` special (everyone), `2` member/club
  price (loyalty card), `3` multibuy (`multibuy_qty` for `multibuy_price` in total),
  `4` clearance.
- **Days** are New Zealand calendar dates, `YYYY-MM-DD`. Timestamps are UTC, RFC 3339.
- **Ids are integers and stable.** A *listing* is one chain's product (its own name,
  size and barcode). A *product* is the same item linked across chains by barcode
  and name matching; a listing carries `product_id` when it has been placed.
- **Series are columnar**: parallel arrays (`from[]`, `to[]`, `price[]`, …) rather
  than arrays of objects. Index `i` of every array belongs to the same span.
- **National prices** are the median across the chain's stores that report the
  item; store-level prices are exact.
- `image` is a path on this host to a 720 px AVIF (background removed). Put `_t`
  before `.avif` for the 200 px thumbnail.
- Responses carry `Cache-Control: public, max-age=300`; the heavier derived views
  (map, comparisons, deals) are recomputed at most every ten minutes.
- Errors are JSON: `{ "error": { "status": 404, "message": "…" } }`.
- CORS is open to every origin. No cookies, no keys.

## Fair use and rate limits

There are no API keys. Each IP address may make **300 requests per minute** and
**10,000 per day** across `/api/v1/*` and `/mcp`. Every response carries
`RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` and `RateLimit-Policy`
headers; over the limit you get `429` with `Retry-After`.

**This API is for looking things up, not for copying the dataset.** If you want
everything, it is already packaged for you: every listing, store, price span, index
point and basket item is written nightly as Parquet and CSV under
[`/data/latest/`](https://api.supertracker.nz/data/latest/manifest.json), the exact same tables this API reads,
under CC BY 4.0. Crawling the listing, product or history endpoints one id at a time
will run into the daily limit long before it finishes; the exports never will.

## For agents

- **MCP**: `POST /mcp` is a stateless Streamable HTTP MCP server (protocol revision
  `2026-07-28`, with the legacy `initialize` handshake still accepted). Its tools are
  these endpoints, one to one, same names as the `operationId`s below, same JSON back.
- **llms.txt**: [`/llms.txt`](https://api.supertracker.nz/llms.txt) is the index; [`/llms-full.txt`](https://api.supertracker.nz/llms-full.txt)
  is this whole reference as Markdown; [`/openapi.json`](https://api.supertracker.nz/openapi.json) is this document.
- **Guides**: connecting an agent step by step is at
  [supertracker.nz/agents](https://supertracker.nz/agents); the developer guide is at
  [supertracker.nz/developers](https://supertracker.nz/developers); the website's own
  pages are indexed at [supertracker.nz/llms.txt](https://supertracker.nz/llms.txt).

## Licence and attribution

Data is **CC BY 4.0**: attribute *Super Tracker (supertracker.nz)* and acknowledge the
source retailers. Prices are as advertised online by each supermarket; in-store prices
may differ. The food-price index is a nowcast, not an official statistic: the basket
and weights are Stats NZ's [Food Price Index](https://datainfoplus.stats.govt.nz/item/nz.govt.stats/749d8c27-1bed-45fb-b941-a1905aee632f),
reviewed in the [2024 CPI review](https://www.stats.govt.nz/methods/consumers-price-index-review-2024/)
and [published monthly](https://datainfoplus.stats.govt.nz/Item/nz.govt.stats/9e9f65b8-533f-4e96-8d6e-030d37de1a65).

## Stability

`/api/v1` is stable: fields are added, never removed or renamed, without a new version
prefix. Endpoints that are not in this document (`/api/v1/pile`, `/api/v1/costs`,
`/api/v1/search/status`, `/api/v1/auth/*`, `/api/v1/admin/*`) exist for the website
and may change without notice.


## Endpoints

### Search

Find products and listings by text or barcode.

#### GET /api/v1/search

**Search products across every chain** · MCP tool `search_products`

Hybrid lexical and semantic search over the whole catalogue, grouped one card per
product: each result carries the offers at every chain that sells it (cheapest
first). This is the endpoint behind the site's search box and the right first call
for "what does X cost". A listing that has not been linked to a product yet is its
own group with `product_id: null`. Without `q` it returns recently seen items.
Use the `product_id` with the Products endpoints, or an offer's `id` with the
Listings endpoints. Search is ranked and capped at 100: for *everything* of a
kind — every beer, sorted by price per litre — use the Shelves endpoints.

Parameters:

- `q` (query, string): Free text (up to 100 characters): a product, brand, category, or the digits under a barcode.
- `retailer` (query, string): Retailer slug: rank that chain's catalogue only, so the result is the best
matches *at* that chain rather than the cross-chain best that it happens to
stock. Each product still carries every chain's offer. Unknown slug is `400`.

- `limit` (query, integer, default 40, 1–100): Maximum number of product groups (1–100).

Responses:

- `200` ProductGroup[] — Product groups, best match first.
- `400` Error — A parameter is malformed or out of range.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/search?q=milk"`

#### GET /api/v1/listings

**Search listings (one row per chain product)** · MCP tool `search_listings`

The same search as `search_products` but flat: one row per listing, not grouped
by product. Useful when you want a specific chain's line rather than the item in
the abstract. Without `q` it returns recently seen listings.

Parameters:

- `q` (query, string): Free text (up to 100 characters): a product, brand, category, or the digits under a barcode.
- `retailer` (query, string): Retailer slug: search that chain's listings only. Unknown slug is `400`.
- `limit` (query, integer, default 20, 1–100): Maximum number of listings (1–100).

Responses:

- `200` ListingCard[] — Listings, best match first.
- `400` Error — A parameter is malformed or out of range.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/listings?q=milk"`

#### GET /api/v1/suggestions

**Autocomplete suggestions for a partial query**

Type-ahead for the search box: categories, brands and products that match a
prefix, each with a site `href`. Cheap and fast (hot prefixes answer in a few
milliseconds). Agents should call `search_products` instead.

Parameters:

- `q` (query, string, required): The text typed so far (1–100 characters).
- `limit` (query, integer, default 8, 1–12)

Responses:

- `200` Suggestion[] — Suggestions in display order.
- `400` Error — A parameter is malformed or out of range.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/suggestions?q=milk"`

#### GET /api/v1/gtin/{gtin}

**Look up a barcode** · MCP tool `lookup_gtin`

Exact lookup of a GTIN/EAN/UPC as printed on the pack (up to 14 digits; leading
zeros are fine, non-digits are ignored). Returns the product sold under that
barcode with its offers at every chain, or `404` when the barcode is unknown.
This is what the site's camera scanner calls.

Parameters:

- `gtin` (path, string, required): The barcode digits, e.g. `9415087020031`.

Responses:

- `200` ProductGroup — The product behind the barcode.
- `400` Error — A parameter is malformed or out of range.
- `404` Error — No such thing.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/gtin/9415087020031"`

### Products

One item across every chain, with its history and where it is sold.

#### GET /api/v1/products/{id}

**A product with every chain's offer and its alternatives** · MCP tool `get_product`

The cross-chain view of one item: brand, size, barcodes, the generic it belongs to
("Regular soft drink, 1.5 L") with its attributes, one `offer` per chain listing
(today's national price, promotion, how many stores price it, how the link was
made and how confident it is, a link to the retailer's page), and up to 16
`alternatives`: other products in the same generic, cheapest first, flagged when
they are a house brand. Product ids come from `search_products`, `lookup_gtin`, or
a listing's `product_id`.

Parameters:

- `id` (path, integer, ≥ 1, required): Product id.

Responses:

- `200` ProductDetail — The product.
- `404` Error — No such thing.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/products/285"`

#### GET /api/v1/products/{id}/history

**National price history of a product, per chain** · MCP tool `get_product_history`

One columnar series per retailer slug: every national price span of every listing
linked to the product since it was first seen, oldest first. A span runs from
`from[i]` to `to[i]` inclusive at `price[i]` cents, with `promo[i]`/`kind[i]` when
a promotion applied. The current price is the last span, whose `to` is the day of
the latest observation.

Parameters:

- `id` (path, integer, ≥ 1, required): Product id.

Responses:

- `200` HistoryByRetailer — Retailer slug → history. Empty object when nothing is linked.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/products/285/history"`

#### GET /api/v1/products/{id}/stores

**The price of a product at every store in the country** · MCP tool `get_product_stores`

Every store of every chain currently pricing the product, cheapest first, with
coordinates. A store appears once: if two of its chain's listings are linked to the
product, the cheaper one wins. Around 80 KB for a product every chain sells; pair
with `list_stores` (filtered by town or area) when you only want one place.

Parameters:

- `id` (path, integer, ≥ 1, required): Product id.

Responses:

- `200` ProductStorePrice[] — Store prices, cheapest first. Empty when no store prices it.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/products/285/stores"`

#### GET /api/v1/products/{id}/spread

**Daily price spread across each chain's stores** · MCP tool `get_product_spread`

For each retailer, the daily minimum, median and maximum store price of the
product and how many stores reported, for the last 365 days: how much the same
item's price varies around the country, and whether that is widening.

Parameters:

- `id` (path, integer, ≥ 1, required): Product id.

Responses:

- `200` SpreadByRetailer — Retailer slug → columnar daily spread.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/products/285/spread"`

### Listings

One chain's product, its current price everywhere and its history.

#### GET /api/v1/listings/{id}

**One chain's listing with today's price at every store** · MCP tool `get_listing`

A single retailer's product: identity (name, brand, size, quantity and unit,
barcode, the chain's own category path, external id, link to the retailer's page),
first and last seen, the `current` national span, `stores` (today's price at every
store carrying it, cheapest first, with coordinates), and `stats` (the daily
min/median/max across stores for the last year, columnar). `product_id` links to
the cross-chain product when the listing has been matched.

Parameters:

- `id` (path, integer, ≥ 1, required): Listing id.

Responses:

- `200` ListingDetail — The listing.
- `404` Error — No such thing.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/listings/285"`

#### GET /api/v1/listings/{id}/history

**Price history of a listing, national or at one store** · MCP tool `get_listing_history`

Every price span of the listing, oldest first, as parallel arrays. By default the
national series (the median across the chain's stores); pass `store` for one
store's exact history, including store-only promotions. An unknown listing or a
store that never priced it returns empty arrays, not `404`.

Parameters:

- `id` (path, integer, ≥ 1, required): Listing id.
- `store` (query, integer, default 0, ≥ 0): Store id from `list_stores` or the listing's `stores`. `0` (default) is the national series.

Responses:

- `200` History — Columnar spans.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/listings/285/history"`

### Retailers

The chains, their own category trees, and the basket priced at each.

#### GET /api/v1/retailers

**The supermarket chains** · MCP tool `list_retailers`

Every chain with a catalogue: slug (the key used everywhere else), display name,
listing and store counts, and when its catalogue was last collected. Slugs today:
`paknsave`, `newworld`, `foursquare`, `woolworths`, `freshchoice`, `supervalue`.

Responses:

- `200` Retailer[] — Chains, largest catalogue first.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/retailers"`

### Shelves

Cross-chain aisles — every chain's version of a kind of thing, as a complete, sortable set.

#### GET /api/v1/shelves

**The shelf directory, or the shelf a query names** · MCP tool `list_shelves`

A shelf is a cross-chain aisle: the set of products of one kind of thing — beer,
cheese, nappies — at every chain, built from the generic kind the products
pipeline assigns rather than from any one retailer's menu. Departments are
`depth: 1`; their aisles hang off them by `parent`. Without `q` this is the whole
directory. With `q` it is the one shelf that text names exactly (its label or an
alias, so `beer` and `beers` both land on Beer), as a list of one or none — the
way to turn a category word into a complete set instead of a ranked search.

Parameters:

- `q` (query, string): A category word to resolve to its shelf. Exact match only.

Responses:

- `200` ShelfSummary[] — Shelves, departments first, biggest first.
- `400` Error — A parameter is malformed or out of range.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/shelves?q=milk"`

#### GET /api/v1/shelves/{slug}

**One page of a shelf, sorted and filtered as a whole set** · MCP tool `browse_shelf`

Everything on the shelf, with a true `total`, sorted server-side across the whole
set — so `sort=per_unit` really is the cheapest per litre of all 1,800 beers, not
of a top-sixty slice. `retailer` keeps only products with a current offer at that
chain (and sorts on that chain's price); `deals` keeps only products with an offer
below shelf price; `kind` narrows to one of the kinds listed in `kinds`; `q` is a
text filter within the shelf (and then `sort=relevance` is the default). Facet
counts in `kinds` and `retailers` are for the current filters, each ignoring its
own dimension. Page with `offset` and `limit`. A product's `best` is its cheapest
admitted offer counting specials, club prices and multibuy rates; `per_unit` is
that per litre, kilogram or item in the shelf's `unit`, null when the product is
measured in something else (it then sorts last).

Parameters:

- `slug` (path, string, required): Shelf slug from `list_shelves`.
- `sort` (query, "popular" | "per_unit" | "per_unit_desc" | "price" | "price_desc" | "deal" | "relevance", default "popular", one of "popular"/"per_unit"/"per_unit_desc"/"price"/"price_desc"/"deal"/"relevance"): `popular` (most widely stocked; the default), `per_unit` / `per_unit_desc`
(cheapest / dearest per litre, kilogram or item), `price` / `price_desc`
(cheapest / dearest pack), `deal` (biggest discount), `relevance` (with `q`).

- `retailer` (query, string): Retailer slug; only products with a current offer there. Unknown slug is `400`.
- `deals` (query, string): `1` or `true`: only products with an offer below shelf price.
- `kind` (query, string): A kind slug from the page's `kinds` facet, e.g. `ipa`.
- `q` (query, string): Optional text filter within the shelf.
- `limit` (query, integer, default 48, 1–96): Products per page (1–96).
- `offset` (query, integer, default 0, ≥ 0): Products to skip.

Responses:

- `200` ShelfPage — The shelf, its facets, and one page of products in the requested order.
- `400` Error — A parameter is malformed or out of range.
- `404` Error — No such thing.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/shelves/beer?q=milk"`

### Retailers

The chains, their own category trees, and the basket priced at each.

#### GET /api/v1/retailers/{slug}/browse

**Walk a chain's own category tree** · MCP tool `browse_retailer`

One point in a retailer's own taxonomy: the child categories underneath it with
listing counts, plus a page of the listings at and below it. The position is given
as repeated `c` parameters, one per level (`?c=Pantry&c=Baking`), because category
labels contain `/`, `,` and `&`. With `q`, both the counts and the page are
filtered to matches, so the categories double as facets. A category that holds
nothing is `404`.

Parameters:

- `slug` (path, string, required): Retailer slug from `list_retailers`.
- `c` (query, string[]): Category path, one value per level, outermost first. Omit for the top of the tree.
- `q` (query, string): Optional text filter within this category.
- `limit` (query, integer, default 60, 1–120)
- `offset` (query, integer, default 0, ≥ 0)

Responses:

- `200` RetailerBrowse — The category node and a page of listings.
- `400` Error — A parameter is malformed or out of range.
- `404` Error — No such thing.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/retailers/paknsave/browse?q=milk"`

#### GET /api/v1/retailers/compare

**The Stats NZ basket priced at every chain** · MCP tool `compare_retailers`

Each basket item priced at each chain from national median prices (specials
count, club prices do not), plus weighted price levels: `headline` and per-group
`levels` map a retailer slug to `[level, items]`, where `1.0` is the cross-chain
median and `0.92` means 8 % cheaper than it. Answers "which chain is cheapest, and
by how much". Recomputed every ten minutes.

Responses:

- `200` Compare — Levels and item prices per chain.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/retailers/compare"`

#### GET /api/v1/retailers/levels

**Each chain's basket price level against the others, by day** · MCP tool `get_retailer_levels`

The headline number of `/retailers/compare` for every day the index has run: the
Stats NZ basket priced at each chain from national medians (club prices excluded),
as a weighted geometric mean of each item's price over the cross-chain median.
`days[]` and, per chain, aligned `level[]` (`1.0` is the median of all chains that
day, `0.93` is 7 % cheaper) and `items[]` (basket items priced there that day),
`null` where a chain has no value.

Parameters:

- `days` (query, integer, default 365, 7–3650): How many days back from today (7–3650).

Responses:

- `200` RetailerLevels — The series.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/retailers/levels"`

### Stores

Every store with its price level, what it charges, and places rolled up.

#### GET /api/v1/stores

**Every store with its price level and place** · MCP tool `list_stores`

Every geolocated store (about 500) with coordinates, the nearest town, region and
urban class from official Stats NZ boundaries, how many club/special/multibuy
promotions it has this week, and its **basket price level**: shelf prices of
basket items against the same chain's national median, weighted like the Stats NZ
basket (`1.03` = 3 % dearer than the rest of its chain, `null` = not enough priced
items). Filter with `retailer`, `town`, `area` or `urban` to get one place instead
of the whole country (~130 KB unfiltered).

Parameters:

- `retailer` (query, string): Retailer slug.
- `town` (query, string): Town name as returned in `town` (case-insensitive), e.g. `Tokoroa`, `Rural Auckland`.
- `area` (query, string): Region name as returned in `area` (case-insensitive), e.g. `Waikato`.
- `urban` (query, "major" | "large" | "medium" | "small" | "rural", one of "major"/"large"/"medium"/"small"/"rural"): Urban class.

Responses:

- `200` Store[] — Stores, grouped by chain then name.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/stores"`

#### GET /api/v1/stores/{id}

**How one store prices against its chain** · MCP tool `get_store`

The store's level overall and per basket group, the eight biggest markups
(`dearer`) and markdowns (`cheaper`) against the chain's national median, a
histogram of shelf ÷ median for every listing priced there (`spread`: 20 bins over
0.80–1.20, buckets 0 and 21 are the tails), and its current `deals`. Store ids
come from `list_stores`.

Parameters:

- `id` (path, integer, ≥ 1, required): Store id.

Responses:

- `200` StoreDetail — The store panel.
- `404` Error — No such thing.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/stores/285"`

#### GET /api/v1/stores/{id}/listings

**What one store charges, searchable** · MCP tool `get_store_listings`

Every product priced at the store this week with its shelf price, the chain's
national median and the ratio between them, paged and optionally filtered by text.
The question a shopper standing in the aisle has: "what does *this* store charge
for X".

Parameters:

- `id` (path, integer, ≥ 1, required): Store id.
- `q` (query, string): Free text (up to 100 characters): a product, brand, category, or the digits under a barcode.
- `limit` (query, integer, default 24, 1–96)
- `offset` (query, integer, default 0, ≥ 0)

Responses:

- `200` StoreListings — A page of the store's shelf.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/stores/285/listings?q=milk"`

#### GET /api/v1/stores/{id}/levels

**One store's basket price level against its chain, by day** · MCP tool `get_store_levels`

The store panel's verdict (`store.level` from `get_store`) for every day the index
has run: the chain's national basket matches priced from this store's shelf against
the chain's daily median, as a weighted geometric mean. `days[]` with aligned
`level[]` (`1.0` is the chain's median that day, `1.03` is 3 % dearer) and
`items[]` (basket items priced here that day), `null` on days fewer than twenty were.
The newest point can differ from `get_store` by a hair: the panel carries the latest
observation, the series the span in force that day.

Parameters:

- `id` (path, integer, ≥ 1, required): Store id.
- `days` (query, integer, default 365, 7–3650): How many days back from today (7–3650).

Responses:

- `200` StoreLevels — The series.
- `404` Error — No such thing.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/stores/285/levels"`

#### GET /api/v1/areas

**Price levels by region, town size and town** · MCP tool `list_areas`

The store levels from `list_stores` rolled up three ways: by region, by Stats NZ
urban class (main centres down to rural), and by town. Because every store is
measured against its own chain, an area's level is the premium of the place, with
the chain mix taken out: "does Gisborne pay more than Hamilton for the same shops".

Responses:

- `200` Areas — Three roll-ups.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/areas"`

### Deals

What is cheaper than shelf right now, split by who gets it.

#### GET /api/v1/deals

**What is cheaper than shelf right now** · MCP tool `list_deals`

Current promotions, biggest saving first, split by who gets them: `special`
(everyone), `member` (needs the chain's loyalty card), or `multibuy` (only if you
take the set). Optionally restricted to one chain and/or filtered by text.
`total` is the size of the whole feed, `listings` the page you asked for.

Parameters:

- `kind` (query, "special" | "member" | "multibuy", default "special", one of "special"/"member"/"multibuy")
- `retailer` (query, string): Retailer slug.
- `q` (query, string): Free text (up to 100 characters): a product, brand, category, or the digits under a barcode.
- `limit` (query, integer, default 24, 1–96)

Responses:

- `200` Deals — The feed.
- `400` Error — A parameter is malformed or out of range.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/deals?q=milk"`

### Food price index

The daily inflation nowcast on the Stats NZ basket.

#### GET /api/v1/index/latest

**Today's food-price index headline** · MCP tool `get_index_latest`

The landing-page number: the anchored headline index as of the latest computed
day, its change over 1 day, 30 days and a year (percent), coverage (basket weight
observed, listings and items feeding it), each group's level, and a one-year
sparkline. Served with an `ETag`; send `If-None-Match` to get `304`.

Responses:

- `200` IndexLatest — The headline.
- `304` — Not modified since the `ETag` you sent.
- `404` — No index has been computed yet.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/index/latest"`

#### GET /api/v1/index/daily

**Daily index series for one group** · MCP tool `get_index_daily`

One row per day for one group over a date range (default the last year): the raw
chained series, the series anchored to the last official Stats NZ monthly print,
and the share of basket weight observed that day. Group `0` is all food; the other
ids are listed by `get_index_groups`.

Parameters:

- `from` (query, string): First day, inclusive. Default one year before today.
- `to` (query, string): Last day, inclusive. Default today.
- `group` (query, integer, default 0): Basket group id (`0` = all food).

Responses:

- `200` IndexPoint[] — Daily points, oldest first.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/index/daily"`

#### GET /api/v1/index/groups

**Index series for the headline and every group, columnar** · MCP tool `get_index_groups`

`days[]` and, per group (all food first, then the Stats NZ groups such as fruit and
vegetables, meat, grocery food), aligned `anchored[]`, `raw[]` and `coverage[]`
arrays with `null` where a group has no value that day. Also the place to learn
each group's `id`, `code`, `name` and basket weight.

Parameters:

- `days` (query, integer, default 365, 7–3650): How many days back from today (7–3650).

Responses:

- `200` IndexGroups — The series.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/index/groups"`

#### GET /api/v1/index/items

**Every basket item's relative price by day, columnar** · MCP tool `get_index_items`

`days[]` and, per observable basket item, an aligned `relative[]` (1.0 = the item's
level on its base day, `null` where the day was not computed) and `n[]`, the
number of listings feeding it that day (0 = carried forward). Each day is the
newest vintage, as in every other index answer. This is the item-by-item trend
table on the inflation page.

Parameters:

- `days` (query, integer, default 365, 7–3650): How many days back from today (7–3650).

Responses:

- `200` IndexItems — The series.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/index/items"`

### Basket

The Stats NZ Food Price Index basket the index is built on.

#### GET /api/v1/basket/{id}

**One basket item and the products it is priced from** · MCP tool `get_basket_item`

The item (name, Stats NZ spec, group, weight, whether it compares per kilogram or
per pack) and every listing currently matched to it, with the latest shelf and
promotion price, the price on the item's comparison basis (`basis_price`, cents per
kg / L for a per-unit item with a weight or volume pack, else the pack price) and
the last day it was priced. A listing priced more than 14 days ago is shown but
does not count. This is what opens when a cell of the comparison table is clicked.

Parameters:

- `id` (path, integer, required)

Responses:

- `200` BasketItemDetail — The item and its listings, by retailer then basis price.
- `404` — No such basket item.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/basket/285"`

#### GET /api/v1/basket

**The Stats NZ basket items and their weights** · MCP tool `get_basket`

Every item in the Food Price Index basket the index is computed from: its Stats
NZ code and name, group, weights, the size specification, the keywords the matcher
uses, how many listings are currently matched to it, and its latest relative price
and staleness. With `include=matches` each item also carries `match_list`, the
matched listings themselves (several megabytes; the `basket` export is the better
way to get that).

Parameters:

- `include` (query, "matches", one of "matches"): Set to `matches` to embed every matched listing per item.

Responses:

- `200` BasketItem[] — Items, by group then name.
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

Example: `curl "https://api.supertracker.nz/api/v1/basket"`

### Bulk data

Nightly exports of the whole dataset. Use these instead of crawling.

#### GET /data/latest/manifest.json

**The nightly exports and their checksums** · MCP tool `get_export_manifest`

The bulk path. Every night after the index run the whole dataset is written as
Zstd Parquet and gzipped CSV under `/data/latest/`: `listings`, `stores`,
`price_spans` (the full span history; this is *the* dataset), `index_daily` and
`basket`. The manifest lists each file's byte size, row count and SHA-256, the
data day and the licence. Download `/data/latest/<name>` directly; the files carry
long cache lifetimes and are not rate limited. Anyone who wants more than a few
hundred lookups should start here.

Responses:

- `200` ExportManifest — The manifest.
- `404` — No export has run yet.

Example: `curl "https://api.supertracker.nz/data/latest/manifest.json"`

### Meta

Health, documentation, and the MCP endpoint.

#### GET /healthz

**Liveness**

`ok` when the API can reach its database.

Responses:

- `200` const "ok" — Healthy.
- `503` — Database unreachable.

Example: `curl "https://api.supertracker.nz/healthz"`

#### GET /openapi.json

**This document**

The OpenAPI 3.1 description of the API, also available as YAML at `/openapi.yaml`. The interactive reference is at `/`.

Responses:

- `200` object — The OpenAPI document.

Example: `curl "https://api.supertracker.nz/openapi.json"`

#### GET /llms.txt

**llms.txt**

An [llms.txt](https://llmstxt.org/) index for this API. `/llms-full.txt` is the complete reference as Markdown, generated from this document.

Responses:

- `200` string — Markdown.

Example: `curl "https://api.supertracker.nz/llms.txt"`

#### POST /mcp

**Model Context Protocol endpoint**

A stateless MCP server over Streamable HTTP. Every operation in this document
that is not marked internal is a read-only tool with the same name as its
`operationId` and the same JSON result. Supports protocol revision `2026-07-28`
(per-request `_meta`, `server/discover`, `Mcp-Method`/`Mcp-Name` headers) and the
legacy `initialize` handshake of `2025-03-26` through `2025-11-25`; no session ids
are issued. Point an MCP client at `https://api.supertracker.nz/mcp` with no
authentication. Resources expose this document, `llms-full.txt` and the export
manifest.

Responses:

- `200` object — A JSON-RPC 2.0 response.
- `202` — Notification accepted.
- `400` — Header mismatch, unsupported protocol version or malformed request, as a JSON-RPC error.
- `404` — Unknown JSON-RPC method (`-32601`).
- `429` Error — Over the per-IP budget. Wait `Retry-After` seconds, or use the exports for bulk work.

## Schemas

Field types are JSON types; `| null` marks a field that can be null. Money is integer cents.

### Error

- `error` (object, required)

### Cents

New Zealand cents.

Type: integer.

### PromoKind

Who the promotion is for: `1` special (everyone), `2` member/club price, `3` multibuy, `4` clearance.

Type: 1 | 2 | 3 | 4.

### Day

New Zealand calendar day.

Type: string.

### ImagePath

Path on this host to a 720 px AVIF; insert `_t` before `.avif` for the 200 px thumbnail.

Type: string.

### ListingCard

A listing as shown on any card, with an honest price.

- `id` (integer, required): Listing id.
- `retailer` (string, required): Retailer slug.
- `name` (string, required)
- `brand` (string | null)
- `size_text` (string | null): Size as printed, e.g. `1.5l`.
- `price` (integer | null): Shelf price, cents.
- `promo_price` (integer | null): Promotional single-unit price, cents.
- `promo_kind` (PromoKind | null)
- `multibuy_qty` (integer | null): Units you must take for `multibuy_price`.
- `multibuy_price` (integer | null): Total price for `multibuy_qty` units, cents.
- `image` (ImagePath | null)
- `available` (boolean, required): Still on the retailer's site at the last collection.
- `product_id` (integer | null): Cross-chain product, once linked.

### ProductGroup

One product with its offers at every chain that sells it, cheapest first.

- `product_id` (integer | null, required): `null` for a listing not yet linked to a product.
- `name` (string, required)
- `brand` (string | null)
- `size_text` (string | null)
- `image` (ImagePath | null)
- `generic_id` (integer | null)
- `generic_label` (string | null): The generic the product belongs to, e.g. `Regular soft drink, 1.5 L`.
- `offers` (ListingCard[], required)

### ShelfSummary

A shelf as the directory, a breadcrumb or a child list shows it.

- `slug` (string, required)
- `label` (string, required)
- `depth` (integer, required): 1 = department, 2 = aisle, 3 = a curated sub-shelf.
- `parent` (string | null, required): Parent shelf's slug; null at a department.
- `products` (integer, required): Products with a current offer, at the last sync.
- `compare_unit` (string | null, required): The unit its per-unit sort compares on: `kg`, `l`, `100g`, `100ml`, `each` or `100sheet`.

### ShelfProduct

### ShelfPage

- `shelf` (ShelfSummary, required): A shelf as the directory, a breadcrumb or a child list shows it.
- `crumbs` (ShelfSummary[], required): Root first, the shelf itself last.
- `children` (ShelfSummary[], required)
- `unit` (string | null, required): The unit family the per-unit sort compares on.
- `kinds` (object[], required): Kinds represented under the current filters (the kind filter excepted), most products first.
- `retailers` (object[], required): Chains stocking the current set (the chain filter excepted).
- `total` (integer, required): Products in the set under the current filters.
- `params` (object, required): The request as applied, so a client can draw its controls from the answer.
- `products` (ShelfProduct[], required)

### Suggestion

- `kind` ("category" | "brand" | "product" | "listing", required)
- `id` (integer | null)
- `label` (string, required)
- `detail` (string | null)
- `href` (string, required): Site path to open.
- `price` (integer | null)
- `promo_price` (integer | null)
- `promo_kind` (PromoKind | null)
- `image` (ImagePath | null)

### Retailer

- `slug` (string, required)
- `name` (string, required)
- `listings` (integer, required): Catalogue size.
- `stores` (integer, required): Active stores in the price panel.
- `last_run` (string | null): When the catalogue was last collected.

### CategoryNode

- `label` (string, required)
- `listings` (integer, required): Listings under this node (matches, when `q` is set).

### RetailerBrowse

- `retailer` (Retailer, required)
- `path` (string[], required): The category path you asked for.
- `categories` (CategoryNode[], required): Child categories, biggest first.
- `total` (integer, required): Listings at and below this node (matching `q`).
- `limit` (integer, required)
- `offset` (integer, required)
- `listings` (ListingCard[], required)
- `q` (string, required): The filter, echoed.

### CurrentPrice

The latest national span of a listing.

- `day` (Day, required): New Zealand calendar day.
- `price` (Cents, required): New Zealand cents.
- `promo_price` (integer | null)
- `promo_kind` (PromoKind | null)
- `multibuy_qty` (integer | null)
- `multibuy_price` (integer | null)

### ListingStorePrice

Today's price of a listing at one store.

- `store_id` (integer, required)
- `store` (string, required)
- `region` (string | null): The retailer's own region code (`NI`/`SI` for Foodstuffs chains, a regional code such as `WKO` for Woolworths). For a consistent place, use the store's `area` and `town` from `list_stores`.
- `lat` (number | null)
- `lng` (number | null)
- `day` (Day, required): New Zealand calendar day.
- `price` (Cents, required): New Zealand cents.
- `promo_price` (integer | null)
- `promo_kind` (PromoKind | null)
- `multibuy_qty` (integer | null)
- `multibuy_price` (integer | null)

### DailyStats

Columnar daily spread across stores. Empty object when there is none.

- `day` (Day[])
- `min` (integer[])
- `med` (integer[])
- `max` (integer[])
- `n` (integer[]): Stores reporting that day.

### ListingDetail

- `id` (integer, required)
- `retailer` (string, required): Retailer display name.
- `retailer_slug` (string, required)
- `external_id` (string, required): The retailer's own product id.
- `name` (string, required)
- `brand` (string | null)
- `size_text` (string | null)
- `qty` (number | null): Quantity in `unit`.
- `unit` (string | null)
- `gtin` (integer | null): Barcode as an integer.
- `category_path` (string[], required): The retailer's own category breadcrumb.
- `first_seen` (Day, required): New Zealand calendar day.
- `last_seen` (Day, required): New Zealand calendar day.
- `image` (ImagePath | null)
- `available` (boolean, required)
- `product_id` (integer | null)
- `stores_total` (integer, required): Stores the chain has in the panel.
- `current` (CurrentPrice | null)
- `stores` (ListingStorePrice[], required): Every store pricing it today, cheapest first.
- `stats` (DailyStats, required): Columnar daily spread across stores. Empty object when there is none.
- `retailer_url` (string | null): The product on the retailer's own site, when it has one.

### History

Price spans as parallel arrays; index `i` of each array is one span.

- `from` (Day[], required): First day of each span.
- `to` (Day[], required): Last day each span was observed.
- `price` (integer[], required): Shelf price, cents.
- `promo` (integer | null[], required): Promotional price, cents.
- `kind` (PromoKind | null[], required)
- `multibuy_qty` (integer | null[], required)
- `multibuy_price` (integer | null[], required)

### HistoryByRetailer

Retailer slug → history.

Object of `History`.

### Spread

- `day` (Day[], required)
- `min` (integer[], required)
- `med` (integer[], required)
- `max` (integer[], required)
- `n` (integer[], required): Stores reporting.

### SpreadByRetailer

Retailer slug → daily spread.

Object of `Spread`.

### Attribute

- `key` (string, required)
- `label` (string, required)
- `value` (string, required)

### Offer

One chain's listing on a product page, with how it got there.

- `id` (integer, required): Listing id.
- `retailer` (string, required): Retailer slug.
- `retailer_name` (string, required)
- `name` (string, required)
- `brand` (string | null)
- `size_text` (string | null)
- `qty` (number | null)
- `unit` (string | null)
- `pack_count` (integer | null)
- `weighable` (boolean, required): Sold by weight.
- `price` (integer | null): National shelf price today, cents.
- `promo_price` (integer | null)
- `promo_kind` (PromoKind | null)
- `multibuy_qty` (integer | null)
- `multibuy_price` (integer | null)
- `price_day` (Day | null): Day of the latest national observation.
- `image` (ImagePath | null)
- `available` (boolean, required)
- `last_seen` (Day, required): New Zealand calendar day.
- `method` (string, required): How the listing was linked: `gtin`, `name`, `llm`, `manual`, …
- `confidence` (number, required)
- `locked` (boolean, required): Pinned by a curator.
- `stores` (integer, required): Stores with a price today (0 for a national-only chain).
- `stores_total` (integer, required)
- `retailer_url` (string | null)
- `category_path` (string[], required)
- `origin` (string | null): Country-of-origin statement, when the retailer gives one.
- `package_type` (string | null)

### Alternative

Another product in the same generic.

- `id` (integer, required): Product id.
- `name` (string, required)
- `brand` (string | null)
- `size_text` (string | null)
- `image` (ImagePath | null)
- `retailers` (integer, required): Chains that sell it.
- `price` (integer | null): Cheapest current shelf price, cents.
- `retailer` (string | null): Where that price is.
- `own_brand` (boolean, required): A house brand.

### ProductDetail

- `id` (integer, required)
- `name` (string, required)
- `brand` (string | null)
- `brand_id` (integer | null)
- `size_text` (string | null)
- `pack_count` (integer | null)
- `qty` (number | null)
- `unit` (string | null)
- `weighable` (boolean, required)
- `image` (ImagePath | null)
- `aliases` (string[], required): Other names it is searched by.
- `retailers` (integer, required): Chains that sell it.
- `gtins` (integer[], required): Barcodes seen for it.
- `generic_id` (integer | null)
- `generic_label` (string | null)
- `generic_kind` (string | null): Slug of the kind, e.g. `soft-drink`.
- `generic_kind_label` (string | null)
- `shelf_slug` (string | null): The most specific shelf its kind is on, for `browse_shelf`.
- `shelf_label` (string | null)
- `compare_unit` (string | null): The unit prices in this kind are compared per (`kg`, `l`, `each`).
- `brand_owner` (string | null): Retailer slug when the brand is a house brand.
- `brand_parent` (string | null): Parent brand of a sub-brand.
- `attributes` (Attribute[], required)
- `offers` (Offer[], required): One per linked listing; available and cheapest first.
- `alternatives` (Alternative[], required)

### ProductStorePrice

- `store_id` (integer, required)
- `store` (string, required)
- `retailer` (string, required): Retailer slug.
- `lat` (number | null)
- `lng` (number | null)
- `price` (Cents, required): New Zealand cents.
- `promo_price` (integer | null)
- `promo_kind` (PromoKind | null)
- `multibuy_qty` (integer | null)
- `multibuy_price` (integer | null)

### Store

- `id` (integer, required)
- `retailer` (string, required): Retailer slug.
- `name` (string, required)
- `region` (string | null): The retailer's own region code (`NI`/`SI` for Foodstuffs chains, a regional code such as `WKO` for Woolworths). For a consistent place, use the store's `area` and `town` from `list_stores`.
- `lat` (number, required)
- `lng` (number, required)
- `level` (number | null): Basket price level against the chain's national median; `1.03` = 3 % dearer.
- `n_items` (integer, required): Basket items the level is computed from.
- `club` (integer, required): Club-price promotions this week.
- `sale` (integer, required): Specials this week.
- `multibuy` (integer, required): Multibuys this week.
- `day` (Day | null): Latest day with prices.
- `town` (string, required): Nearest urban area, or `Rural <region>`.
- `area` (string, required): Region (Stats NZ Regional Council).
- `urban` (string | null)

### GroupLevel

- `code` (string, required)
- `name` (string, required)
- `level` (number, required)
- `n` (integer, required)

### StoreListing

- `id` (integer, required): Listing id.
- `name` (string, required)
- `brand` (string | null)
- `size_text` (string | null)
- `image` (ImagePath | null)
- `price` (Cents, required): New Zealand cents.
- `promo_price` (integer | null)
- `promo_kind` (PromoKind | null)
- `median` (integer, required): The chain's national median, cents.
- `ratio` (number, required): `price / median`.

### Bin

- `bucket` (integer, required): 0 and 21 are the tails; 1–20 span 0.80–1.20 in 0.02 steps.
- `n` (integer, required)

### StoreDetail

- `store` (Store, required)
- `groups` (GroupLevel[], required)
- `dearer` (StoreListing[], required)
- `cheaper` (StoreListing[], required)
- `spread` (Bin[], required)
- `listings_priced` (integer, required)
- `deals` (ListingCard[], required)

### StoreListings

- `total` (integer, required)
- `limit` (integer, required)
- `offset` (integer, required)
- `listings` (StoreListing[], required)

### Area

- `key` (string, required)
- `label` (string, required)
- `level` (number | null): Item-weighted geometric mean of the stores' levels.
- `stores` (integer, required)
- `chains` (string[], required)
- `towns` (string[], required)

### Areas

- `regions` (Area[], required)
- `urban` (Area[], required): By Stats NZ urban class, main centres first.
- `towns` (Area[], required)

### Deals

- `kind` ("special" | "member" | "multibuy", required)
- `total` (integer, required): Size of the whole feed.
- `listings` (ListingCard[], required)

### IndexLatest

- `as_of` (Day, required): New Zealand calendar day.
- `headline` (object, required)
- `coverage` (object, required)
- `groups` (object[], required)
- `sparkline` (object, required)

### IndexPoint

- `day` (Day, required): New Zealand calendar day.
- `group_id` (integer, required)
- `raw` (number, required): Chained series, base 1000 on the first day.
- `anchored` (number | null): Anchored to the last official monthly value.
- `coverage` (number, required)

### GroupSeries

- `id` (integer, required)
- `code` (string, required)
- `name` (string, required)
- `weight` (number, required): Basket weight, percent.
- `anchored` (number | null[], required)
- `raw` (number | null[], required)
- `coverage` (number | null[], required)

### IndexGroups

- `days` (Day[], required)
- `groups` (GroupSeries[], required)

### ItemSeries

- `id` (integer, required)
- `name` (string, required)
- `spec` (string | null): The Stats NZ representative size.
- `group` (string, required): Basket group code.
- `weight` (number, required)
- `per_unit` (boolean, required): Compared per kilogram / litre rather than per pack.
- `unit` (string | null)
- `relative` (number | null[], required): Aligned with `days`; 1.0 = the item's level on its base day.
- `n` (integer | null[], required): Listings feeding the item that day; 0 = carried forward.

### IndexItems

- `days` (Day[], required)
- `items` (ItemSeries[], required)

### BasketItemListing

- `listing_id` (integer, required)
- `retailer` (string, required): Retailer slug.
- `name` (string, required)
- `brand` (string | null)
- `size_text` (string | null)
- `qty` (number | null)
- `unit` (string | null)
- `weighable` (boolean, required): Sold loose by weight; the price is per kilogram.
- `price` (integer | null): Shelf price, cents.
- `promo_price` (integer | null): Special or club price, cents, when one applies.
- `basis_price` (number | null): The price that counts, cents, on the item's comparison basis.
- `priced_on` (Day | null): Last day the price was seen.
- `valid_from` (Day, required): New Zealand calendar day.
- `origin` ("auto" | "manual", required)
- `image` (string | null)
- `available` (boolean, required)

### BasketItemDetail

- `id` (integer, required)
- `name` (string, required)
- `spec` (string | null)
- `group` (string, required)
- `group_name` (string, required)
- `per_unit` (boolean, required)
- `unit` (string | null)
- `weight` (number, required)
- `listings` (BasketItemListing[], required)

### LevelSeries

- `retailer` (string, required): Retailer slug.
- `level` (number | null[], required): 1.0 = the cross-chain median that day.
- `items` (integer | null[], required)

### RetailerLevels

- `days` (Day[], required)
- `retailers` (LevelSeries[], required)

### StoreLevels

- `days` (Day[], required)
- `level` (number | null[], required): Aligned with `days`; `1.0` is the chain's national median that day.
- `items` (integer | null[], required): Basket items priced at the store that day.

### Compare

- `retailers` (string[], required)
- `headline` (object of [number, integer], required): Retailer slug → `[level, items]`; `1.0` is the cross-chain median.
- `groups` (object[], required)
- `items` (object[], required)

### BasketMatch

- `listing_id` (integer, required)
- `retailer` (string, required)
- `name` (string, required)
- `brand` (string | null)
- `size_text` (string | null)
- `price` (integer | null)
- `promo_price` (integer | null)
- `confidence` (number | null)
- `origin` ("auto" | "manual", required)
- `locked` (boolean, required)
- `valid_from` (Day, required): New Zealand calendar day.
- `ref_price` (integer | null): Price when the match was made; the link factor's base.
- `linked_relative` (number | null)
- `image` (ImagePath | null)

### BasketItem

- `id` (integer, required)
- `code` (string, required): Stats NZ classification code.
- `name` (string, required)
- `group_code` (string, required)
- `group_name` (string, required)
- `group_weight` (number, required)
- `weight` (number, required): Item weight within the basket, percent.
- `spec` (string | null): The size specification priced, e.g. `Apples, fresh — 1 kg`.
- `observable` (boolean, required): Whether the group is observable from supermarket shelves.
- `unit` (string | null)
- `qty_min` (number | null)
- `qty_max` (number | null)
- `per_unit` (boolean, required)
- `keywords` (string[], required)
- `exclude` (string[], required)
- `matches` (integer, required): Listings currently matched.
- `relative` (number | null): Latest relative price of the item (1.0 = base).
- `stale` (boolean | null): Carried forward past the staleness cap.
- `match_list` (BasketMatch[]): Only with `include=matches`.

### ExportManifest

- `day` (Day, required): New Zealand calendar day.
- `licence` (const "CC BY 4.0", required)
- `generated_at` (string, required)
- `files` (object[], required)

