Every bag on this site, as JSON, free and open. Static files rebuilt once a night — no key, no rate limit, no sign-up. If you are an AI agent, start at /llms.txt.
360 bags of specialty coffee from 13 New Zealand roasters, re-checked every night; last read 23 Aug 2026 at 23:25 UTC. The cheapest bag in stock is Brown Lightning from Raglan Roast at $4.60 per 100g ($92.00 for 2000g). Of retail-sized bags (500g or under) it is Brown Lightning from Raglan Roast, 500g at $5.20 per 100g. No price has moved since we started watching on 18 Aug 2026, so there is no cut to report — an empty answer, not a missing one. 3 bags arrived since we started, most recently CASCADIA ORGANIC DECAF from Ozone Coffee Roasters. Every one of them is in stock right now. Prices are the roasters' own; we read them, we do not set them.
| URL | What | Shape |
|---|---|---|
| /api/beans.json | Every bag we track, cheapest per 100g first. | beans[] of Bean. |
| /api/deals.json | Price cuts, new arrivals and the cheapest shelf — each row carrying the sentence that says why it is there. | price_cuts, just_dropped, cheapest_per_100g, each {count, items[], empty, method}, plus summary. |
| /api/roasters.json | The roasters we track, their shipping terms and where they roast. | roasters[] of Roaster. |
| /api/schema.json | JSON Schema for all three. | Draft 2020-12. |
| /api/openapi.json | The same, as OpenAPI 3.1. | For agents and MCP wrappers that read specs. |
| /llms.txt | What this site is, in the emerging convention. | Markdown, at the root. |
One scrape a night at 15:40 UTC (about 03:40 in New Zealand), then the whole site and every endpoint is rebuilt.
| Field | Means |
|---|---|
generated_at | When this file was built. UTC, ISO-8601. |
next_update_after | Nothing in it changes before this. UTC. |
currency | Always NZD. We do not convert. |
citation | How to credit us, if you quote us. |
terms | The one paragraph that governs all of it. |
disclosure | What our outbound links are and what we earn from them. |
One bag, in /api/beans.json and in every list in /api/deals.json.
| Field | Type | Means |
|---|---|---|
id | string | Stable id, `<roaster-slug>:<product>-<weight>`. Survives price changes. |
roaster | string | The roaster's name, as they write it. |
roaster_slug | string | Their key here, and the last part of their page URL. |
roaster_url | string | Short link to their shop's home page, on our domain. |
name | string | The coffee's name, as the roaster writes it. |
url | string | Where to send a reader: a short link on our domain that redirects to the roaster's product page and counts the click. |
shop_url | string | The roaster's own product page, unredirected — so you can see exactly where `url` lands. |
page | string | Our page for this roaster, where this bag is listed. |
price_nzd | number|null | Shelf price in NZD. Null when we could not read one. |
weight_g | integer|null | Bag weight in grams. Null when the roaster does not state it. |
price_per_100g | number|null | Shelf price per 100g. Null when price or weight is null. |
in_stock | boolean | Whether the roaster showed it as available at the last check. |
origin | string|null | Country or region, where the roaster names one. |
roast_style | string|null | `espresso`, `filter` or `omni`, from the roaster's own words. |
roast_level | string|null | Light/medium/dark, only where the roaster states it. |
flavour_notes | array<string> | The roaster's own tasting notes, verbatim. |
flavours | array<string> | Those notes mapped onto our 18-key flavour vocabulary. |
shipping | object | What it costs to get this bag to a door — see `shipping` below. |
first_seen | string | ISO-8601 UTC. When WE first saw it, not when the roaster listed it. |
last_seen | string | ISO-8601 UTC. The last check that found it. |
last_price_change | string|null | ISO-8601 UTC of the last price MOVE. Null means it has never moved since we started watching — not that we have no history. |
previous_price_nzd | number|null | What it cost before that move. Null if it has never moved. |
The `shipping` object on a Bean: what it costs to get THAT bag to a door.
| Field | Type | Means |
|---|---|---|
published | boolean | Whether the roaster publishes a rate outside checkout. Four of thirteen do not. |
free_over_nzd | number|null | Order total they ship free at, where they publish one. |
flat_nzd | number|null | Their flat rate below that, where they publish one. |
flat_varies | boolean | True where the rate depends on the island; `flat_nzd` is then the cheaper. |
ships_free | boolean | Whether this single bag already clears their free-shipping threshold. |
delivered_price_nzd | number|null | This bag at the door: price plus shipping. |
delivered_price_exact | boolean | False when `delivered_price_nzd` is a FLOOR — the roaster publishes no rate, so the true price is this or more. Never treat a floor as a price. |
source | string|null | The roaster page a human read the terms off. |
note | string|null | Their terms in their own words, where they publish any. |
last_verified | string|null | The day a human last read that page. Nothing re-checks it. |
One roaster, in /api/roasters.json.
| Field | Type | Means |
|---|---|---|
slug | string | Their key here. |
name | string | Their name. |
url | string | Short link to their shop, on our domain (redirects, counts). |
shop_url | string | Their own URL, unredirected. |
page | string | Our page for them. |
bean_count | integer | Bags of theirs we are tracking. One per size, so a coffee sold in three sizes counts three. |
coffee_count | integer | Distinct coffees behind those bags, sizes collapsed. Never more than `bean_count`. |
cheapest_per_100g | number|null | Their cheapest bag per 100g. |
last_checked | string|null | ISO-8601 UTC of our last successful read of their shop. |
ok | boolean | Whether that last read succeeded. |
stale | boolean | True when we have not managed to check them for 3 days. |
city | string|null | Where they roast, where we have confirmed it. |
place | string|null | Suburb and city, as one line. |
lat | number|null | Latitude, for distance sorting. Null where we could not confirm one. |
lng | number|null | Longitude. |
location_source | string|null | The page a human read the address off. |
shipping | object | Their published shipping terms — see `terms`. |
The `shipping` object on a Roaster: what they publish, not what a bag costs.
| Field | Type | Means |
|---|---|---|
published | boolean | Whether they publish a rate outside checkout. Four of thirteen do not. |
free_over_nzd | number|null | Order total they ship free at, where they publish one. |
flat_nzd | number|null | Their flat rate below that, where they publish one. |
flat_varies | boolean | True where the rate depends on the island; `flat_nzd` is then the cheaper. |
source | string|null | The roaster page a human read the terms off. |
note | string|null | Their terms in their own words, where they publish any. |
last_verified | string|null | The day a human last read that page. Nothing re-checks it. |
Prices, weights and stock are the roasters' own, read from their public shop pages and re-checked every night. We do not set them, mark them up, or estimate them: a figure we could not read is null. Free to quote with attribution and a link.
Cite as: Coffee Addict — every specialty coffee bean on sale in New Zealand, priced per 100g and re-checked every night. https://coffeeaddict.nz