# Grocery Flyer Scraper — Weekly Deals & Price Drops (`scrapersdelight/flipp-flyer-digest`) Actor

Scrape every grocery store flyer for a US ZIP or Canadian postal code from Flipp: item name, sale price, brand and validity dates across 110+ merchants. Tracks week-over-week price drops and outputs a markdown digest ready for an AI meal planner. No login or API key.

- **URL**: https://apify.com/scrapersdelight/flipp-flyer-digest.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** E-commerce, AI, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$80.00 / 1,000 per merchant flyer digests

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/platform/actors/running/actors-in-store#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 Flyer Scraper — Weekly Deals & Price Drops

Get every grocery store flyer for a **US ZIP or Canadian postal code** in one run — item name, sale
price, brand, and the exact dates the deal runs — across **110+ merchants** from a single input.

Then it does the part nobody else does: **tells you what changed since last week.**

```
🔄 Since last run (2026-08-03)
26 new · 56 cheaper · 0 pricier · 0 ended

Price drops
| Item                                    | Was   | Now   | Save  | Merchant   |
| Fresh Boneless, Skinless Chicken Breast | $8.19 | $6.69 | $1.50 | Starsky    |
| Long Eggplant                           | $3.48 | $1.98 | $1.50 | Food Depot |
```

### What you get

**One row per flyer item:**

| field | notes |
|---|---|
| `merchant`, `merchantId` | the store |
| `name`, `brand` | product as printed on the flyer |
| `price`, `priceRaw` | sale price (`null` when the flyer prints no price — see below) |
| `validFrom`, `validTo` | exactly when the deal runs |
| `isMultiProductBundle` | ⚠︎ flag — see the honesty note |
| `changeType` | `new` · `price_drop` · `price_rise` · `unchanged` |
| `previousPrice`, `priceDelta` | versus your last run |
| `imageUrl` | product cutout image |
| `categories`, `flyerId`, `postalCode`, `locale` | provenance |

**Plus `OUTPUT.md`** — a markdown digest in the key-value store: price drops sorted by biggest saving,
what's new this week, then the full listing grouped by merchant. Paste it straight into ChatGPT/Claude
and ask for a meal plan built around what's actually on sale.

### Pick your own stores

Most people shop at four or five places. Run once in discovery mode to see what's available:

```json
{ "postalCode": "M5V3L9", "listMerchantsOnly": true }
```

That returns every store delivering flyers to your postal code — **115 for downtown Toronto, 51 of
them grocery** — with name, merchant ID, categories and flyer dates. Pick yours, then:

```json
{ "postalCode": "M5V3L9", "merchants": ["sobeys", "Costco", "3384"] }
```

The `merchants` list accepts any mix of:

- **store names**, case-insensitive and partial — `"sobeys"` matches `"Sobeys"`
- **merchant IDs** from the discovery run — `"3384"`
- **pasted flipp.com links** — any 4+ digit run in the URL is read as a merchant ID

Anything that matches nothing is called out by name in the log rather than silently ignored, and an
explicit store list is never truncated by `maxMerchants` (that cap only guards unfiltered runs).

Picking stores also **improves the data**: a real-grocer list measured **91.3% price fill** against
78.9% for the broad category filter.

#### Why stores and not flyer links

Flipp's website is a single-page app — a bogus flyer id and a real one return **byte-identical**
HTML — so a pasted link can't be validated. And flyer IDs are reissued **every week** when new flyers
publish, so a saved flyer link would break every 7 days. Merchant IDs are stable (verified: no
merchant carries more than one), so a saved store list keeps working. Links are still accepted, but
they're read as a merchant hint, not a flyer address.

### Weekly monitoring

Set an Apify **Schedule** and each run diffs against the last one for that postal code. Flyers run
about seven days (`validFrom`→`validTo`), so **weekly is the natural cadence** — point it at the
morning your local flyers drop. Daily works too and costs little; most days will simply report few
changes.

- Run 1 writes the baseline and reports no changes.
- Run 2+ label every row and add the "Since last run" section.
- Snapshots are per postal code + locale, so you can track several areas independently.
- `onlyChanges: true` narrows the dataset to just new + changed rows, ideal for a recurring schedule.

### Input

| input | default | notes |
|---|---|---|
| `postalCode` | — **required** | `M5V3L9` (Canada) or `10001` (US). Partial codes are rejected. |
| `merchants` | — | Your stores: names, merchant IDs or pasted links. Empty = use the category filter. |
| `listMerchantsOnly` | `false` | Discovery run — return the available stores and scrape nothing. |
| `locale` | `en-ca` | `en-ca` or `en-us`. **US and Canada only.** |
| `maxMerchants` | `15` | Uncapped, a dense metro emits ~18,000 items. |
| `categories` | Groceries, Pharmacy, Restaurants | Filters flyers *before* fetching them. |
| `allCategories` | `false` | Take every flyer (mostly hardware/sporting goods). |
| `trackChanges` | `true` | Diff against your previous run. |
| `onlyChanges` | `false` | Emit only what changed (ignored on run 1). |
| `includeMarkdown` | `true` | Write `OUTPUT.md`. |

### Pricing

