Query grocer.nz public NZ supermarket data — canonical product and barcode/GTIN lookup, cross-retailer product matching, store lookup, current per-store prices, and historical price rows from public DuckDB/parquet assets. Use for exact NZ grocery product matching or pricing across Woolworths, New World, PAK'nSAVE, Fresh Choice, and related stores. Read-only; no login or private user data.
Scanned 9/10/2026
Install to Claude Code
npx -y skills add thecolab-ai/.skills --skill grocer-nz --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Grocer Nz?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/thecolab-ai-grocer-nz)More formats (shields.io, HTML) on the badges page.
---
name: grocer-nz
description: "Query grocer.nz public NZ supermarket data — canonical product and barcode/GTIN lookup, cross-retailer product matching, store lookup, current per-store prices, and historical price rows from public DuckDB/parquet assets. Use for exact NZ grocery product matching or pricing across Woolworths, New World, PAK'nSAVE, Fresh Choice, and related stores. Read-only; no login or private user data."
license: MIT
compatibility: "Requires Python 3.10+ and network access for live data"
metadata:
thecolab.category: "food-and-fuel"
thecolab.source_owner: "Grocer.nz"
thecolab.source_type: "commercial"
thecolab.auth: "none"
thecolab.access_mode: "html-readonly"
thecolab.data_class: "public"
thecolab.writes: "false"
thecolab.browser: "false"
thecolab.risk: "low"
thecolab.cache_ttl: "24h"
thecolab.schema_version: "1"
thecolab.skill_type: "html-readonly"
thecolab.pack: "nz-commercial-web"
thecolab.source_url: "https://assets-prod.grocer.nz/public"
thecolab.allowed_domains: "assets-prod.grocer.nz,grocer.nz,meilisearch.grocer.nz"
thecolab.last_verified: "2026-07-24"
thecolab.health: "healthy"
thecolab.maintainer: "@adam91holt"
---
# Grocer.nz Public Price Data
## Overview
This skill queries public data exposed by **grocer.nz**, a New Zealand grocery price comparison app. It is useful as a canonical product/GTIN crosswalk as well as for current supermarket price checks, per-store comparisons, and historical product price rows.
The CLI uses:
- Grocer public static assets: `https://assets-prod.grocer.nz/public/`
- Public DuckDB base database: `base_v3.duckdb.br`
- Per-store current price parquet files: `prices_per_store_v3/public_prices_<store_id>.parquet`
- Per-product history parquet files: `price_history_v3/price_history_<product_id>.parquet`
- Grocer frontend Meilisearch product index for search.
Script path:
```bash
skills/grocer-nz/scripts/cli.py
```
The script bootstraps `duckdb` via `uv` when the active Python does not already have it.
## When to Use
- Adam asks for historic supermarket pricing in NZ.
- Adam wants to compare prices across Woolworths, New World, PAK'nSAVE, Fresh Choice, Super Value, The Warehouse, etc.
- Adam wants a store id for a known store, e.g. Papakura.
- Adam wants price history for a known Grocer product id.
- Adam wants current prices for one product across selected stores.
- Adam wants the public DuckDB/parquet download URLs so another agent can download or analyse the raw files itself.
- Adam wants to identify the same exact product in the Woolworths, New World, or PAK'nSAVE skills.
- Adam has a UPC, EAN, or GTIN and needs the corresponding Grocer product id.
Do **not** use this for authenticated Grocer Pro features, private user lists, or anything requiring sign-in. This skill is read-only public data only.
## Commands
Set a helper variable:
```bash
GROCER=skills/grocer-nz/scripts/cli.py
```
### Get raw asset download URLs
Use `assets` when the caller wants the source files rather than parsed rows. This
command only constructs links from Grocer's fixed public asset layout. It makes
no network request, starts no DuckDB process, and does not download or parse any
file.
```bash
# Base catalogue DuckDB URL (also the default with no selectors)
python3 $GROCER assets --base --json
# Current-price parquet URLs for selected stores
python3 $GROCER assets --store-id 230 --store-id 307 --json
# Per-product history parquet URLs
python3 $GROCER assets --product 5452 --product 5461 --json
# Combine all three asset types
python3 $GROCER assets --base --store-id 230 --product 5452 --json
```
The result contains `delivery: external_url` and a `resources` array. Each
resource has a declared `format`, filename, and public `url`. Through The Colab
MCP, supported resources are also emitted as native MCP `resource_link` content,
so the client downloads directly from Grocer rather than through the MCP server.
The URLs are deliberately not probed; a nonexistent store or product id can
therefore produce a link that returns 403/404 when downloaded. At most 50 links
may be requested in one call.
### List stores
```bash
python3 $GROCER stores --query Papakura
python3 $GROCER stores --vendor "PAK" --limit 20 --json
```
Useful known Papakura ids from live smoke:
- `118` — Woolworths Papakura
- `206` — Fresh Choice Papakura
- `230` — PAK'nSAVE Papakura
- `307` — New World Papakura
### Search products
Search only:
```bash
python3 $GROCER search "milk" --limit 5
```
Search plus current prices in matching stores:
```bash
python3 $GROCER search "milk" --store-query Papakura --limit 3
python3 $GROCER search "whittakers" --store-id 230 --store-id 307 --json
```
The search command returns Grocer product ids, canonical GTIN-14 `barcodes`, and compact `retailer_search_terms`. Use the product ids with `prices` and `history`; use the retailer search terms with the retailer-native skills.
### Resolve a barcode
Grocer stores barcodes in zero-padded GTIN-14 form. The command accepts compact UPC/EAN forms too:
```bash
python3 $GROCER barcode 94152210
python3 $GROCER barcode 00000094152210 --json
```
For Anchor Blue Milk 2L, this resolves to Grocer product `5452`, canonical GTIN `00000094152210`, and retailer search term `94152210`.
### Match the exact product across retailers
1. Search Grocer by product description and select the right brand/name/size candidate.
2. Copy a returned `retailer_search_terms` value.
3. Search each retailer skill with that value:
```bash
python3 skills/woolworths-nz/scripts/cli.py search 94152210 --json
python3 skills/paknsave-nz/scripts/cli.py search 94152210 --json
python3 skills/newworld-nz/scripts/cli.py search 94152210 --json
```
Woolworths search can include loosely related results, so keep only rows whose `barcode`, normalised to GTIN-14, equals Grocer's canonical barcode. Foodstuffs barcode search returns the matching New World/PAK'nSAVE product id; both banners use the same Foodstuffs product id for the same product. If barcode search returns no exact result, do not silently substitute a similar size or variant—fall back to brand/name/size matching and label it as a probable match.
### Current prices for a product
```bash
python3 $GROCER prices 5461 --store-query Papakura
python3 $GROCER prices 5461 --store-id 230 --store-id 307 --json
```
`--all-stores` is supported but can be slow because it downloads many per-store parquet files:
```bash
python3 $GROCER prices 5461 --all-stores --limit 30
```
### Price history for a product
```bash
python3 $GROCER history 5461 --store-query Papakura --limit 20
python3 $GROCER history 5461 --store-id 230 --json
```
History files are per product and include store-level rows when available.
### Raw product metadata
```bash
python3 $GROCER product 5461 --json
```
### Ad-hoc SQL query (guarded, read-only)
Run a single `SELECT`/`WITH` statement over the dataset for analysis the fixed
commands don't cover (cheapest-across-stores, vendor trends, joins, aggregates).
Always-available relations: `products`, `barcodes`, `stores`, `vendors`. Load extra data with
flags: `--store-id`/`--store-query`/`--all-stores` populate a `prices` table;
`--product` populates a `history` table (with a `product_id` column added).
```bash
# cheapest blue-top milk (product 5452) across Papakura stores
python3 $GROCER query "select s.name, p.original_price_cent from prices p join stores s on s.id=p.store_id where p.product_id=5452 order by 2" --store-query Papakura --json
# count products by vendor that stock a given product's price rows
python3 $GROCER query "with p as (select id,name,brand from products where lower(brand)='anchor') select * from p" --limit 20
# resolve compact retailer barcodes against Grocer's canonical catalogue
python3 $GROCER query "select b.barcode, p.* from barcodes b join products p on p.id=b.product_id where b.barcode='00000094152210'" --json
# price history rows for a product
python3 $GROCER query "select store_id, price_cent, updated_at from history order by updated_at desc" --product 5461 --limit 30 --json
```
**Safety:** the statement must be a single read-only `SELECT`/`WITH` (no `;`, no
DDL/DML). Filesystem and network access are disabled (`enable_external_access=
false`, `lock_configuration=true`) after the requested parquet are loaded, and
the base catalogue is attached `READ_ONLY`. An arbitrary query can therefore only
read the public grocery data exposed here — it cannot write, read other files,
reach the network, or change settings. Results are capped (`--limit`, max 5000).
## Data Notes
See `references/api-notes.md` for the sniffed endpoints and file layout.
Important model fields:
- Products: `id`, `name`, `brand`, `unit`, `size`, `redirected_to`
- Barcodes: zero-padded GTIN-14 `barcode`, `product_id`
- Stores: `id`, `vendor_id`, `name`, `is_enabled`
- Current prices: `updated_at`, `store_id`, `product_id`, `original_price_cent`, `sale_price_cent`, `club_price_cent`, `online_price_cent`, multibuy fields
- Price history: `updated_at`, `store_id`, `price_cent`
Prices are stored in **NZ cents**. `$5.50` is represented as `550`.
## Common Pitfalls
1. **No store selected = limited price context.** Product search comes from Meilisearch. Current per-store pricing requires store ids or `--store-query` so the CLI can download the relevant parquet files.
2. **`--all-stores` can be noisy/slow.** It may download hundreds of current price parquet files. Prefer targeted store ids for fast checks.
3. **The `.br` DuckDB file may arrive already decompressed.** The CLI treats Grocer's `base_v3.duckdb.br` CDN response as a DuckDB file directly because Cloudflare/Caddy often transparently decodes it.
4. **DuckDB DDL cannot use prepared parameters for `read_parquet(?)`.** Quote local parquet paths explicitly before `create view ... as select * from read_parquet('<path>')`; parameter binding works for normal `select` filters but not this statement shape.
5. **History parquet schema is leaner than the app's local table.** Per-product history files may only include `updated_at`, `store_id`, and `price_cent`; get `vendor_id` by joining `public_stores`, and infer product id from the requested history file.
6. **Historical availability varies.** Some products/stores have no history parquet or no rows for a selected store.
7. **Effective price is conservative.** The CLI reports `effective_price_cent` as the minimum of original/sale/club/online when present. Multibuy needs human interpretation because quantity matters.
8. **Live smoke tests depend on upstream stock/history.** Product `5461` and Papakura stores were live-valid when added, but upstream catalogue changes can make the smoke fail without a code bug.
9. **This is public read-only data.** Do not try to bypass sign-in or scrape private Grocer user/list/pro features.
10. **Retailer barcode search has different shapes.** Search Foodstuffs with the compact barcode (leading zero padding removed). Woolworths returns its own `barcode`; normalise and verify that field because its text search may include unrelated rows.
11. **`assets` constructs but does not validate links.** Use it for low-overhead handoff of raw public files. Use the parsed commands when the agent needs verified rows immediately.
## Verification Checklist
Run these before relying on a fresh install:
```bash
python3 $GROCER stores --query Papakura --json
python3 $GROCER assets --base --store-id 230 --product 5452 --json
python3 $GROCER search "milk" --store-query Papakura --limit 2
python3 $GROCER prices 5461 --store-query Papakura --limit 10
python3 $GROCER history 5461 --store-query Papakura --limit 5
```
Expected smoke signal: Papakura stores include Woolworths/New World/PAK'nSAVE, and product `5461` returns Anchor 2L milk pricing/history rows for at least PAK'nSAVE/New World/Woolworths Papakura when current Grocer data includes them.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!