The data.

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.

2994 bags of specialty coffee from 157 New Zealand roasters, re-checked every night; last read 08 Oct 2026 at 13:18 UTC. The cheapest bag in stock is Apollo from TOB Coffee at $4.00 per 100g ($40.00 for 1000g). Of retail-sized bags (500g or under) it is Roaster's Pick from Able Coffee Collaborative, 500g at $4.20 per 100g. The biggest price cut is Brazil Ibiraci Peaberry from Cascade Coffee Co., down 31.0% from $29.00 to $20.00. 99 bags arrived since we started, most recently KINGSLAND BLEND from Roasted Addiqtion. 114 of the 2994 are out of stock right now. Prices are the roasters' own; we read them, we do not set them.

ENDPOINTS
URLWhatShape
/api/beans.jsonEvery bag we track, cheapest per 100g first.beans[] of Bean.
/api/deals.jsonPrice 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.jsonEvery roaster we have found in New Zealand: the ones we price-track with their shipping terms and where they roast, and the ones we only know of, with the reason.roasters[] of Roaster (tracked: true), listed[] of ListedRoaster (tracked: false).
/api/subscriptions.jsonEvery roaster's subscription, priced per delivery at each bag size, with the delivery charge kept as its own number.sizes[] of {key, label, count, method, freight, items[] of Subscription}, plus summary and default_size.
/api/schema.jsonJSON Schema for all four.Draft 2020-12.
/api/openapi.jsonThe same, as OpenAPI 3.1.A spec anyone is free to wrap. We also host the MCP server below, so wrapping it is a choice rather than the only way in.
/llms.txtWhat this site is, in the emerging convention.Markdown, at the root.
POST /mcpOur own MCP server: the same data as three tools — search_beans, best_deals, roaster_info. No key, no account, read-only.JSON-RPC over streamable HTTP. Card at /.well-known/mcp/server-card.json.

One scrape a night at 12:30 UTC (00:30 NZST, 01:30 NZDT), then the whole site and every endpoint is rebuilt.

THE MCP SERVER

We host one at https://coffeeaddict.nz/mcp — the same data as 3 read-only tools (search_beans, best_deals, roaster_info), over streamable HTTP. In Claude: Settings › Connectors › Add custom connector, and paste https://coffeeaddict.nz/mcp. Other clients take the same URL as a streamable-HTTP server. There is no key and no account to make, and every tool is read-only.

Server card: https://coffeeaddict.nz/.well-known/mcp/server-card.json

ON EVERY PAYLOAD
FieldMeans
generated_atWhen this file was built. UTC, ISO-8601.
next_update_afterNothing in it changes before this. UTC.
currencyAlways NZD. We do not convert.
citationHow to credit us, if you quote us.
termsThe one paragraph that governs all of it.
disclosureWhat our outbound links are and what we earn from them.
BEAN

One bag, in /api/beans.json and in every list in /api/deals.json.

FieldTypeMeans
idstringStable id, `<roaster-slug>:<product>-<weight>`. Survives price changes.
roasterstringThe roaster's name, as they write it.
roaster_slugstringTheir key here, and the last part of their page URL.
roaster_urlstringShort link to their shop's home page, on our domain.
namestringThe coffee's name, as the roaster writes it.
urlstringWhere to send a reader: a short link on our domain that redirects to the roaster's product page and counts the click.
shop_urlstringThe roaster's own product page, unredirected — so you can see exactly where `url` lands.
pagestringOur page for this roaster, where this bag is listed.
price_nzdnumber|nullShelf price in NZD. Null when we could not read one.
weight_ginteger|nullBag weight in grams. Null when the roaster does not state it.
price_per_100gnumber|nullShelf price per 100g. Null when price or weight is null.
in_stockbooleanWhether the roaster showed it as available at the last check.
originstring|nullCountry or region, where the roaster names one.
roast_stylestring|null`espresso`, `filter` or `omni`, from the roaster's own words.
roast_levelstring|nullLight/medium/dark, only where the roaster states it.
flavour_notesarray<string>The roaster's own tasting notes, verbatim.
flavoursarray<string>Those notes mapped onto our 18-key flavour vocabulary.
shippingobjectWhat it costs to get this bag to a door — see `shipping` below.
first_seenstringISO-8601 UTC. When WE first saw it, not when the roaster listed it.
last_seenstringISO-8601 UTC. The last check that found it.
last_price_changestring|nullISO-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_nzdnumber|nullWhat it cost before that move. Null if it has never moved.
SHIPPING

