# ALDI Australia Scraper - Prices, Special Buys, Stores & History (`crawlplant/aldi-au`) Actor

ALDI Australia scraper: the full range (~3,200 products) with prices, unit prices, Super Savers, Lower Prices and Special Buys (also announced ones, with on-sale date), prices for any of ~614 stores, and price history ALDI doesn't show: previous price, 30-day low, 90-day average. MCP-ready.

- **URL**: https://apify.com/crawlplant/aldi-au.md
- **Developed by:** [CrawlPlant](https://apify.com/crawlplant) (community)
- **Categories:** E-commerce, Automation, AI
- **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

## ALDI Australia Scraper - Prices, Special Buys, Stores & History

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

Get the full **ALDI Australia** range as JSON, CSV, Excel or via API: prices, unit prices, **Super Savers**, **Lower
Prices**, **Special Buys with their on-sale date** (including the ones announced for the coming week), prices of **any of
\~614 ALDI stores**, product details (description, ingredients, allergens, storage) and the store list. ALDI's website shows
no "was" price, so we keep the **price history** ourselves: the previous price and when it changed, the lowest price in
30 days and the 90-day average.

### Why this one

- **Runs don't fail.** Results come from a catalogue we refresh every night, so a run does not depend on ALDI answering at
  that moment: you get the latest data in seconds. See [Reliability](#reliability).
- **Special Buys before they hit the shelves.** Every Special Buy carries `onSaleDate`; announced ones (next Wednesday,
  next Saturday) come with a future date, so you can plan, compare or alert ahead of the day.
- **Every ALDI promotion, labelled.** `specialType` says whether a product is a Super Saver, a Lower Price, a Special Buy or
  Limited Time Only, and specials mode lists each group in full.
- **Price history ALDI doesn't show.** Previous price, when it changed, lowest price in 30 days, 90-day average, updated
  every night. See [Price history](#price-history).
- **Prices for any store.** Some fresh produce, meat and liquor prices differ by store and state (Mandarins 1kg: $4.99 at
  ALDI Inglewood WA, $2.99 at ALDI Darlinghurst NSW on 11 October 2026). Every store is read every night: pick stores by
  id, postcode or all ~614; each store gets its own rows.
- **The whole range in one run.** All ~3,200 products, straight from the nightly snapshot.
- **Clean data.** Brand in the product name like on the shelf label, unit prices normalised to per kg, per litre and per
  item, the pack price and the per-kg price of weighed meat kept apart.
- **Pay for results, not for runs.** $0.80 per 1,000 products on the Free plan, down to $0.50; the start fee is $0.00005.
  See [the price table](#how-much-does-it-cost-to-scrape-aldi).

### What can you use it for?

- **Price monitoring**: shelf and unit prices of the products you care about, on a schedule.
- **Special Buys tracking**: next week's Special Buys as data, for deal sites, newsletters, resellers and alerts.
- **Basket comparison**: ALDI next to Coles and Woolworths with the same output format (see [More Australian data](#more-australian-data)).
- **Price-change alerts**: products whose price changed since your last run, sent to email, Slack or Sheets.
- **Regional price analysis**: the same product across ALDI stores and states.
- **AI agents**: a fast, well-described tool for "what's on Special Buys this week?" style questions.

### Quick start

1. Click **Try for free** (or **Start**), keep the default input (search for `milk`, up to 100 products) and press
   **Start**. It finishes in seconds and costs about $0.07 on the Free plan.
2. Open the **Output** tab: a table of products with prices, unit prices, specials and Special Buy dates. Switch views to
   **Product details** or **Specials & price drops**, or export as JSON, CSV or Excel.
3. Change the mode or pick an example below.

### Modes

| Mode | What you get |
|---|---|
| `search` | Products matching each query (default: `milk`) |
| `category` | Every product in a category link, e.g. Dairy, Eggs & Fridge > Milk |
| `specials` | Super Savers, Lower Prices, Special Buys (on sale and announced), Limited Time Only |
| `products` | Products by id (the number at the end of a product link) |
| `barcodes` | Products behind known EAN / GTIN codes; unknown codes appear in warnings |
| `urls` | Any ALDI product, search, category or Special Buys link pasted from the browser |
| `categories` | Every category and subcategory with its link and product count |
| `catalog` | The whole range (~3,200 products) |
| `stores` | Every ALDI store (~614) with address, suburb, state, postcode and coordinates |

### Ready-to-use examples

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

```json
{ "mode": "products", "productIds": ["398691", "371515", "399885"] }
```

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

```json
{ "queries": ["mandarins", "beef mince"], "storeIds": ["G525", "G094"], "maxItems": 10 }
```

**3. Cheapest oil and vinegar per litre:** a category sorted by unit price.

```json
{ "mode": "category", "categoryUrls": ["https://www.aldi.com.au/products/pantry/oils-vinegars/k/1111111172"], "sortBy": "unit_price_asc" }
```

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

```json
{ "mode": "category", "categoryUrls": ["https://www.aldi.com.au/products/dairy-eggs-fridge/milk/k/1111111160"], "sortBy": "price_asc" }
```

**5. Next week's Special Buys (announced, not on sale yet):**

```json
{ "mode": "specials", "specialsGroups": ["upcoming-special-buys"], "maxItems": 500 }
```

**6. Every Special Buy still in stores, plus the announced ones:**

```json
{ "mode": "specials", "specialsGroups": ["special-buys"], "maxItems": 1000 }
```

**7. This week's Super Savers:**

```json
{ "mode": "specials", "specialsGroups": ["super-savers"] }
```

**8. All Lower Prices:**

```json
{ "mode": "specials", "specialsGroups": ["lower-prices"], "maxItems": 1000 }
```

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

```json
{ "mode": "catalog", "changedSince": "last-run", "maxItems": 5000 }
```

**10. New Special Buys only:** schedule it on Wednesday and Saturday mornings.

```json
{ "mode": "specials", "specialsGroups": ["special-buys"], "onlyNewSpecials": true, "maxItems": 500 }
```

**11. The whole range (analytics, BI, price comparison sites):**

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

**12. New products this week:**

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

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

```json
{ "mode": "products", "productIds": ["398691", "398689"], "priceHistoryDays": 90 }
```

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

```json
{
  "mode": "urls",
  "urls": [
    "https://www.aldi.com.au/product/farmdale-light-milk-2l-000000000000398691",
    "https://www.aldi.com.au/products/pantry/confectionery/k/1111111181"
  ],
  "maxItems": 50
}
```

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

```json
{ "queries": ["bread"], "postcodes": ["2010", "3000"], "maxItems": 20 }
```

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

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

**17. One product in every ALDI store** (a price map of Australia):

```json
{ "queries": ["mandarins"], "allStores": true, "maxItems": 2 }
```

**18. The store directory:**

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

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

```json
{ "mode": "catalog", "brands": ["Farmdale"], "changedSince": "last-run", "includeDisappeared": true, "maxItems": 500 }
```

**20. Special Buys on selected dates and themes:**

```json
{
  "mode": "specials",
  "specialBuysDates": ["2026-10-17", "2026-10-24"],
  "specialBuysThemes": ["Gardening", "Tools and DIY"],
  "maxItems": 500
}
```

**21. Only the Special Buys on the date in a link:**

```json
{ "mode": "urls", "urls": ["https://www.aldi.com.au/special-buys/2026-10-17"], "maxItems": 500 }
```

**22. A product by its barcode:**

```json
{ "mode": "barcodes", "barcodes": ["8000500003787"], "fetchFullDetails": true }
```

**23. Special Buys stock at your store:**

```json
{ "mode": "products", "productIds": ["574158"], "storeIds": ["G525"], "includeStock": true }
```

**24. Product variants and label images:**

```json
{ "mode": "products", "productIds": ["752061001"], "fetchFullDetails": true }
```

### How to…

#### Get next week's ALDI Special Buys as data

Specials mode with `upcoming-special-buys` returns every Special Buy ALDI has announced, each with `onSaleDate` (the day it
reaches the stores) and `offerDescription` ("On Sale Sat 17 Oct"). `special-buys` adds the ones already on sale ("While
Stocks Last"), including earlier weeks' that are still listed.

#### Scrape ALDI Super Savers and Lower Prices

`specialsGroups: ["super-savers"]` or `["lower-prices"]`, or leave it empty for every group. Each product's `specialType`
names its group.

#### Track ALDI price drops

ALDI marks "Lower Prices" but never shows what a product cost before. Every row carries `previousPrice` and
`priceChangedAt` from our nightly history; `changedSince: "last-run"` returns only what moved since your previous run.

#### Compare ALDI prices across stores and states

Add `storeIds` (or `postcodes`) to any search, category or specials run. Each store's rows carry `storeId`, `storeName`,
`storeState` and `priceScope: "store"`.

#### Get ALDI prices per kg or per litre

`pricePerKg`, `pricePerLitre` and `pricePerItem` are filled from ALDI's comparison price. Weighed meat keeps the pack price
in `price` and the per-kg price in `unitPrice`. `sortBy: "unit_price_asc"` on a category puts the cheapest per kg or litre
first (ALDI quotes some products per 100 g and others per 1 kg; we sort on the normalised price).

#### Export the whole ALDI range

Mode `catalog` with `maxItems` 5000 returns every product in one run.

### Input options

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

- `storeIds`: ALDI store ids, a letter and 3 digits, e.g. `G525` (Darlinghurst NSW); run mode `stores` to list them all.
- `postcodes`: up to 100 Australian postcodes; every ALDI store in each postcode (the run log lists them). A postcode without
  an ALDI gives no rows; the warnings name its nearest store and distance, so you can add it to `storeIds`. `maxItems`
  applies per store.
- `allStores`: every ALDI store (~614). `maxItems` applies per store, so keep it small.

Without them you get ALDI's national prices, which our nightly catalogue and price history cover. Store prices come from our
nightly read of every product in every ALDI store, so even `allStores` answers in seconds; a store not read since this week's
Wednesday is fetched live. A product the store doesn't sell gives no row for that store.

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

- `sortBy`: `relevance`, `price_asc`, `price_desc`, `unit_price_asc`, `name`. Price and name sorts are done by ALDI before
  paging, so you get the true cheapest products, not a re-ordered first page; `unit_price_asc` collects every match first,
  then sorts by the comparison price.
- `specialsOnly`, `minPrice` / `maxPrice`, `excludeCategories` (category names or slugs, e.g. `Liquor`), `brands` (exact
  brand names).
- `specialsGroups` (specials mode): `super-savers`, `lower-prices`, `special-buys`, `upcoming-special-buys`,
  `limited-time-only`. Empty = all, or just Special Buys when dates or themes are selected.
- `specialBuysDates` (specials mode and Special Buys links): on-sale dates as `YYYY-MM-DD`. Empty = all dates.
  A date in a URL overrides this list. `upcoming-special-buys` still keeps only dates after today in Sydney.
- `specialBuysThemes` (specials mode and Special Buys links): exact ALDI theme names, e.g. `Gardening` or `Tools and DIY`.
  Multiple dates and themes are combined; each product appears once. Dates and themes filter Special Buys before
  `maxItems`, leaving other explicitly selected specials groups unchanged. Complete date/theme memberships are read
  nightly, including known older dates still in the catalogue. Missing or stale memberships fall back to live listings.
- `excludeAllergens` (e.g. `["Peanut", "Milk"]`): uses product details, from our database when we have them or from the
  product page with `fetchFullDetails`. Products without allergen data are left out.

**Price history** (from our shared daily price database, national prices):

- `changedSince`: only products whose price changed since a date (`2026-10-12`) 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, with their last price and when they were last seen.
- `onlyNewSpecials`: only products that joined a specials group in the last 7 days.
- `onlyNew`: only products that appeared at ALDI 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 our data may be (default 36; anything read before this week's Wednesday is fetched live; `0` =
  always fetch live, including `catalog`). The catalogue cache is used only when its nightly read is complete and fresh;
  otherwise the whole range is fetched live.
- `fetchFullDetails`: add description, ingredients, allergens, storage, origin, variants and packaging images for products
  that don't have them yet. Newly fetched details also attempt verified barcode decoding from the images. Image reads
  add requests and processing time; the detail charge remains per successfully fetched product, including photo reads.
- `includeStock`: with stores selected, add a Special Buys stock observation. `stockStatus` is ALDI's traffic-light
  colour and `stockInfoDisplay` its wording; `stockObservedAt` is when we checked. No exact quantities are supplied.
  Nightly observations are used only within `maxAgeHours` and never beyond 24 hours; `stockSource` is `cache` or `live`.
  Missing/older observations are read live; `maxAgeHours: 0` always checks live. Regular groceries and newly announced
  products may have no stock data. Failed reads retain price rows with warnings.
- `barcodes` (barcodes mode): known GTIN-8/12/13/14 codes, with valid check digits. ALDI's lookup maps a code to SKU(s).
  This does not enumerate barcodes for every catalogue product. Successfully resolved codes are retained in our cache.
- `includeRaw`: add the original ALDI payload (every field, including ones we don't map).

### Example output

An announced Special Buy (products mode, 11 October 2026; images and empty detail fields left out):

```json
{
  "store": "aldi",
  "productId": "000000000000269634",
  "barcode": null,
  "name": "Weiss Soft Gingerbread Assorted 200g",
  "brand": "Weiss",
  "size": "200 g",
  "url": "https://www.aldi.com.au/product/weiss-soft-gingerbread-assorted-200g-000000000000269634",
  "price": 4.99,
  "wasPrice": 4.99,
  "unitPrice": 2.5,
  "unitMeasure": "100G",
  "pricePerKg": 25,
  "isOnSpecial": true,
  "specialType": "Special Buy",
  "offerDescription": "On Sale Wed 14 Oct",
  "onSaleDate": "2026-10-14",
  "inStock": null,
  "categoryPath": ["Christmas", "Biscuits & Cookies"],
  "storeId": null,
  "scrapedAt": "2026-10-11T17:13:59.688Z",
  "source": "live"
}
```

Rows served from our database also carry `previousPrice`, `priceChangedAt`, `firstSeenAt`, `lowestPrice30d` and
`averagePrice90d`.

### Output fields

| Field | Description |
|---|---|
| `productId` | ALDI SKU, 18 digits (stable id) |
| `name`, `brand`, `size` | Product name with the brand as on the shelf label; ALDI's own brands title-cased |
| `price` | Shelf price in AUD; weighed meat: the approximate pack price |
| `wasPrice` | Equals `price`: ALDI shows no was-price online (see `previousPrice`) |
| `unitPrice`, `unitMeasure` | ALDI's comparison price, e.g. 1.7 per `1L`, 0.89 per `100G` |
| `pricePerKg`, `pricePerLitre`, `pricePerItem` | Unit price normalised (only the one matching the measure is set) |
| `isOnSpecial`, `specialType` | Super Saver, Lower Price, Special Buy or Limited Time Only (the strongest when several) |
| `onSaleDate`, `offerDescription` | Special Buys: the day they reach the stores; "While Stocks Last" once on sale |
| `categoryPath` | Category > subcategory (Special Buys often have none) |
| `description`, `ingredients`, `allergens`, `storage`, `countryOfOrigin` | Product details, as ALDI publishes them |
| `variants`, `abstractSku`, `isAbstract` | Native size/colour buckets and SKU matrix; preserves the family ALDI reports |
| `nutritionImageUrl`, `ingredientsImageUrl` | Label pictures, separately identified from the product gallery |
| `nutrition` | Structured label values when supplied by the source; null when only an image is available |
| `usageInstructions`, `legalDisclaimer`, `warnings`, `energyClass`, `ageRestriction`, `alcohol`, `nutritionalClaims` | Additional source details; missing values stay null |
| `barcode`, `barcodes`, `barcodeSource` | Primary and all verified known codes, with their source; null/empty when unknown |
| `stockInformationAvailable` | Whether ALDI exposes a stock-check service for this product; not its stock status |
| `stockStatus`, `stockInfoDisplay`, `stockObservedAt`, `stockSource` | With includeStock and stores: fresh traffic-light observation, source wording and time |
| `imageUrl`, `images`, `url` | Product images and page |
| `storeId`, `storeName`, `storeSuburb`, `storeState`, `storePostcode`, `priceScope` | Store rows: the store whose prices the row shows |
| `previousPrice`, `priceChangedAt` | The last price change we saw |
| `lowestPrice30d`, `averagePrice90d` | From our nightly history |
| `firstSeenAt`, `specialStartedAt` | When we first saw the product / its current special |
| `priceHistory` | With `priceHistoryDays`: the list of price changes |
| `scrapedAt`, `source` | When the data was read, and `cache` or `live` |

ALDI's product JSON does not normally list a barcode; verified photo decodes and known-code lookups can fill it. `inStock` remains unknown when the
source provides only an ambiguous traffic-light level: red is labelled "Low/no stock", not a confirmed zero. Prices
and stock have separate observation times.

Store directory rows also include `tradingHours`, `holidayHours`, `timezone`, `services`, `url`, phone,
`averageRating`, `reviewCount`, `openingHoursNotes` and `storeDetailsObservedAt` when ALDI's public Store Locator supplies
them. `openingStatus` is the locator's status at its own `observedAt`, not a guarantee it is open now when reading an old
cached row. The phone may be ALDI's central number; ratings refer to stores, not products. Missing locator entries keep
their original directory record.

### Price alerts in 5 minutes

1. Save a **task** with the input you want to watch (examples 5, 9 and 10 above are good starting points).
2. Add a **schedule** (daily for price changes; Wednesday and Saturday mornings for Special Buys).
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~aldi-au/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mode": "specials", "specialsGroups": ["upcoming-special-buys"], "maxItems": 100}'
```

**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/aldi-au').call({ mode: 'specials', specialsGroups: ['upcoming-special-buys'], maxItems: 500 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((p) => `${p.onSaleDate} ${p.name}: $${p.price}`));
```

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

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("crawlplant/aldi-au").call(run_input={"queries": ["coffee"], "sortBy": "price_asc", "maxItems": 20})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["name"], item["price"], 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/aldi-au
```

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

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

Try prompts like *"what are ALDI's Special Buys this Saturday?"*, *"cheapest olive oil per litre at ALDI"* or *"which ALDI
prices dropped this week?"*.

**Use it when** you need ALDI Australia prices, Special Buys (also announced ones), Super Savers, Lower Prices, price
history, product details or the prices of chosen ALDI stores.

**Not for** Coles or Woolworths products (see [More Australian data](#more-australian-data)), online orders, exact stock
quantities, or the printed catalogue PDF: the Special Buys come as product rows.

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

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 |
| Store (per 1,000, mode `stores`) | $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). Details we already have and price history 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` | ~90 | $0.07 |
| Next week's Special Buys | ~200-300 | $0.16-0.24 |
| `mandarins` in 10 stores, 5 each | ~50 | $0.04 |
| Store directory | ~614 | $0.49 |
| Whole range | ~3,200 | ~$2.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.

### Reliability

- Most queries are answered from our nightly catalogue in seconds, so a run doesn't depend on ALDI answering at that moment.
- 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 data and its freshness every 3 hours, around the clock.

### Price history

We have tracked every ALDI price nightly since **October 2026**. Every product row carries `previousPrice`,
`priceChangedAt`, `lowestPrice30d` and `averagePrice90d`; `priceHistoryDays` adds the full list of changes. `onlyNew` /
`onlyNewSpecials` look at the last 7 days. Price history covers ALDI's national prices; store rows have none.

### Data freshness

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

- `cache`: from our database, no older than your `maxAgeHours`. The whole range is refreshed every night (Sydney time).
- `live`: fetched from ALDI during your run.

Product details have a separate freshness clock: new/older detail versions are backfilled, and up to 100 existing
details per night are refreshed after 30 days or a name, brand, size or main-image change. Detail-only updates preserve
price history and verified codes. An unreadable barcode is retried during this refresh.

Special Buys theme membership is refreshed nightly and replaced only after every theme page for a date succeeds.
The store directory and locator metadata are refreshed weekly.

Store stock runs separately during 03:00–05:00 Sydney time. Each tick is capped at 45 minutes, 20,000 logical requests
and the scheduler deadline; nearby observations are saved under their own store IDs. Incomplete reads retry through
the scheduler and customer requests check missing pairs live. Stock never marks a price snapshot complete or changes
`scrapedAt`; use `stockObservedAt` to judge its age. No quantity or Boolean availability is inferred from a colour.

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 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.
- **`excludeAllergens` returns few products:** it needs 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 ALDI's national prices only.
- **A product id isn't returned:** the product no longer exists; it is listed in the run warnings and not charged.

### More Australian data

- [Coles Australia Scraper](https://apify.com/crawlplant/coles-au): prices, specials and stock for any of ~878 Coles stores, barcodes, nutrition, price history.
- [Woolworths Australia Scraper](https://apify.com/crawlplant/woolworths-au): prices, specials, barcodes, nutrition and price history.
- [Australian Bank Interest Rates](https://apify.com/crawlplant/au-bank-rates) and [Australian Energy Plans](https://apify.com/crawlplant/au-energy-plans): official Consumer Data Right data.

All three supermarket Actors share one output format, so ALDI, Coles and Woolworths rows line up in one table.

### FAQ

#### Do ALDI prices differ by store?

Most don't. Some fresh produce, meat and liquor prices do, and the range differs (from ~750 products in a small store to
\~3,200). In a sample of 390 products read in five stores across four states on 11 October 2026, 14 prices differed. Add
`storeIds` or `postcodes` to see a store's own prices.

#### When do ALDI Special Buys change?

New Special Buys go on sale every Wednesday and Saturday. ALDI announces them a week or so ahead; we list them as soon as
they are announced, with `onSaleDate`.

#### Does ALDI show a "was" price?

No. ALDI marks Lower Prices but shows only the current price. Our nightly history fills that gap: `previousPrice`,
`priceChangedAt`, `lowestPrice30d`, `averagePrice90d`.

#### Where do the barcodes come from?

ALDI's product JSON does not normally expose GTIN, but some packaging photos contain a readable barcode. When fetching
new full details, we try to decode those photos and retain a code only when ALDI's barcode lookup maps it to the same
product SKU. Curved, obscured or missing barcodes remain empty. The nightly detail backfill also saves verified codes.
Use `mode: "barcodes"` to resolve codes you already know; successful codes are retained for future results.

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

$0.80 per 1,000 products on the Free plan, $0.50 on Business. The whole range (~3,200 products) costs about $2.60. See the
[price table](#how-much-does-it-cost-to-scrape-aldi).

#### Is it legal to scrape ALDI?

The Actor reads only public product and price pages that anyone can open without an account, at a polite pace, and stores
no personal data. Check that your use of the data fits your local laws and ALDI's terms.

### Good to know

- ALDI publishes store stock levels for eligible Special Buys, not exact quantities or stock for every grocery.
- Product details (ingredients, allergens) are as ALDI publishes them; a few are incomplete on ALDI's own site.
- Prices are ALDI's national prices unless you choose stores.

### Run summary

Every run writes an `OUTPUT` record to its key-value store: items pushed, how many came from the cache, requests, warnings
and what was charged.

### Data sources

Public product, price and store data from aldi.com.au.

### Privacy

The Actor stores no personal data. To improve it, we record which options a run used and how many items it returned,
with a one-way hash of your account id; never your input values or results.

# Actor input Schema

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

What to fetch: keyword search, a category listing, specials (Super Savers, Lower Prices, Special Buys, Limited Time Only), products by id or barcode, any ALDI links, the list of categories, the full catalogue (~3,200 products; set Max items high, filters apply), or the store directory with hours and services.

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

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

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

Used in 'category' mode, e.g. https://www.aldi.com.au/products/dairy-eggs-fridge/milk/k/1111111160 (or dairy-eggs-fridge/milk, or the key 1111111160). 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. 000000000000398691 (leading zeros may be left out: 398691).

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

Used in 'barcodes' mode: up to 100 GTIN-8/12/13/14 codes with valid check digits. ALDI maps each known code to product IDs. Unknown codes are reported in warnings; leading zeros are preserved. Example: 8000500003787 (Ferrero Rocher 200g).

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

Used in 'urls' mode: paste any product, search, category or Special Buys link (or a bare product id). The sort order is kept; a Special Buys date in the link overrides Special Buys dates below.

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

Used in 'specials' mode. Empty = all. Values: super-savers, lower-prices, special-buys (on sale now and announced), upcoming-special-buys (announced, not on sale yet), limited-time-only.

## `specialBuysDates` (type: `array`):

Used in 'specials' mode and for Special Buys links. On-sale dates in YYYY-MM-DD format, e.g. 2026-10-17. Empty = all dates; upcoming-special-buys still excludes dates on or before today in Sydney. With dates or themes selected and no Specials groups, only Special Buys are returned. A dated URL overrides this list.

## `specialBuysThemes` (type: `array`):

Used in 'specials' mode and for Special Buys links. Exact ALDI theme names, e.g. Gardening or Tools and DIY. Empty = all themes; multiple themes are combined without duplicate products. Themes filter Special Buys only, before Max items. Complete nightly theme memberships are used when fresh; missing or stale memberships fall back to live listings.

## `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 ~3,200 products). Store directory: number of stores.

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

Prices and range of these ALDI stores, each store as its own rows (some fresh produce, meat and liquor prices differ by store and state). The id is a letter and 3 digits, e.g. G525 (Darlinghurst NSW); mode 'Store directory' lists them all. Empty = ALDI's national prices (the ones with price history). Store prices come from our nightly read of every store (fetched live when it's older than this week's Wednesday).

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

Australian postcodes (up to 100): every ALDI store in each postcode, each as its own rows. A postcode without an ALDI gives no rows; the run warnings name its nearest store and distance, so you can add it to Store ids.

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

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

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

Result order. Relevance, price and name are applied by ALDI before paging; unit price collects every match first, then sorts (cheapest per kg / litre / item first).

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

Keep only products that are a Super Saver, Lower Price, Special Buy or Limited Time Only.

## `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`):

Categories to leave out, by name or URL slug, e.g. 'Liquor' or 'christmas'.

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

Keep only these brands (exact brand name, any case), e.g. 'Farmdale', 'Belmont Biscuit Co.'.

## `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.

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

Return only products whose price changed since this date/time (e.g. 2026-10-12), or 'last-run' for changes since your previous run with the same input. National prices 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 and 90-day average once we have that history.

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

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

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

Only products that became a Super Saver, Lower Price, Special Buy or Limited Time Only in the last 7 days.

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

Adds description, ingredients, allergens, storage, origin, variants and packaging images from the product endpoint. Newly fetched details also attempt barcode decoding from photos, confirmed against ALDI's SKU mapping; these image reads take extra time. A small charge applies per successfully fetched detail, including image reads. Cached details are included at no extra cost.

## `includeStock` (type: `boolean`):

With storeIds, postcodes or allStores: add Special Buys stock observations. Nightly cache is used within Max cache age and never beyond 24 hours; missing or older observations are read live. Max cache age 0 forces live checks. Adds stockStatus, stockInfoDisplay, stockObservedAt and stockSource (cache/live), independently of the price timestamp. Low/no stock does not prove zero units. Failed lookups retain the price row with a warning. Extra requests are included in the product price.

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

We refresh every product nightly (Sydney time). Data older than this, or read before this week's Wednesday (Special Buys day), is fetched live instead. 0 = always fetch live.

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

Add the original ALDI 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": [],
  "specialBuysDates": [],
  "specialBuysThemes": [],
  "maxItems": 100,
  "storeIds": [],
  "postcodes": [],
  "allStores": false,
  "sortBy": "relevance",
  "specialsOnly": false,
  "excludeCategories": [],
  "brands": [],
  "excludeAllergens": [],
  "includeDisappeared": false,
  "priceHistoryDays": 0,
  "onlyNew": false,
  "onlyNewSpecials": false,
  "fetchFullDetails": false,
  "includeStock": false,
  "maxAgeHours": 36,
  "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/aldi-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/aldi-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/aldi-au --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,crawlplant/aldi-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/ab0vT2YpOquVCc2D1/builds/SMnQViveuZz36jqSB/openapi.json
