# Coles Australia Scraper - Prices, Specials, Stock & History (`crawlplant/coles-au`) Actor

Coles AU prices, specials and stock for any of ~878 stores (by id, postcode or all), ~27k products, with price history nobody else keeps: previous price, 30-day low, 90-day average and a real-discount check. Barcodes, nutrition, allergens, store directory. Nightly cache, so runs don't fail.

- **URL**: https://apify.com/crawlplant/coles-au.md
- **Developed by:** [Piotr Zimniak](https://apify.com/crawlplant) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 products

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Coles Australia Scraper - Prices, Specials, Stock & History

*Independent tool, not affiliated with or endorsed by Coles Group. It reads only public product, price and stock data.*

Get prices, unit prices, specials, **stock levels for any Coles store**, barcodes and full product details (ingredients,
allergens, nutrition, country of origin) from **coles.com.au** as JSON, CSV, Excel or via API. It is the only Coles scraper
on the Store that **keeps price history** (September 2026): the previous price and when it changed, the lowest price in
30 days, the 90-day average, and whether a "special" beats that history.

### Why this one

- **Runs don't fail.** Results come from a catalogue we refresh every night, so a run does not depend on Coles answering at
  that moment: you always get the latest data, in seconds.
  See [Reliability](#reliability).
- **Any store, or all of them.** Prices, stock and aisle for the Coles stores you choose, by store id, by postcode (every
  store in it) or all ~878 stores, each store as its own rows. Plus the full store directory with addresses and opening hours.
- **Price history for every product.** Previous price, when it changed, lowest price in 30 days, 90-day average, and a check
  whether a "special" is a genuine discount. Tracking since 27 September 2026, updated every night; see [Price history](#price-history).
- **Barcodes and nutrition at no extra cost** for products already in our database; `fetchFullDetails` fills in the rest.
- **The whole catalogue in one run.** Every product Coles sells online (~27k), straight from the nightly snapshot.
- **Clean data.** Unit prices normalised to per kg, per litre and per item; multi-buy offers as text ("Buy 2 for $9.40");
  sponsored duplicates removed.
- **Pay for results, not for runs.** $0.80 per 1,000 products on the Free plan, down to $0.50, store rows included; the
  start fee is $0.00005 (a twentieth of a cent). See [Pricing](#pricing).
- **See it live.** Last night's price moves, specials and the Coles store finder: [crawlplant.com/coles-au](https://crawlplant.com/coles-au/).

### What can you use it for?

- **Price monitoring**: shelf and unit prices of the products you care about, on a schedule.
- **Store-level retail analytics**: compare prices, stock and availability across Coles stores and regions.
- **Deal hunting and consumer research**: half-price and multi-buy specials with a check on whether the discount is real.
- **Price-change alerts**: products whose price changed since your last run, sent to email, Slack or Sheets.
- **Basket and store comparison**: barcodes and per-kg prices make matching with other supermarkets straightforward
  (see our [Woolworths Australia Scraper](https://apify.com/crawlplant/woolworths-au), same output format).
- **Nutrition and allergen apps**: filter by dietary statement, allergens, sugar or protein.
- **AI agents**: a fast, well-described tool for "find me the cheapest…" style questions (see [Use with AI agents](#use-with-ai-agents)).

### Quick start

1. Click **Try for free** (or **Start**), keep the default input (search for `milk`, 100 products) and press **Start**.
   It finishes in seconds and costs $0.08 on the Free plan.
2. Open the **Output** tab: a table of products with prices, unit prices, specials and stock. Switch views to
   **Product details** or **Deals check**, or export as JSON, CSV or Excel.
3. Change the input using one of the [ready-to-use examples](#ready-to-use-examples) below, and save it as a **task** to reuse or schedule it.

### Modes

| Mode | What you give it | What you get |
|---|---|---|
| `search` (default) | `queries`: keywords | matching products, like the site's search |
| `category` | `categoryUrls`: any `coles.com.au/browse/...` link (or just `dairy-eggs-fridge/milk`) | the products in that category |
| `specials` | optional `specialsGroups`: `halfprice`, `multibuy`, `onlineonly` | current specials (all of them by default) |
| `barcodes` | `barcodes`: EAN / GTIN codes | those products, looked up in our database (~91% of Coles products have a barcode) |
| `products` | `productIds`: product ids (the number at the end of a product link) | those exact products, with full details |
| `urls` | `urls`: any Coles product, search, category or specials link, or a product id | whatever each link shows; sort and specials filters in the link are kept |
| `catalog` | nothing (set `maxItems` high, e.g. 30000) | the whole catalogue from the nightly snapshot; filters apply |
| `categories` | nothing | departments and categories with their links |
| `stores` | nothing, or `storeIds` / `postcodes` | the store directory: address, coordinates, phone, opening hours, click & collect id |

Add `storeIds`, `postcodes` or `allStores` to any product mode (not `catalog` / `categories`) to get the prices and stock of those stores.

### Ready-to-use examples

Paste any of these into the **JSON** tab of the input (or send them through the API).

**1. Price check for a shopping list:** the exact products you buy, by product id.

```json
{ "mode": "products", "productIds": ["8150288", "439693", "7667368"] }
```

**2. Prices and stock in specific stores:** one row per product per store.

```json
{ "queries": ["tim tam"], "storeIds": ["313", "7689", "584"], "maxItems": 10 }
```

**3. Cheapest coffee per kg:** a search sorted by unit price, within a price range.

```json
{ "queries": ["coffee beans"], "sortBy": "unit_price_asc", "minPrice": 5, "maxPrice": 40, "maxItems": 50 }
```

**4. Everything in a category, cheapest first:**

```json
{ "mode": "category", "categoryUrls": ["https://www.coles.com.au/browse/dairy-eggs-fridge/milk"], "sortBy": "price_asc", "maxItems": 500 }
```

**5. This week's half-price specials:**

```json
{ "mode": "specials", "specialsGroups": ["halfprice"], "maxItems": 2000 }
```

**6. New specials only, every Wednesday:** schedule this weekly to get what just went on special.

```json
{ "mode": "specials", "onlyNewSpecials": true, "maxItems": 5000 }
```

**7. Price-drop alerts since your last run:** schedule it daily; each run returns only products whose price changed.

```json
{ "mode": "products", "productIds": ["8150288", "439693", "7667368"], "changedSince": "last-run" }
```

**8. Best sellers on special in a department:**

```json
{ "mode": "category", "categoryUrls": ["pantry"], "sortBy": "best_sellers", "specialsOnly": true, "maxItems": 100 }
```

**9. Allergy-safe: no peanuts or tree nuts, with full details:**

```json
{ "queries": ["muesli bars"], "excludeAllergens": ["Peanut", "Tree Nut"], "fetchFullDetails": true, "maxItems": 50 }
```

**10. High-protein yoghurt (at least 8 g per 100 g):**

```json
{ "queries": ["yoghurt"], "minProteinPer100g": 8, "fetchFullDetails": true, "maxItems": 100 }
```

**11. The whole catalogue (analytics, BI, price comparison sites):** served from the nightly snapshot in seconds.

```json
{ "mode": "catalog", "maxItems": 30000 }
```

**12. New products this week:**

```json
{ "mode": "catalog", "onlyNew": true, "maxItems": 30000 }
```

**13. 90-day price history for a few products:**

```json
{ "mode": "products", "productIds": ["8150288", "439693"], "priceHistoryDays": 90 }
```

**14. Links straight from the browser:**

```json
{ "mode": "urls", "urls": [
  "https://www.coles.com.au/product/coles-full-cream-milk-3l-8150288",
  "https://www.coles.com.au/search/products?q=tim%20tam&sortBy=priceAscending",
  "https://www.coles.com.au/on-special?filter_Special=multibuy"
] }
```

**15. Your local Coles by postcode:** every Coles store in each postcode, each as its own rows.

```json
{ "queries": ["tim tam"], "postcodes": ["2000", "3000", "4000"], "maxItems": 10 }
```

**16. One product in every Coles store** (price and stock map of Australia):

```json
{ "mode": "products", "productIds": ["8150288"], "allStores": true }
```

**17. The store directory:** every Coles store with address, coordinates and opening hours.

```json
{ "mode": "stores", "maxItems": 1000 }
```

**18. Products by barcode** (EAN / GTIN, e.g. from a scanner or another store's data):

```json
{ "mode": "barcodes", "barcodes": ["9300601186945", "9310072034485"] }
```

**19. This week's biggest discounts, half price and better, biggest first:**

```json
{ "mode": "specials", "minDiscountPercent": 50, "sortBy": "discount_desc", "maxItems": 3000 }
```

**20. Daily watch of a brand, including products that were delisted:**

```json
{ "mode": "catalog", "brands": ["Arnott's"], "changedSince": "last-run", "includeDisappeared": true, "maxItems": 30000 }
```

### How to…

#### Scrape Coles half-price specials

Use example 5 (`mode: specials`, `specialsGroups: ["halfprice"]`), or `multibuy` / `onlineonly` for the other special types.
Add `minDiscountPercent: 40` to keep only big discounts, or use example 19 to rank them biggest first.

#### Get this week's Coles catalogue as data

The weekly catalogue changes on Wednesday. `{ "mode": "specials", "maxItems": 10000 }` returns every product on special
right now, with shelf price, was price, special type, multi-buy offer text and unit price. Schedule it every Wednesday
morning (Sydney time), add `postcodes` to see it at your own stores, or use example 6 to get only what went on special
this week. This is the catalogue's products as data, not the printed PDF.

#### Check if a Coles special is a real discount

Every product on special has `discountCheck.verdict`: `genuine` means the "was" price was really charged for at least 28 of
the 90 days before the special; `questionable` means it wasn't. The verdict is given once a product has 28 days of price
history (see [Price history](#price-history)).

#### Compare Coles prices and stock across stores

Add `storeIds`, `postcodes` or `allStores` to any product mode (examples 2, 15, 16). Each product comes once per store
with that store's price, `inStock`, `availableQuantity` and `aisle`.

#### Get every Coles store location and opening hours

`mode: stores` (example 17) returns every Coles store with address, coordinates, phone, trading and holiday hours.

#### Look up Coles products by barcode

`mode: barcodes` (example 18) with EAN / GTIN codes from the pack or from another store's data.

#### Get Coles prices per kg or per litre

Every product has `pricePerKg`, `pricePerLitre` or `pricePerItem`. Sort a search by unit price with
`sortBy: unit_price_asc` (example 3).

#### Export the whole Coles catalogue

`mode: catalog` with `maxItems: 30000` (example 11) returns every product Coles sells online from the nightly snapshot.

### Input options

**Stores** (combine freely; each store gets its own rows):

- `storeIds`: Coles store ids, the number at the end of a store page link, e.g. `313` in `coles.com.au/find-stores/coles/wa/karratha-313`
  (or run mode `stores` to list them all).
- `postcodes`: up to 500 Australian postcodes; every Coles store in each postcode (the run log lists them). A postcode without
  a Coles gives no rows; the warnings name its nearest store and distance, so you can add it to `storeIds`. `maxItems` applies per store.
- `allStores`: every Coles store (~878). `maxItems` applies per store, so keep it small.

Without them you get Coles' default online store (id `7674`), which is the one our nightly catalogue and price history cover.
Store-specific results are always fetched live.

**Filters and sorting** (all modes):

- `sortBy`: `relevance`, `price_asc`, `price_desc`, `unit_price_asc`, `best_sellers`, `discount_desc`. Sorting is done by Coles before paging
  (`discount_desc` re-sorts the collected results by % off, so combine it with `specialsOnly` or specials mode and a high `maxItems`),
  so you get the true cheapest products, not just a re-ordered first page.
- `specialsOnly` (filtered by Coles, no wasted pages), `inStockOnly`, `minPrice` / `maxPrice`,
  `excludeCategories` (department names or slugs; `tobacco` by default), `brands` (exact brand names),
  `minDiscountPercent` (e.g. 40 = only specials at least 40% below the was-price; multi-buy offers never match).
- `specialsGroups` (specials mode): `halfprice`, `multibuy`, `onlineonly`. Empty = every special.

**Health and nutrition**: `dietary` (e.g. `["Gluten Free"]`, all required), `excludeAllergens` (e.g. `["Peanut", "Milk"]`),
`maxSugarPer100g`, `minProteinPer100g`. These use product details: from our database when we have them, or from the product
page with `fetchFullDetails`. Products without the data a filter needs are left out, so you never get a product we can't confirm.

**Price history** (from our shared daily price database, default online store):

- `changedSince`: only products whose price changed since a date (`2026-09-01`) or since your previous run (`last-run`).
- `includeDisappeared` (with `changedSince: "last-run"`): also returns rows with `type: "disappeared"` for products your previous
  run returned that are gone now (delisted, or no longer matching your filters), with their last price and when they were last seen.
- `onlyNewSpecials`: only products that went on special in the last 7 days (Coles specials change on Wednesdays).
- `onlyNew`: only products that appeared at Coles in the last 7 days.
- `priceHistoryDays`: add the list of price changes from the last N days.

**Other options**:

- `maxItems`: products per query / category / link and store, after filters (default 100). For specials, products and catalog it
  applies per run and store.
- `maxAgeHours`: how old cached data may be (default 12; `0` = always fetch live).
- `fetchFullDetails`: add barcode, ingredients, allergens, dietary statement, nutrition panel, country of origin and storage
  from the product page, for products that don't have them yet. It takes one extra request per product and is charged per product.
- `includeRaw`: add the original Coles payload (every field, including ones we don't map).

### Example output

```json
{
  "store": "coles",
  "productId": "8150288",
  "barcode": "9300601186945",
  "name": "Coles Full Cream Milk",
  "brand": "Coles",
  "size": "3L",
  "url": "https://www.coles.com.au/product/coles-full-cream-milk-3l-8150288",
  "imageUrl": "https://cdn.productimages.coles.com.au/productimages/8/8150288.jpg",
  "price": 4.95,
  "wasPrice": 4.95,
  "unitPrice": 1.65,
  "unitMeasure": "1L",
  "pricePerLitre": 1.65,
  "isOnSpecial": false,
  "specialType": null,
  "offerDescription": null,
  "inStock": true,
  "storeId": "584",
  "availableQuantity": 438,
  "purchaseLimit": 20,
  "aisle": null,
  "categoryPath": ["Dairy, Eggs & Fridge", "Milk", "Full Cream Milk"],
  "ingredients": "Homogenised and Pasteurised Full Cream Milk.",
  "allergens": { "contains": ["Milk"], "mayContain": [] },
  "nutrition": {
    "servingSize": "250mL",
    "servingsPerPack": "12.00",
    "items": [{ "name": "Energy (kJ)", "per100": "258 kJ", "perServing": "646 kJ" }, { "name": "Protein", "per100": "3.4 g", "perServing": "8.5 g" }]
  },
  "dietaryTags": ["Source of Calcium"],
  "countryOfOrigin": "Australian Milk",
  "storage": "Keep this way up,Keep refrigerated. Store at or below 5°C",
  "previousPrice": null,
  "priceChangedAt": null,
  "lowestPrice30d": 4.95,
  "averagePrice90d": null,
  "discountCheck": null,
  "scrapedAt": "2026-09-27T15:45:14.223Z",
  "source": "live"
}
```

### Output fields

| Group | Fields |
|---|---|
| Identity | `store`, `productId`, `barcode` (GTIN), `name`, `brand`, `size`, `url`, `imageUrl`, `images`, `categoryPath` |
| Price | `price`, `wasPrice`, `unitPrice` + `unitMeasure`, `pricePerKg`, `pricePerLitre`, `pricePerItem` |
| Store & stock | `storeId`, `storeName`, `storeSuburb`, `storeState`, `storePostcode`, `inStock`, `availableQuantity`, `purchaseLimit`, `aisle` |
| Store directory (mode `stores`) | `storeId`, `name`, `brand`, `address`, `suburb`, `state`, `postcode`, `latitude`, `longitude`, `phone`, `tradingHours`, `holidayHours`, `collectionPointId`, `url` |
| Specials | `isOnSpecial`, `specialType` (Half Price, Multi Buy, Online Special, Special), `offerDescription`, `specialStartedAt`, `discountCheck`, `specialCycle` |
| History | `previousPrice`, `priceChangedAt`, `lowestPrice30d`, `averagePrice90d`, `firstSeenAt`, `priceHistory` (with `priceHistoryDays`) |
| Product details | `description`, `ingredients`, `allergens`, `nutrition`, `dietaryTags`, `storage`, `countryOfOrigin` |
| Freshness | `scrapedAt`, `source` (`cache` or `live`) |

All prices are in AUD, as numbers. `wasPrice` equals `price` when nothing is discounted (multi-buy offers have no was-price;
see `offerDescription`). Detail fields are `null` (or empty lists) until we have the product page for a product.

**Deal check for specials**: `discountCheck.verdict` is:

- `genuine` when the "was" price was actually charged for at least 28 of the 90 days before the special;
- `questionable` when it was not (for example, when the price was raised shortly before the "discount");
- `insufficient-history` when the product has less than 28 days of price history.

`specialCycle` shows how often a product goes on special and when the next one is expected, once we have seen 3 of its specials.

### Price alerts in 5 minutes

1. Save a **task** with the input you want to watch (examples 6 and 7 above are good starting points).
2. Add a **schedule** (for example every Wednesday morning, when specials change, or daily for price drops).
3. In the task's **Integrations** tab, send results to email, Slack, Google Sheets, Zapier, Make, n8n or a webhook.

With `changedSince: "last-run"`, each run returns only what changed since the previous one, so quiet days cost nothing.

### Run it through the API

**cURL** (runs synchronously and returns the items):

```bash
curl -X POST "https://api.apify.com/v2/acts/crawlplant~coles-au/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"queries": ["milk"], "maxItems": 20}'
```

**JavaScript** (`npm install apify-client`):

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('crawlplant/coles-au').call({ mode: 'specials', specialsGroups: ['halfprice'], maxItems: 200 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((p) => `${p.name}: $${p.price} (was $${p.wasPrice})`));
```

**Python** (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("crawlplant/coles-au").call(run_input={"queries": ["coffee"], "sortBy": "unit_price_asc", "maxItems": 20})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["name"], item["pricePerKg"])
```

### Use with AI agents

The Actor works as a tool in the [Apify MCP server](https://mcp.apify.com): Claude, ChatGPT, Cursor, VS Code, LangChain,
CrewAI, OpenAI Agents and others. Small queries are answered from the cache in seconds, and every output field is described,
so agents understand the data without extra prompting.

To give an assistant only this tool, use this server URL:

```
https://mcp.apify.com?tools=crawlplant/coles-au
```

Example for Claude Desktop (`claude_desktop_config.json`):

```json
{ "mcpServers": { "coles": { "command": "npx", "args": ["mcp-remote", "https://mcp.apify.com?tools=crawlplant/coles-au"] } } }
```

Try prompts like *"find the cheapest olive oil per litre at Coles"*, *"which Coles half-price specials are genuine this week?"* or
*"is Tim Tam in stock at Coles Karratha (store 313)?"*.

### Pricing

Pay per result: you pay for the products returned, and platform usage (compute, proxies) is included.

| Event | Free plan | Starter | Scale | Business and up |
|---|---|---|---|---|
| Product (per 1,000 results) | $0.80 | $0.70 | $0.60 | $0.50 |
| Full product details (per 1,000, only with `fetchFullDetails`) | $0.40 | $0.35 | $0.30 | $0.25 |
| Actor start (per run) | $0.00005 | $0.00005 | $0.00005 | $0.00005 |

Store rows count as products (3 stores × 10 products = 30 results), and each store in mode `stores` counts as one result. Barcodes and details we already have, price history and deal
checks are included in the product price. A failed or empty run costs only the start fee.

| Example on the Free plan | Results | Cost |
|---|---|---|
| Default run: search `milk` | 100 | $0.08 |
| `tim tam` in the stores of 3 postcodes, 10 each | ~30-60 | $0.02-0.05 |
| One product in every Coles store | ~878 | $0.70 |
| Store directory | ~878 | $0.70 |
| Whole catalogue | ~27,000 | ~$21.60 |
| Daily watch with `changedSince: "last-run"` | only the changes | often $0.00 |

Set a **maximum cost per run** in the run options to stop a large run at your budget.

**Compared with other Coles scrapers** (Free plan prices on the Apify Store, 27 September 2026): the most-used one charges
$1.00 per 1,000 products plus $0.008 per run; the others charge $1.49-10.00 per 1,000, several with a start fee of
$0.005-0.10 and extra charges for details. None of them includes price history.

### Reliability

- Most queries are answered from our nightly catalogue in seconds, so a run doesn't depend on Coles answering at that
  moment. Store-specific results are fetched live through Australian proxies, included in the price.
- If a live request doesn't come through, the run still finishes with everything else and a note in its summary. An empty
  run costs only the start fee.
- Automated checks test the Actor, the data and its freshness every 3 hours, around the clock.

### Price history

We have tracked every Coles online price since **27 September 2026**, and every night adds each change. Every product row
carries `previousPrice`, `priceChangedAt`, `lowestPrice30d` and `averagePrice90d`; `priceHistoryDays` adds the full list of
changes. `discountCheck` rates a special once the product has 28 days of history, and `onlyNew` / `onlyNewSpecials`
look at the last 7 days.

### Data freshness

Every item has `scrapedAt` (when the price was read from Coles) and `source`:

- `cache`: from our database, no older than your `maxAgeHours`. The whole catalogue is refreshed every night (Sydney time).
- `live`: fetched from Coles during your run. Store-specific results are always live.

If a live request doesn't come through, the run still finishes with the latest cached data and a note in the run's `OUTPUT` record.

### Troubleshooting

- **Fewer results than expected:** filters (price, dietary, allergens) leave out products that don't match or have no data.
  The `OUTPUT` record in the run's key-value store lists any warnings, e.g. when up to 30 pages were scanned without enough matches.
- **Health filters return few products:** they need product details. Add `"fetchFullDetails": true` for complete results.
- **"category not found":** run mode `categories` to get the exact category links, then paste one into `categoryUrls`.
- **`previousPrice` is `null`:** we have not seen that product's price change since we started tracking it.
- **No history fields on store rows:** price history covers Coles' default online store only.
- **`onlyNew` / `onlyNewSpecials` return nothing with a warning:** these need a week of tracking history; the warning shows the
  date they start working.
- **A product id isn't returned:** the product no longer exists; it is listed in the run warnings and not charged.

### FAQ

#### Do Coles prices and stock differ by store?

Yes, for some products. Without `storeIds` / `postcodes` / `allStores` you get the default online prices Coles shows before
you choose a store or postcode. With them, the prices, stock and aisle of those stores, each store as its own rows.

#### How much does it cost to scrape Coles?

$0.80 per 1,000 products on the Free plan, down to $0.50 on higher plans, plus $0.00005 per run. The default run costs
$0.08. See [Pricing](#pricing).

#### Is a Coles special a real discount?

Check `discountCheck.verdict` on the product: `genuine` or `questionable`, based on what the product really cost before
the special. See [Check if a Coles special is a real discount](#check-if-a-coles-special-is-a-real-discount).

#### When do Coles specials change?

Every Wednesday. Schedule a specials run on Wednesday morning (Sydney time) to track each new week.

#### Which stores does a postcode give?

The stores whose address is in that postcode, from our weekly store directory. For a postcode without a Coles, the warning's
nearest store is measured from the centre of the postcode (ABS Postal Areas), as the crow flies.

#### Why are some products missing `price`?

The product is currently unavailable at that store.

#### How is this different from other Coles scrapers?

Runs finish reliably, results are served from a nightly snapshot in seconds, and every product
carries price history, a genuine-discount check and normalised unit prices. Barcodes and nutrition come free when we
already have them, and there is a full-catalogue mode. See [Pricing](#pricing) for a price comparison.

#### Is it legal to scrape Coles?

The Actor reads only public product, price and stock information that anyone can see without logging in.
It does not collect personal data or reviews. It is not affiliated with Coles Group.

### Good to know

- With filters, up to 30 result pages (~1,400 products) are scanned per query or category and store; the run says so when that
  cap is hit.
- Coles has no sort by name; use a price sort for exact, repeatable ordering.
- Tobacco is excluded by default.

### Run summary

The key-value store record `OUTPUT` shows items returned (cache vs live), invalid records skipped, request statistics,
the proxy route used, extra events charged and any warnings.

### Data sources

Product, price, stock and store data: public pages of coles.com.au. Postcode locations: Australian Bureau of Statistics, Postal
Areas (ASGS Edition 3), [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).

### Privacy

The Actor only reads public product pages and does not collect personal data.
To decide which features to build next, each run sends the developer usage statistics: the mode, which options were
used (e.g. "sortBy: price\_asc", "specialsOnly: on", "2 stores"), result counts and duration.
**It never sends your search terms, URLs, product ids, store ids, postcodes, results or any account details.** Your Apify account id is
replaced on arrival by a one-way keyed hash (the id itself is never stored), used only to count how many different
accounts use the Actor.

# Actor input Schema

## `mode` (type: `string`):

What to fetch: keyword search, a category listing, current specials, products by id, any Coles links, the list of departments and categories, the full catalogue from our nightly snapshot (fast; set Max items high, filters apply), or the store directory (address, coordinates, opening hours).

## `queries` (type: `array`):

Used in 'search' mode. Each query is searched separately.

## `categoryUrls` (type: `array`):

Used in 'category' mode, e.g. https://www.coles.com.au/browse/dairy-eggs-fridge/milk (or just dairy-eggs-fridge/milk). Tip: run mode 'List categories' to get every category URL.

## `productIds` (type: `array`):

Used in 'products' mode: the number at the end of a product URL, e.g. 8150288.

## `barcodes` (type: `array`):

Used in 'barcodes' mode: EAN / GTIN codes, e.g. 9300601186945. Looked up in our Coles database (~91% of products have a barcode); leading zeros don't matter.

## `urls` (type: `array`):

Used in 'urls' mode: paste any product, search, category or specials link (or a bare product id). Sort order and the specials filter in a link are kept.

## `specialsGroups` (type: `array`):

Used in 'specials' mode. Empty = all specials. Values: halfprice, multibuy, onlineonly.

## `maxItems` (type: `integer`):

Maximum products per query / category / link, per store (after filters). For specials, products and the full catalogue: per run and store (the catalogue has ~27k products). Store directory: number of stores.

## `storeIds` (type: `array`):

Prices and stock of these Coles stores, each store as its own rows. The id is the number at the end of a store page link, e.g. 313 in coles.com.au/find-stores/coles/wa/karratha-313. Empty = Coles' default online store (the one with price history). Store-specific results are always fetched live.

## `postcodes` (type: `array`):

Australian postcodes (up to 500): every Coles store in each postcode, each as its own rows. A postcode without a Coles gives no rows; the run warnings name its nearest store and distance, so you can add it to Store ids. Store-specific results are always fetched live.

## `allStores` (type: `boolean`):

Run the searches / categories / specials in every Coles store (~878). Combine with a small Max items: it applies per store.

## `sortBy` (type: `string`):

Result order. Relevance, price, unit price and best sellers are applied by Coles before paging; biggest discount re-sorts the collected results by % off (use with specialsOnly and a high Max items). Price sorts skip products that are unavailable (no price).

## `specialsOnly` (type: `boolean`):

Keep only products currently on special (filtered by Coles, so no pages are wasted).

## `inStockOnly` (type: `boolean`):

Skip products currently unavailable at the store.

## `minPrice` (type: `number`):

Keep only products priced at or above this amount.

## `maxPrice` (type: `number`):

Keep only products priced at or below this amount.

## `excludeCategories` (type: `array`):

Departments to leave out, by name or URL slug, e.g. 'liquorland' or 'Pet'. Tobacco is excluded by default (age-restricted); remove it from the list to include it.

## `brands` (type: `array`):

Keep only these brands (exact brand name, any case), e.g. 'Coles', 'Arnott's'.

## `minDiscountPercent` (type: `number`):

Keep only products at least this much below their was-price, e.g. 50 for half price and better. Multi-buy offers have no was-price and never match.

## `dietary` (type: `array`):

Keep only products whose dietary statement includes ALL of these, e.g. Gluten Free, Vegan, Source of Calcium. Needs product details (see Fetch full details).

## `excludeAllergens` (type: `array`):

Skip products that contain or may contain any of these, e.g. Peanut, Milk, Gluten. Products without allergen data are skipped too. Needs product details.

## `maxSugarPer100g` (type: `number`):

Keep only products with at most this much sugar per 100 g/mL. Needs product details.

## `minProteinPer100g` (type: `number`):

Keep only products with at least this much protein per 100 g/mL. Needs product details.

## `changedSince` (type: `string`):

Return only products whose price changed since this date/time (e.g. 2026-09-01), or 'last-run' for changes since your previous run with the same input. Default online store only.

## `includeDisappeared` (type: `boolean`):

With 'Price changed since' = last-run: also list products your previous run returned that are gone now (delisted or no longer matching), as rows with type 'disappeared'.

## `priceHistoryDays` (type: `integer`):

Add each product's price changes from the last N days (0 = off). Every result always includes previous price, 30-day low, 90-day average and a genuine-discount check.

## `onlyNew` (type: `boolean`):

Only products that appeared at Coles in the last 7 days.

## `onlyNewSpecials` (type: `boolean`):

Only products that went on special in the last 7 days (specials change every Wednesday).

## `fetchFullDetails` (type: `boolean`):

Adds barcode (GTIN), ingredients, allergens, dietary statement, nutrition panel, country of origin and storage from each product page (1 extra request and a small charge per product). Many products already have them from our database at no extra cost.

## `maxAgeHours` (type: `integer`):

Serve cached data younger than this. 0 = always fetch live.

## `includeRaw` (type: `boolean`):

Add the original Coles payload to each record (every field, including ones we do not map).

## Actor input object example

```json
{
  "mode": "search",
  "queries": [
    "milk"
  ],
  "categoryUrls": [],
  "productIds": [],
  "barcodes": [],
  "urls": [],
  "specialsGroups": [],
  "maxItems": 100,
  "storeIds": [],
  "postcodes": [],
  "allStores": false,
  "sortBy": "relevance",
  "specialsOnly": false,
  "inStockOnly": false,
  "excludeCategories": [
    "tobacco"
  ],
  "brands": [],
  "dietary": [],
  "excludeAllergens": [],
  "includeDisappeared": false,
  "priceHistoryDays": 0,
  "onlyNew": false,
  "onlyNewSpecials": false,
  "fetchFullDetails": false,
  "maxAgeHours": 12,
  "includeRaw": false
}
```

# Actor output Schema

## `results` (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 = {
    "queries": [
        "milk"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawlplant/coles-au").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 = { "queries": ["milk"] }

# Run the Actor and wait for it to finish
run = client.actor("crawlplant/coles-au").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 '{
  "queries": [
    "milk"
  ]
}' |
apify call crawlplant/coles-au --silent --output-dataset

```

## MCP server setup

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

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/2jzLcp89oca98FJOs/builds/FjF9Q7aJnmacSb5kO/openapi.json