The `shipping` object on a Bean: what it costs to get THAT bag to a door.

FieldTypeMeans
publishedbooleanWhether the roaster publishes a rate outside checkout. Several do not.
free_over_nzdnumber|nullOrder total they ship free at, where they publish one.
free_over_gnumber|nullOrder WEIGHT they ship free at, for the roasters whose threshold is a weight rather than a basket total. Either threshold alone is enough.
flat_nzdnumber|nullTheir flat rate below that, where they publish one.
flat_min_gnumber|nullMinimum order weight for `flat_nzd` to apply, where they set one. Below it their rate is unpublished — do not apply the flat rate to a lighter order.
flat_variesbooleanTrue where the rate depends on the island; `flat_nzd` is then the cheaper.
ships_freebooleanWhether this single bag already clears a free-shipping threshold of either kind — the basket total or the weight.
delivered_price_nzdnumber|nullThis bag at the door: price plus shipping. Null when the roaster publishes no rate this bag can use (unmet free-shipping threshold, under-threshold rate unpublished) — the door price is unknowable then, and null cannot be misquoted the way a 'floor' price was.
delivered_price_exactbooleanFalse when `delivered_price_nzd` is a FLOOR — their rate varies by island and we quote its cheapest. Never treat a floor as a price.
sourcestring|nullThe roaster page a human read the terms off.
notestring|nullTheir terms in their own words, where they publish any.
last_verifiedstring|nullThe day a human last read that page. Nothing re-checks it.
ROASTER

One roaster, in /api/roasters.json.

FieldTypeMeans
slugstringTheir key here.
namestringTheir name.
urlstringShort link to their shop, on our domain (redirects, counts).
shop_urlstringTheir own URL, unredirected.
pagestringOur page for them.
bean_countintegerBags of theirs we are tracking. One per size, so a coffee sold in three sizes counts three.
coffee_countintegerDistinct coffees behind those bags, sizes collapsed. Never more than `bean_count`.
cheapest_per_100gnumber|nullTheir cheapest bag per 100g.
last_checkedstring|nullISO-8601 UTC of our last successful read of their shop.
okbooleanWhether that last read succeeded.
stalebooleanTrue when we have not managed to check them for 3 days.
citystring|nullWhere they roast, where we have confirmed it.
placestring|nullSuburb and city, as one line.
latnumber|nullLatitude, for distance sorting. Null where we could not confirm one.
lngnumber|nullLongitude.
location_sourcestring|nullThe page a human read the address off.
shippingobjectTheir published shipping terms — see `terms`.
trackedbooleanAlways true in `roasters`: we read this roaster's shop every night. The `listed` array is the roasters we know of and do not price-track, and every row there is false.
TERMS

The `shipping` object on a Roaster: what they publish, not what a bag costs.

FieldTypeMeans
publishedbooleanWhether they publish a rate outside checkout. Several roasters do not.
free_over_nzdnumber|nullOrder total they ship free at, where they publish one.
free_over_gnumber|nullOrder WEIGHT they ship free at, for the roasters whose threshold is a weight rather than a basket total. Either threshold alone is enough.
flat_nzdnumber|nullTheir flat rate below that, where they publish one.
flat_min_gnumber|nullMinimum order weight for `flat_nzd` to apply, where they set one. Below it their rate is unpublished — do not apply the flat rate to a lighter order.
flat_variesbooleanTrue where the rate depends on the island; `flat_nzd` is then the cheaper.
sourcestring|nullThe roaster page a human read the terms off.
notestring|nullTheir terms in their own words, where they publish any.
last_verifiedstring|nullThe day a human last read that page. Nothing re-checks it.
LISTEDROASTER

