# Grocery Price Monitor - Coles & Woolworths AU Change Feed (`datadeltas/grocery-price-monitor`) Actor

Daily change feed for Australian grocery prices: what changed price, went on special, or came back in stock at Coles and Woolworths since your last run. Emits only changes - schedule it and get alerts-ready diffs, not full dumps.

- **URL**: https://apify.com/datadeltas/grocery-price-monitor.md
- **Developed by:** [Daniel Matthee](https://apify.com/datadeltas) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $15.00 / 1,000 change detecteds

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Grocery Price Monitor — Coles & Woolworths AU Change Feed

Get a **daily diff of Australian grocery prices**, not another full dump.
Point this actor at the searches and products you care about on
**coles.com.au** and **woolworths.com.au**, schedule it, and every run emits
only what **changed since the last run**: price moves, products going on
special (or coming off one), and stock coming back or disappearing.

> **Important — the first run emits nothing.** Run 1 seeds the baseline into
> a persistent state store and logs `FIRST RUN: seeded baseline state...`.
> From run 2 onwards you get the change feed. That's the whole point: an
> empty dataset on a scheduled run means *nothing changed*, not a failure.

### Example change record

```json
{
  "retailer": "coles",
  "sku": "8638285",
  "name": "Shapes Original Chicken Crimpy Cracker Biscuits",
  "field": "price",
  "oldValue": 4.0,
  "newValue": 2.0,
  "pctChange": -50.0,
  "productUrl": "https://www.coles.com.au/product/shapes-original-chicken-crimpy-cracker-biscuits-8638285",
  "detectedAt": "2026-08-31T09:15:04Z"
}
```

`field` is one of `price`, `special`, or `availability`. Special changes carry
the offer text (`"Half Price"`, `"Buy 2 for $4.80"`, `"2 for $10.00 - $2.78/100G"`)
as `newValue` — and `null` when a special ends. Availability changes flip
between `true` and `false`. `pctChange` is set only on price changes.

### Input options

| Field | Type | Default | What it does |
|---|---|---|---|
| `watchQueries` | array | `["milk"]` | Search terms watched on each selected retailer. Every product they return is monitored. |
| `watchUrls` | array | `[]` | Coles browse/category URLs and/or Woolworths product URLs (`.../shop/productdetails/<stockcode>/...`). Routed to the right retailer automatically. |
| `retailers` | array | both | `coles`, `woolworths-au`, or both. |
| `maxItemsPerRetailer` | integer | `500` | Hard cap on products checked per retailer per run. |
| `changeThresholdPct` | integer | `0` | Suppress price moves smaller than this %. Special and availability changes are **always** reported. |
| `proxyConfiguration` | proxy | AU residential | Both sites run bot protection (Imperva / Akamai); keep the residential AU default. |

### Use cases

- **"Tell me when Weet-Bix or my baby formula goes on half price"** — watch a
  handful of queries, schedule daily, wire the dataset to an email/Slack
  integration. You only ever get pinged on actual changes.
- **"Track competitor shelf prices for my brand across Coles and Woolies"** —
  brand managers watch their product searches and get an auditable feed of
  every price move and promo start/end, with percentages.
- **"Build a price-history dataset for the products I resell"** — the change
  feed *is* the history: append each run's dataset and you have a compact
  time series of every move, without storing thousands of unchanged rows.

### How it behaves

- One retailer failing (site change, bot-block burst) degrades to a warning —
  the other retailer still runs, and the failed retailer's items keep their
  previous state so you get no false changes. The run still succeeds.
- State lives in the named key-value store `grocery-price-monitor-state` and
  survives across runs. Products that drop out of search ranking are kept in
  state untouched — never reported as fake availability changes.
- Newly watched products (new query, new URL) are seeded silently; you'll see
  changes for them from their second sighting.
- Schema drift on either site logs a clear warning naming the field and keeps
  going — a renamed field never crashes your scheduled feed.

### Update & maintenance promise

Both retailers' response schemas are checked weekly and after any observed
drift warning spike. Field contract changes (never renames without notice)
are announced in the changelog below.

### Changelog

- **0.1** (2026-08-31) — Initial release: Coles + Woolworths AU, price /
  special / availability change feed, per-item seeding, price-move threshold,
  pay-per-event pricing.

# Actor input Schema

## `watchQueries` (type: `array`):

Keywords to watch on each selected retailer, e.g. "milk", "weet-bix". Every product these searches return (up to Max items per retailer) is monitored.

## `watchUrls` (type: `array`):

Coles browse/category URLs (https://www.coles.com.au/browse/...) and/or Woolworths product URLs (https://www.woolworths.com.au/shop/productdetails/<stockcode>/...). Each URL is routed to its retailer automatically.

## `retailers` (type: `array`):

Which retailers to monitor.

## `maxItemsPerRetailer` (type: `integer`):

Hard cap on products checked per retailer per run. Keeps runs cheap and fast.

## `changeThresholdPct` (type: `integer`):

Suppress price moves smaller than this percentage (0 = report every price move). Special and availability changes are ALWAYS reported regardless of this threshold.

## `proxyConfiguration` (type: `object`):

Coles (Imperva) and Woolworths (Akamai) both run bot protection - Apify residential AU proxies are strongly recommended.

## Actor input object example

```json
{
  "watchQueries": [
    "milk"
  ],
  "retailers": [
    "coles",
    "woolworths-au"
  ],
  "maxItemsPerRetailer": 500,
  "changeThresholdPct": 0,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "AU"
  }
}
```

# Actor output Schema

## `records` (type: `string`):

No description

# 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 = {
    "watchQueries": [
        "milk"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "AU"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("datadeltas/grocery-price-monitor").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 = {
    "watchQueries": ["milk"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "AU",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("datadeltas/grocery-price-monitor").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 '{
  "watchQueries": [
    "milk"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "AU"
  }
}' |
apify call datadeltas/grocery-price-monitor --silent --output-dataset

```

## MCP server setup

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

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/gbdbOnsNVcsxDFtim/builds/PWdOJJyjqIqWaKShi/openapi.json