**$0.08 per merchant flyer digest** — one charge per merchant, *not* per item. A default 15-merchant
run costs about **$1.20**; all 139 merchants in a dense metro about **$11**.

Per-item pricing would be dishonest here: a single postal code yields ~18,000 items, so any sane
per-row rate bills $45–113 for one run. The merchant-week is also the unit you actually care about.
Merchants are charged **before** their rows are pushed, so if you hit a budget cap you get complete
merchant digests rather than a fragment of every one.

### Honest limits

- **~78% of items carry a price.** Some flyer tiles are image-only or promotional; those come back
  with `price: null` rather than a guess.
- **~36% of item names bundle multiple products** under one price — *"RED BULL or GURU ENERGY DRINK,
  $5.50"*. Those are flagged `isMultiProductBundle` and marked ⚠︎ in the digest. Treat them as an
  offer, not a per-SKU price. If you are doing per-SKU price intelligence, filter them out.
- **US and Canada only.** Other locales return no data.
- Change tracking matches on `merchant + normalised name`, because item IDs are re-issued with each new
  flyer. About 1.4% of rows share a merchant+name pair and collapse into one tracked item.
- An invalid-but-well-formed code (e.g. `00000`) makes the upstream API return an empty list rather
  than an error; this Actor fails loudly instead of reporting a silent successful empty run.

Data comes from Flipp's public flyer service. You are responsible for complying with Flipp's Terms of
Service and for how you use the data.

# Actor input Schema

## `postalCode` (type: `string`):

Full Canadian postal code (M5V3L9) or US ZIP (10001). Partial codes are rejected by Flipp.

## `locale` (type: `string`):

en-ca for Canada, en-us for the United States. Flipp is US/CA only — en-gb 404s.

## `merchants` (type: `array`):

Only scrape these stores. Accepts store names (case-insensitive, partial match — "sobeys" matches "Sobeys"), numeric merchant IDs, or pasted flipp.com links. Run once with listMerchantsOnly to see what's available for your postal code. Store IDs are stable week to week; flyer links are not.

## `listMerchantsOnly` (type: `boolean`):

Return the list of stores that deliver flyers to your postal code — name, merchant ID, categories and flyer dates — and scrape nothing else. Use this once to build your "My stores" list, then turn it off.

## `maxMerchants` (type: `integer`):

How many flyers to open. Uncapped a single postal code emits ~18,000 items; 15 keeps a run to roughly 1,500-2,500 rows.

## `categories` (type: `array`):

Only open flyers whose categories include one of these. Default keeps food/pharmacy and drops the 68.9% of items that are hardware, sporting goods etc.

## `allCategories` (type: `boolean`):

Take every flyer regardless of category. Expect a much larger, mostly non-grocery result.

## `trackChanges` (type: `boolean`):

Compare against the previous run for this postal code and label each item new / price\_drop / price\_rise. Needed for a weekly schedule to be meaningful.

## `onlyChanges` (type: `boolean`):

Emit ONLY new items and price changes. Ignored on the first run (no baseline exists yet). Use this for a recurring schedule so each run's dataset is just the deltas.

## `includeMarkdown` (type: `boolean`):

Also write OUTPUT.md to the key-value store — a per-merchant markdown table ready to paste into an AI meal planner.

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

Flipp needs no proxy; datacenter works. Left configurable for rate-spreading.

## Actor input object example

```json
{
  "postalCode": "M5V3L9",
  "locale": "en-ca",
  "merchants": [
    "Sobeys",
    "No Frills",
    "Costco"
  ],
  "listMerchantsOnly": false,
  "maxMerchants": 15,
  "categories": [
    "Groceries",
    "Pharmacy",
    "Restaurants"
  ],
  "allCategories": false,
  "trackChanges": true,
  "onlyChanges": false,
  "includeMarkdown": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per flyer item: merchant, name, price, validity dates, bundle flag and change status vs your previous run.

## `digest` (type: `string`):

Human- and LLM-readable digest: price drops since your last run, what's new this week, then the full listing per merchant.

# 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 = {
    "postalCode": "M5V3L9",
    "locale": "en-ca",
    "merchants": [
        "Sobeys",
        "No Frills",
        "Costco"
    ],
    "maxMerchants": 15,
    "categories": [
        "Groceries",
        "Pharmacy",
        "Restaurants"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/flipp-flyer-digest").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 = {
    "postalCode": "M5V3L9",
    "locale": "en-ca",
    "merchants": [
        "Sobeys",
        "No Frills",
        "Costco",
    ],
    "maxMerchants": 15,
    "categories": [
        "Groceries",
        "Pharmacy",
        "Restaurants",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/flipp-flyer-digest").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 '{
  "postalCode": "M5V3L9",
  "locale": "en-ca",
  "merchants": [
    "Sobeys",
    "No Frills",
    "Costco"
  ],
  "maxMerchants": 15,
  "categories": [
    "Groceries",
    "Pharmacy",
    "Restaurants"
  ]
}' |
apify call scrapersdelight/flipp-flyer-digest --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/flipp-flyer-digest"
        }
    }
}

```

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/zbW4vyNcHH8Qkc2Pi/builds/Veom7Dg35i91bpQZK/openapi.json
