# AU Grocery Prices - Coles, Woolworths, ALDI & IGA (`toptock/au-grocery-prices`) Actor

Scrape Australian grocery prices from Coles, Woolworths, ALDI and IGA in one run. Get price, was-price, half-price and multi-buy specials, unit price, stock and category - or match the same product across all four chains on one row and see which is cheapest.

- **URL**: https://apify.com/toptock/au-grocery-prices.md
- **Developed by:** [Elijah Friederick](https://apify.com/toptock) (community)
- **Categories:** E-commerce, Open source
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 product results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## AU Grocery Prices — Coles, Woolworths, ALDI & IGA

Australian grocery prices from **all four major chains in one run**. Search a keyword, get back what Coles, Woolworths, ALDI and IGA each sell, in the same columns — price, was-price, specials, unit price, availability and a link.

Ask for `milk` and you get every chain's answer side by side. Switch to comparison mode and the same product from different chains lands **on one row** with the cheapest chain named.

### Why this Actor

Every other Australian grocery scraper covers one chain. Comparing prices meant running three or four different Actors, each with different field names, and matching the products yourself.

This one does all four, normalises them into one shape, and can do the matching for you.

**Measured reliability:** 5 out of 5 consecutive test runs returned all four chains. For context, the two most-used competing Actors currently sit at 40% and 57% successful runs over the last 30 days.

### What you get

| Field | Example |
|---|---|
| `chain`, `chainName` | `coles` · Coles |
| `name`, `brand`, `size` | Arnott's Tim Tam Original Chocolate Biscuits · Arnott's · 200g |
| `sizeNormalised` | `200g` — same size written the same way for every chain |
| `price`, `wasPrice` | 4.49 · 6.00 |
| `isOnSpecial`, `specialType` | true · `half_price` / `multi_buy` / `25% off` / `special` |
| `saveAmount`, `savePercent` | 1.51 · 25 |
| `multiBuy` | `{"quantity": 2, "totalPrice": 8, "unitPriceText": "$2.00/100G"}` |
| `specialEndsAt` | `2026-09-29T00:00:00Z` (IGA only — the other chains don't publish it) |
| `unitPrice`, `unitPriceText` | 2.25 · `$2.25 per 100 g` |
| `available`, `category` | true · Biscuits |
| `barcode` | `9310199012717` (Woolworths only) |
| `url`, `imageUrl` | link to the product page and its photo |

### Input

```json
{
  "keywords": ["full cream milk 2l", "weet-bix", "tim tam"],
  "chains": ["coles", "woolworths", "aldi", "iga"],
  "maxResultsPerKeyword": 25,
  "output": "comparison",
  "onSpecialOnly": false
}
```

| Option | Meaning |
|---|---|
| `keywords` | Products to search in every selected chain. Required. |
| `chains` | Any of `coles`, `woolworths`, `aldi`, `iga`. Defaults to all four. |
| `maxResultsPerKeyword` | Cap per keyword **per chain**. Default 25, max 500. |
| `output` | `products` (one product per row) or `comparison` (one matched product per row). |
| `onSpecialOnly` | Only return products currently on special. |
| `igaStoreId` | IGA prices vary by store. Defaults to store 32600 (Erskine Park, NSW). |

### Output — comparison mode

Real output from a test run:

```json
{
  "type": "comparison",
  "name": "Arnott's Tim Tam Original Chocolate Biscuits",
  "sizeNormalised": "200g",
  "chains": ["aldi", "coles", "iga", "woolworths"],
  "cheapestChain": "aldi",
  "cheapestPrice": 4.49,
  "priceSpread": 1.51,
  "products": {
    "coles":      { "price": 6.0,  "name": "Arnott's Tim Tam Chocolate Biscuits Original", "...": "..." },
    "woolworths": { "price": 6.0,  "name": "Arnott's Tim Tam Original Chocolate Biscuits 200g", "...": "..." },
    "aldi":       { "price": 4.49, "name": "ARNOTT'S Tim Tam Original Chocolate Biscuits 200g", "...": "..." },
    "iga":        { "price": 4.5,  "name": "Arnott's Tim Tam Chocolate Original", "...": "..." }
  }
}
```

### Food recall alerts (FSANZ)

Set `output` to `recalls` to get **official Australian food recalls** from Food Standards Australia New Zealand, parsed into clean fields — and, if you want, checked against what the four supermarkets are selling right now.

```json
{
  "output": "recalls",
  "recallDays": 90,
  "keywords": ["ham", "peanut butter"],
  "recallStates": ["NSW"],
  "recallAllergens": ["peanut"],
  "findAlternatives": true
}
```

| Option | Meaning |
|---|---|
| `keywords` | Optional watch list. Only recalls whose product or company contains every word of a keyword. Leave empty for all recalls. |
| `recallDays` | How far back to look. Default 90. FSANZ history starts late 2023. |
| `recallStates` | Only recalls affecting these states. Nationwide recalls always match. |
| `recallHazards` | `allergen`, `listeria`, `salmonella`, `e-coli`, `microbial`, `foreign-matter`, `chemical`, `other`. |
| `recallAllergens` | Only allergen recalls involving e.g. `peanut`, `milk`, `sesame`. |
| `meatOnly` | Only meat, poultry and smallgoods. |
| `findAlternatives` | Search the selected chains for the same kind of product from **other brands**, cheapest unit price first. |
| `organicOnly` | Only suggest alternatives with "organic" in the name. |

Each row:

```json
{
  "type": "recall",
  "product": "JC Seafood Sweet Chilli & Lime Salmon Portions",
  "size": "275g",
  "company": "KB Seafood Company Pty Ltd",
  "publishedAt": "2026-09-10",
  "states": ["WA"],
  "retailers": ["woolworths"],
  "dateMarking": "Best Before 18.09.26",
  "hazardType": "allergen",
  "allergens": ["soy"],
  "problem": "The presence of an undeclared allergen (soy).",
  "whatToDo": "Consumers who have a soy allergy or intolerance should not consume this product...",
  "noticePdfUrls": ["https://www.foodstandards.gov.au/sites/default/files/..."],
  "url": "https://www.foodstandards.gov.au/food-recalls/recall-alert/jc-seafood-sweet-chilli-lime-salmon-portions-275g",
  "alternativesQuery": "salmon portions",
  "possibleShelfListings": [ { "chain": "woolworths", "name": "...", "url": "..." } ],
  "alternatives": [ { "chain": "coles", "name": "...", "price": 9.0, "unitPriceText": "...", "url": "..." } ],
  "source": "Food Standards Australia New Zealand",
  "licence": "CC BY 4.0 - https://creativecommons.org/licenses/by/4.0/"
}
```

**How to read the shelf check**

- `possibleShelfListings` are products from the **recalled brand** that look like the recalled item. A recall normally covers specific batches or dates only — check `dateMarking` against the pack before acting. Being listed online does not mean the listing is affected.
- `alternatives` are products of the same kind from **other brands**. They are not under *this* recall; that is all it means. They are not certified safe, and "organic" is a preference filter, not a safety claim — organic products are recalled too.
- `alternativesQuery` shows the search phrase used, so you can see why an alternative was suggested.

Recalls are fetched fresh and cached between runs, so repeat runs only read new notices.

**Always follow the official notice.** This Actor restates FSANZ information for convenience; the linked FSANZ page and the company's recall notice are authoritative. If you are unwell, seek medical advice.

### How product matching works

Two products are only treated as the same item when **all** of these agree:

1. **Normalised size** — 2L and 2000ml are the same; 175g and 200g are not.
2. **Brand** — with one deliberate exception: house brands are comparable to each other, so Coles Full Cream Milk, Woolworths Full Cream Milk and ALDI's Farmdale line up on one row. Community Co, Black & Gold, Essentials and Macro are treated the same way.
3. **Product variant** — flavour, dietary and physical variants must match exactly. Tim Tam Original never matches Tim Tam Chewy Caramel. Lactose-free never matches regular. 3-ply never matches 4-ply. Free-range eggs never match cage eggs.

The matcher is deliberately conservative. It would rather miss a match than invent a wrong one, so a price comparison it gives you is one you can trust.

### Example uses

- **Weekly shop** — price your regular list at all four chains and see which is cheapest this week.
- **Specials watch** — run with `onSpecialOnly` on a schedule to catch half-price items as they land.
- **Track a price over time** — run daily and keep the dataset; `wasPrice` and `specialEndsAt` tell you when a discount started and when it ends.
- **Feed an AI assistant** — one call returns the whole Australian grocery market for a keyword, already normalised.

### Notes and limits

- Prices are the **online** prices published by each chain, which can differ from the shelf price in a particular store.
- IGA is a group of independently-owned stores, so its prices are per-store. Change `igaStoreId` to price a different one.
- ALDI's online catalogue is smaller than its in-store range.
- Only Woolworths publishes barcodes, so matching uses brand, size and product name rather than barcodes.
- Only IGA publishes an end date for its specials.
- No account or login is needed for any chain. This Actor only reads prices that are already public on each chain's website.

### Data sources and attribution

- **Prices and products** are read from the public websites of Coles, Woolworths, ALDI and IGA. Product images are linked, never copied or re-hosted. This Actor is not affiliated with or endorsed by any retailer; brand names are used only to identify products.
- **Food recall information** is sourced from Food Standards Australia New Zealand (FSANZ), licensed under Creative Commons Attribution 4.0 (CC BY 4.0). FSANZ does not endorse this Actor or its use of the information. Company recall notices (PDFs) are third-party documents and are linked, not reproduced.
- Information is provided as-is for convenience and may be incomplete or out of date. Always check the official source before relying on it for health or safety decisions.

# Actor input Schema

## `keywords` (type: `array`):

Products to search in every selected chain, e.g. <b>milk</b>, <b>weet-bix</b>, <b>toilet paper</b>. Each keyword is searched in all selected supermarkets and returned in one dataset. In <b>recall alerts</b> mode these are optional and act as a watch list: only recalls whose product or company contains every word of a keyword are returned (e.g. <b>ham</b>, <b>peanut butter</b>).

## `chains` (type: `array`):

Which chains to scrape. Default: all four.

## `maxResultsPerKeyword` (type: `integer`):

Cap on products returned for each keyword in each chain. Default 30, max 500.

## `output` (type: `string`):

<b>One product per row</b> returns every product separately. <b>One comparison per row</b> matches the same product across chains (same size, compatible brand - house brands like Coles/Essentials/Farmdale/Black & Gold are treated as comparable) and names the cheapest chain. <b>Food recall alerts</b> returns official Australian food recalls from Food Standards Australia New Zealand, with hazard, allergens, states and retailers - optionally cross-checked against the four chains' shelves.

## `onSpecialOnly` (type: `boolean`):

Return only products currently on special (half-price, multi-buy, percent-off, TPR).

## `igaStoreId` (type: `string`):

IGA prices are store-specific. Default is store 32600 (Ashcroft's IGA Erskine Park, NSW). Find other IDs via igashop.com.au.

## `recallDays` (type: `integer`):

Return recalls published in the last N days. FSANZ history goes back to late 2023.

## `recallStates` (type: `array`):

Only recalls affecting these states. Nationwide recalls always match. Empty = all.

## `recallHazards` (type: `array`):

Only these kinds of recall. Empty = all.

## `recallAllergens` (type: `array`):

Only allergen recalls involving these allergens - e.g. pick peanut for a peanut-allergy household. Empty = all.

## `meatOnly` (type: `boolean`):

Only recalls of meat, poultry and smallgoods.

## `findAlternatives` (type: `boolean`):

For each recall, search the selected supermarkets for the same kind of product from other brands (cheapest unit price first), and flag listings that may be the recalled product itself so you can check batch and date codes. Adds a few seconds per recall.

## `organicOnly` (type: `boolean`):

Only suggest alternatives with 'organic' in the name. This is a preference filter - organic products are recalled too, so it is not a safety guarantee.

## Actor input object example

```json
{
  "keywords": [
    "milk",
    "bread",
    "eggs"
  ],
  "chains": [
    "coles",
    "woolworths",
    "aldi",
    "iga"
  ],
  "maxResultsPerKeyword": 30,
  "output": "products",
  "onSpecialOnly": false,
  "igaStoreId": "32600",
  "recallDays": 90,
  "meatOnly": false,
  "findAlternatives": false,
  "organicOnly": false
}
```

# Actor output Schema

## `results` (type: `string`):

All scraped rows. In 'products' mode each row is one product from one chain (price, was-price, special type, unit price, availability). In 'comparison' mode each row is one product matched across chains, with the cheapest chain and the price spread.

## `resultsCsv` (type: `string`):

The same rows as a CSV download, ready for a spreadsheet.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "keywords": [
        "milk",
        "bread",
        "eggs"
    ],
    "chains": [
        "coles",
        "woolworths",
        "aldi",
        "iga"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("toptock/au-grocery-prices").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "keywords": [
        "milk",
        "bread",
        "eggs",
    ],
    "chains": [
        "coles",
        "woolworths",
        "aldi",
        "iga",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("toptock/au-grocery-prices").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "keywords": [
    "milk",
    "bread",
    "eggs"
  ],
  "chains": [
    "coles",
    "woolworths",
    "aldi",
    "iga"
  ]
}' |
apify call toptock/au-grocery-prices --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,toptock/au-grocery-prices"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/8y1cg2SGbvMsBAQ8l/builds/2yaKHBu6qAqkbprGu/openapi.json
