Prices for 5 UK supermarkets (Tesco, Sainsbury's, Asda, Aldi, Morrisons) in one schema: price, was-price, Clubcard/Nectar/More Card price, EAN where published, promotion, stock, scrapedAt. Use it to compare a product or basket across shops. Pay per product returned plus $0.005 a run.
Announced change: matchKey (from 4 November 2026 at the earliest)
Announced 5 October 2026. These changes ship in the same build as the default-retailers change announced below, no earlier than 4 November 2026 (that build: no earlier than 7 November 2026).
matchKey writes unit words in product names in the singular, so "2 Pints" and "2 Pint" give the same key (pints, litres, grams and kilograms).
matchKey writes pint-sized volumes in pints: a pack within 0.5% of a whole number of UK pints (568.26 ml each) is written as that number of pints, <n>pt. So "2.272L", "2.27L", "2272ml" and "4 pint" all become 4pt; other volumes and weights stay in ml and g as today.
For one release, every row also carries matchKeyV1, the key exactly as it is built today, so existing joins keep working while you move to the new key. matchKeyV1 is removed in the release after that, announced here with its date.
UK edition only. Apart from matchKey and the temporary matchKeyV1, no field, price or event changes.
Announced change: default retailers (from 7 November 2026 at the earliest)
Announced 8 October 2026. This change ships in the same build as the matchKey change above, no earlier than 7 November 2026. Morrisons stays opt-in until then.
A run that does not set retailers searches all five retailers (Tesco, Sainsbury's, Asda, Aldi and Morrisons) instead of Aldi and Sainsbury's.
maxItems is shared equally between the shops that run, so a default call (100 items) returns up to 20 rows from each of five shops instead of up to 50 from each of two. It is still at most 100 rows, so what it costs is unchanged ($0.255 on the Free plan: $0.005 to start and $0.0025 for each of up to 100 products).
A run with maxItems set to 0 (no limit) is different: it returns, and charges for, rows from all five shops instead of two.
To keep today's behaviour, set retailers to ["aldi", "sainsburys"]. Runs that name their retailers are not affected.
UK edition only. No field, price or event changes.
Tesco category URLs (16 October 2026)
Tesco moved Fresh Food, Bakery, Frozen Food, Treats & Snacks, Food Cupboard, Drinks, Household and Kiosk under a new top-level category, Groceries & Essentials, so category URLs starting with those names stopped working. They work again: an old URL such as /shop/en-GB/browse/fresh-food/all is resolved under its new parent (the run's warnings say so), and the new form /shop/en-GB/browse/groceries-and-essentials/fresh-food/all works too. A category URL deeper than a shelf (super department, department, aisle, shelf) is now refused for that URL, with zero rows and nothing charged, instead of extra levels being ignored; Tesco's old shelf pages such as Whole Milk no longer exist. No price, event or field changes.
0.2 — 2026-09
Tesco adapter: search, category and product modes, reading Tesco's own product API.
ean present on every Tesco row seen (Tesco's API returns a GTIN for every product).
Clubcard prices: single-item Clubcard prices in loyaltyPrice; multibuy and meal-deal Clubcard offers in promotionText.
tesco added to the retailers input.
Pricing unchanged: same per-run start fee and per-product price, no new events.
Sainsbury's category mode (25 Sep 2026): pagination now follows the site's redirect when a category URL has moved, and a category whose page says "page cannot be found" is reported as not found instead of a parse failure (issue #31).
outputDataset input (added 24 Sep 2026): also append every row to a named dataset in your account, so repeated runs build a history in one place. Rows are still charged once.
datasetName input (25 Sep 2026, build 0.2.8): at the end of a successful run, name the run's own dataset with {isoWeek}, {date} and {retailer} placeholders, so a scheduled task's weekly datasets are named and never expire; a taken name, an empty run or a run that stopped early leaves the dataset unnamed with a warning, so a retry can take the name.
Build 0.2.7 (25 Sep 2026) was made from an unreviewed working tree and is superseded by 0.2.8; do not pin it.
categoryUrls accepts up to 250 URLs per run (was 50), so a retailer's whole category list fits in one category-mode run; the per-retailer request budget still bounds the work.
Platform migrations (27 Sep 2026): when Apify moves a run to another server (or a run is resurrected), the Actor reads back what the run's dataset already holds and skips those products, so rows stored before the move are no longer pushed or charged again (issue #41). If that read fails, the run stops rather than risk charging twice. RUN_SUMMARY gains resumedRows.
Sainsbury's category mode (29 Sep 2026): an aisle is paged by its real page size, sponsored grid tiles included, so the Actor no longer asks for a page past the end and reports it as a failure (issue #38). When the last page does come back empty, the aisle simply ends there; RUN_SUMMARY counts that in pagesShort, which is left out when 0. Sponsored products shown in the grid are now returned, once each (issue #39). A department URL (a hub page with no listing) now says to use one of its aisle URLs, and only Sainsbury's real "page not found" page is reported as not found (issue #36).
Morrisons fix (10 Oct 2026): Morrisons began adding an analytics query to its product links on listing pages, which made every Morrisons row fail to build its url, so a Morrisons search or category run returned no Morrisons rows (nothing is charged for a row that is not returned). The adapter now reads past the query; the product url in a row is unchanged and carries no query.
Run summary (10 Oct 2026): a retailer whose products mostly fail to be read (half or more of what it listed) is now marked status: "degraded" in RUN_SUMMARY instead of ok, with a reason, and the run's final status message carries a warning naming it. Rows, prices and events are unchanged.
Free daily dataset: the README now links the dataset's plain read-by-id URL, which needs no account or token and does not expire. The earlier signed link stops working on 8 Oct 2026.
Input form wording (8 Oct 2026): every input field now has a plain description, an example value and, where a field belongs to one mode, a note saying which, so AI agents that use the Actor through MCP fill the form correctly. The maxItems description now says the limit is split equally between the shops that run. No field, default, limit or choice changed.
Morrisons search (8 Oct 2026): sponsored products now come after the search's own results instead of first, so a small maxItems is no longer spent on ads that do not match the query. They are still returned, once each; category and catalogue order is unchanged.
Morrisons (5 Oct 2026, opt-in): morrisons added to the retailers input, but not to the default selection. Modes search, category, product and catalogue. Morrisons publishes no barcode, so rows carry an empty ean and join on matchKey; More Card prices go in loyaltyPrice; catchweight items are priced for their typical weight with the per-kilo figure in unitPrice; the standing "Unbeatable Price" badge is not a promotion and is not in promotionText. A listing page gives only its first 50 products, so category and search mode can miss products in larger categories; RUN_SUMMARY gains listingShortfall (left out when 0) to count them.
Catalogue mode (5 Oct 2026): mode accepts catalogue, which reads a retailer's whole grocery range with no queries or URLs. Morrisons only for now; other selected retailers are skipped with the reason catalogue mode is not available for <retailer> yet. A check run on 29 Sep 2026 returned 26,452 priced grocery rows in 3 hours 41 minutes with no blocks and no proxy; set a 4-hour run timeout, and use maxItems to cap rows and cost. RUN_SUMMARY gains tailFetched, tailNotFound and tailNonGrocery (each left out when 0).
Pricing unchanged for Morrisons and catalogue mode: same per-run start fee and per-product price, no new events.
POST /basket on the Actor's Standby URL (October 2026): prices a basket of up to 200 items across Tesco, Sainsbury's, Asda and Aldi in one HTTP call; items match by ean, retailerProductId, or name with an optional packSize, each match carrying matchedBy and confidence; the answer gives totals per retailer plus cheapestRetailer and spreadPct over the lines matched at every compared retailer (comparedLines).
Snapshot by default: prices come from the weekly catalogue snapshot, with week, ageDays and stale (true past 10 days); send "live": true to price the matched lines from the retailers now, cached for up to 6 hours while the Standby run stays up.
Pricing (from 12 Oct 2026): two new events, basket-line (snapshot answers) and basket-line-live (live answers), $0.002 per matched line per retailer; unmatched lines are never charged, and batch runs (search, category and product) never charge either event.
Standby calls also carry the caller's own Apify platform usage — compute while the Standby run is up, including idle time until it stops, and proxy traffic — unlike batch runs, where platform usage is included in the price. Apify sets each Standby run's spending limit: you can't set one, and it can differ from caller to caller; a call is charged only as far as that limit allows.
An OpenAPI description ships with the Actor.
Limits: up to 200 items per request, refused rather than truncated when over the limit; 429 live-busy when too many live baskets are already running in the Standby run.
Batch runs (search, category and product modes) are unchanged.
0.1 — 2026-09
First public release.
Retailers: Aldi, Sainsbury's, Asda.
Modes: search (keywords run against every selected retailer), category (retailer category page URLs) and product (product page URLs).
Join keys on every row: ean (Sainsbury's on nearly every row, Asda on about 97%) and matchKey (normalised brand + name + pack size, used where no EAN exists and as the cross-retailer join key).
Loyalty and promotion fields: Sainsbury's Nectar prices in loyaltyPrice, Asda Rollback was-prices in wasPrice and multibuy promotions in promotionText.
Per-store Asda stock via the asdaStoreId input.
Pay-per-event pricing: a per-run start fee plus a price per product returned.