One roaster in the `listed` array of /api/roasters.json: we know they exist, we do not read their prices, and `reason` says why. The directory at /roasters/ is the same two tiers as a page.

FieldTypeMeans
namestringTheir name, as our sources write it.
urlstring|nullShort link to their own site, on our domain (redirects, counts). Null where we have found no website for them.
websitestring|nullTheir own URL, unredirected. Null where we have none.
townstring|nullThe town or suburb we have them in.
regionstring|nullTheir New Zealand region.
trackedbooleanAlways false here. See `reason` for why.
reasonstringWhy we do not price-track them, in plain words: no website we could find, their site did not answer our check, their robots.txt asks us not to read it, their shop is password-protected, or simply not yet.
sells_onlineboolean|nullWhether they appear to sell beans online at all. Null where we could not tell.
platformstring|nullWhat their shop is built on, where we could tell (`shopify`, `woocommerce`, `squarespace`, …). This is how we decide who is cheap to onboard next.
SUBSCRIPTION

One roaster's cheapest plan at one bag size, in /api/subscriptions.json.

FieldTypeMeans
roaster_slugstringTheir key here, and the last part of their page URL.
roasterstringThe roaster's name, as they write it.
urlstringWhere to send a reader: a short link on our domain that redirects to the roaster's subscription listing and counts the click.
shop_urlstringThe roaster's own listing, unredirected — so you can see exactly where `url` lands.
pagestringOur page for this roaster.
bag_size_gintegerThe bag this row priced. Rows inside one size view can differ: five roasters sell the small bag as 200g and eight sell it as 250g, and neither is restated as the other.
bagstringThat weight, written out.
price_nzdnumberCoffee only, per delivery — the cheapest plan the roaster publishes at this size.
price_frombooleanTrue when their other coffees at this size cost more, so `price_nzd` is a floor rather than the price.
delivery_nzdnumber|nullWhat freight costs ON THIS ORDER, not their general rate: 0 where the order already clears their free-shipping threshold. Null where they publish no rate that applies to it.
delivery_publishedbooleanWhether that figure came from a published rate.
delivery_rulestringThe threshold or flat rate that decided it, in words.
delivered_nzdnumberCoffee plus delivery: what a delivery actually costs.
delivered_exactbooleanFalse when `delivered_nzd` is a FLOOR — the roaster publishes no rate below their threshold, or their other coffees cost more. Never treat a floor as a price.
delivered_per_100gnumberThat total over the grams in the bag. This is what the comparison is ranked on.
discount_percentnumber|nullMeasured from the two published prices, never from an advertised percentage. 0 means the roaster sells the plan at shelf price — a real finding. Null means no comparable one-off price existed. The two are never merged.
every_daysarray<integer>Every delivery cadence offered at this size, in days. The per-delivery price does not move with it.
kindstring`subscribe_and_save` (an ordinary bag on repeat, published only at /products/<handle>.js) or `standalone` (the roaster's own subscription listing).
choose_coffeebooleanWhether the plan lets the subscriber pick the coffee.
plan_countintegerHow many plans the roaster publishes at this size.
commitmentstring`prepaid` (you buy the whole term in advance), `minimum` (rolling, but a stated number of deliveries must arrive before you can cancel) or `rolling`. **`rolling` does NOT mean cancel anytime** — it means the roaster publishes nothing about a minimum term, and almost none of them do outside the checkout.
term_total_nzdnumber|nullWhat a prepaid plan charges up front, for `deliveries` deliveries. Null on any other plan. This is the number that decides whether the offer is open to a reader at all, which is why prepaid plans are listed separately from the ranking rather than inside it.
term_deliveriesinteger|nullHow many deliveries that cheque covers, or the minimum a `minimum` plan ties you to. Null on a rolling plan.
housebooleanTrue only for a product of ours. False on every row today, and a house product is never ranked among the roasters' — see /subscriptions/.
WHAT WE REFUSE TO CLAIM
USING 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