# Sobeys & Voila Scraper: Prices, Flyers & Scene+ Deals (`vant47/sobeys-voila-scraper`) Actor

Get Voila online prices and Sobeys, Safeway Canada, IGA, FreshCo, Foodland and Thrifty Foods flyer offers by Canadian postal code: prices, Scene+ text, dates, unit prices and stock when supplied. US$0.004 per product or flyer item plus US$0.005 per run. No login.

- **URL**: https://apify.com/vant47/sobeys-voila-scraper.md
- **Developed by:** [fortbrand](https://apify.com/vant47) (community)
- **Categories:** E-commerce, Automation
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.20 / 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

## Sobeys & Voila Scraper: Prices, Flyers & Scene+ Deals

For grocery price analysts, brands and comparison services monitoring the Sobeys family in Canada. Collect **Voila regional online prices**, or **current regional flyer entries** from Sobeys, Safeway Canada, IGA, FreshCo, Foodland and Thrifty Foods. Keep prices, public promotions, Scene+ wording, dates and comparison units when supplied. About **US$0.004 per delivered product or flyer item**: **20 observations cost US$0.085 including the US$0.005 minimum start charge**. No retailer login is needed.

Choose a banner, enter Canadian postal codes and set your result limit. The common retail field names let you combine snapshots in a database or spreadsheet. Every observation identifies its channel through `priceScope`, with source identifiers and links for review.

### Choose a mode

`banner: "voila"` collects the online product catalogue. Search for milk, coffee or chicken, browse categories, or monitor exact product IDs. Voila's own full-postal-code lookup resolves the delivery region. The Actor selects that region and checks the site's returned identity before collecting its prices.

`banner: "sobeys"`, `"safeway-ca"`, `"iga"`, `"freshco"`, `"foodland"` or `"thrifty-foods"` collects that banner's current weekly flyer entries. Supply locations and optional search terms. Empty searchTerms includes all matching named entries with numeric advertised prices. The public flyer provider resolves the postal area, then the Actor verifies each item against its current flyer.

Voila observations use `priceScope: "regional_online_catalogue"` and a delivery-region UUID in `storeId`. Flyer observations use `priceScope: "regional_weekly_flyer"`; their region evidence appears in `geography`, while physical store fields stay null. Preserve that scope when exporting or comparing baskets.

### Understand the prices

For Voila, `price` is the current advertised online price. `regularPrice` retains the source base price, and `onSale` is true when the source supplies a lower sale price. Conditional promotions remain in `offers`. A "Buy 1 Get 1 Free (Add 2)" condition accompanies the single-product price; retain its quantity requirement in a basket comparison.

For flyers, read `priceBasis`, `advertisedQuantity` and `priceText` together. A printed "2 for $5" has a $5 advertised bundle total and quantity 2. An entry can cover selected varieties or a product group. A supplied individual comparison price remains in `unitPrice` with its unit.

Scene+ points stay in `scenePlusOffers` and promotion text. A numeric `memberPrice` requires explicit member-price evidence. For example, a source flyer showing $2 member pricing and $2.99 without a Scene+ card retains both amounts and sets `priceRequiresMembership`. Points are never converted to money.

Catchweight products preserve the source typical quantity and unit in `typicalWeight`, with `sellByWeight` and `priceIsEstimate` identifying their pricing basis. Keep these alongside the comparison price when analysing meat or produce.

### What you get

A readable `verdict` comes first, followed by the common retail schema. Unknown values remain null.

| Fields | Meaning |
| --- | --- |
| `retailer`, `country`, `currency` | Source retailer, CA and CAD. |
| `storeId`, `storeName`, `location` | Voila delivery region and the requested postal code. Flyer source resolution is retained separately in geography. |
| `productId`, `sku`, `gtin` | Source identifiers, kept as text. Flyer product IDs begin `flyer:`; SKU lists stay lists, and GTIN needs barcode provenance. |
| `title`, `brand`, `size` | Source title, explicit brand and supplied package-size label. |
| `price`, `regularPrice`, `onSale` | Current advertised amount, base price and numeric sale flag. |
| `promoText`, `promoType`, `promoStart`, `promoEnd` | Promotion wording, source type and supplied validity dates. |
| `memberPrice`, `memberProgram` | Explicit member price and identified loyalty programme. |
| `unitPrice`, `unitOfMeasure` | Comparison amount and its unit, such as PER_100G or PER_100ML. |
| `inStock`, `stockLevel` | Online availability and any supplied quantity. |
| `rating`, `reviewCount` | Source aggregate review values when supplied. |
| `category`, `url`, `imageUrl`, `scrapedAt` | Category path, product/source page, image and UTC observation time. |

Voila extras include `regionId`, `retailerRegionId`, `requestedLocations`, `locationResolvedBy`, `deliverable`, `ocadoProductId`, `categoryPath`, `dietaryAttributes`, `sellByWeight`, `priceIsEstimate`, `typicalWeight`, `catchWeight` and optional `details`.

Flyer extras include `flyerId`, `flyerName`, `merchantId`, `itemId`, `priceText`, `priceBasis`, `advertisedQuantity`, `description`, `disclaimer`, `flyerUrl` and `geography`. Both modes provide `banner`, `priceScope`, `isDeal`, `offers`, `scenePlusOffers`, `sourceUrl` and `sourceJobId`.

The overview dataset view puts prices, promotions, units, stock and region together. The deals view emphasizes offer evidence; the full-data view retains all fields. A free `SUMMARY` record reports products, regions, deals, source outcomes, duplicates, notes and spending-limit status. `DEALS` exports delivered deal rows; larger exports list JSON parts. Dataset notes use `recordType: "NOTE"`, and priced observations use `recordType: "PRODUCT"`.

### Start in three steps

1. Choose Voila online or a flyer banner.
2. Enter postal codes and your searches, product IDs or supported links.
3. Set limits, run, review SUMMARY and export JSON, CSV or Excel.

The prefill searches milk for M5V 2T6 with a 20-product cap. On 9 November 2026 it returned 20 distinct products in the retailer-resolved VoilaGTA region. Two larger cloud runs each delivered 1,200 unique observations across several searches. Ten independent live product-page checks matched current prices, regular prices, comparison units, stock and promotion conditions.

### Inputs

| Input | Behaviour |
| --- | --- |
| `banner` | `voila` by default; six other choices collect banner flyers. |
| `searchTerms` | Voila searches each term in each region, trimming terms to 50 characters. Flyers match any supplied term against advertised names. |
| `startUrls` | Voila product, category, search or promotions links. |
| `productIds` | Voila retailer IDs such as `267314EA`, `507464EA` or `218807KG`. |
| `locations` | Full Canadian postal codes, such as M5V 2T6 and H2X 1Y4. Required for flyers. |
| `storeIds` | Voila delivery-region UUIDs from earlier output; take precedence over locations. |
| `maxItemsPerSearch` | Voila: new rows per source and region. Flyers: new entries across current flyers for one postal selection. Default 20, maximum 10,000. |
| `maxItems` | Global observation cap; default 20, maximum 20,000. |
| `dealsOnly` | Voila keeps sales or public offers, including points offers. Flyer mode already collects advertised offers. |
| `includeDetails` | Adds Voila product-page descriptions and label fields, including ingredients and nutrition when supplied. No additional product fee. |
| `proxyConfiguration` | Automatic datacenter first, then Canadian residential after refusal. Explicit disable, groups, country or custom proxies are respected. |

Empty Voila locations and storeIds use the website-assigned region. For a repeatable basket, retain the region ID from a successful postal lookup. Several postal codes resolving to one Voila region produce one observation per product, with the requests grouped in `requestedLocations`.

Compare two delivery regions:

```json
{
  "banner": "voila",
  "searchTerms": ["milk", "coffee"],
  "locations": ["M5V 2T6", "H2X 1Y4"],
  "maxItemsPerSearch": 20,
  "maxItems": 80
}
```

Monitor a product and its label:

```json
{
  "productIds": ["267314EA"],
  "storeIds": ["4b2788bd-7207-4a8e-b5ef-0ad94345846b"],
  "includeDetails": true,
  "maxItems": 1
}
```

Collect Sobeys flyer entries:

```json
{
  "banner": "sobeys",
  "locations": ["M5V 2T6"],
  "maxItemsPerSearch": 20,
  "maxItems": 20
}
```

Other flyer examples use `safeway-ca` with T2P 1J9, `iga` with H2X 1Y4, and `thrifty-foods` with V8W 1P6. For Voila categories, use a current category link such as `https://voila.ca/categories/dairy-eggs/WEB1100610`; for public promotions use `https://voila.ca/promotions`. Categories are verified in the selected region before delivery.

### Real output example

Selected fields from the 9 November 2026 prefill:

```json
{
  "retailer": "Voila by Sobeys",
  "storeId": "4b2788bd-7207-4a8e-b5ef-0ad94345846b",
  "storeName": "VoilaGTA",
  "location": "M5V 2T6",
  "productId": "507464EA",
  "gtin": null,
  "title": "Sealtest 2% Milk Partly Skimmed 4 L",
  "price": 6.49,
  "regularPrice": 6.49,
  "unitPrice": 0.16,
  "unitOfMeasure": "PER_100ML",
  "inStock": true,
  "priceScope": "regional_online_catalogue"
}
```

The same product can have different regional prices: another test returned product 267314EA at $4.69 in VoilaGTA and $5.49 in QC Region 1. The region remains part of the observation's identity.

### Pricing and limits on charges

Processing fees are in US dollars; grocery price observations are in Canadian dollars.

One online product is one distinct product in one delivery region. One flyer item is one current named priced entry in one flyer. Duplicate inputs, overlapping sources and a reused flyer across postal areas do not create duplicate charges within a run.

| Observations | Base | Bronze | Silver | Gold |
| --- | --- | --- | --- | --- |
| 20 | $0.08 | $0.076 | $0.072 | $0.064 |
| 100 | $0.40 | $0.38 | $0.36 | $0.32 |
| 1,000 | $4.00 | $3.80 | $3.60 | $3.20 |

Add the $0.005 minimum start charge, covering public visitor or flyer setup. At the usual 512 MB setting it is charged once; Apify charges one start unit per GB, with a minimum of one. Details have no separate product fee. Notes, summaries, retries, failures, duplicates and empty matches have no product charge; the platform start charge still applies.

Apify's maximum total charge is checked before another observation is delivered. The Actor stops cleanly at that limit and records it in SUMMARY. Result counts are caps, so fewer source matches can produce fewer rows.

### Monitor prices every day

Save a task with a stable basket or category and a verified region. In Apify Console, create a schedule, choose the task and timezone, and connect the completed-run webhook to your database or workflow.

For Voila, join snapshots on `banner + storeId + productId`; for flyers, use `banner + flyerId + itemId`. Keep `scrapedAt` as observation time. Store current prices, base prices, comparison units and offer conditions separately. Each run collects a fresh snapshot and charges for that run's delivered observations.

Check SUMMARY's `freeFailures`, `outcomes` and `notes` before accepting a snapshot. Make, Zapier, n8n, Sheets and database workflows can consume the dataset and preserve gaps when source coverage is incomplete.

### API and AI agents

Use your own Apify token:

```bash
curl -X POST \
  'https://api.apify.com/v2/acts/vant47~sobeys-voila-scraper/runs' \
  -H 'Authorization: Bearer YOUR_APIFY_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"searchTerms":["milk"],"locations":["M5V 2T6"],"maxItems":20}'
```

```python
from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("vant47/sobeys-voila-scraper").call(
    run_input={"searchTerms": ["milk"], "locations": ["M5V 2T6"], "maxItems": 20}
)
rows = list(client.dataset(run["defaultDatasetId"]).iterate_items())
```

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('vant47/sobeys-voila-scraper').call({
  searchTerms: ['milk'], locations: ['M5V 2T6'], maxItems: 20
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

#### Use with AI agents (MCP)

Connect to `https://mcp.apify.com`. Ask for 20 Voila milk products for M5V 2T6, or current Sobeys flyer prices for that postal area. Supply the banner, source selection and cap. Ask the assistant to inspect SUMMARY and preserve region identity, null values, membership requirements and bundle conditions.

### Limits

- Coverage is Voila regional online catalogues and current regional banner flyers. Exact-store shelf and pickup prices are outside coverage; flyer stock and physical store fields stay null.
- Assortments differ by region. The website default can be empty, and some full postal codes have no exact source match.
- Online availability is a source signal, not an audited shelf count. Checkout fees, taxes, substitutions and account-specific eligibility are outside the observation.
- Barcodes need source provenance. Missing dates, quantities, ratings or member prices remain null. Flyer SKU groups are never relabelled as UPCs.
- Flyer mode excludes recipes, social links and panels without numeric prices. Optional nutrition and label details apply to Voila; flyer descriptions and conditions are included directly.
- Source access can change. Retries keep a fresh anonymous context and verify the requested region. Explicit proxy choices prevent automatic switching to another selection.
- No retailer login, account cookies, delivery-slot reservations, shopper-basket changes or CAPTCHA solving. Historical backfill is unavailable.
- Voila has a 150-page safety limit per source and region. Jobs stop cleanly near their configured timeout, retain delivered rows and report `timeLimitReached`.

### FAQ

**Can I compare Toronto and Montreal?** Enter one postal code for each, or reuse their verified region IDs.

**Do details cost extra?** There is no extra product fee. Public label fields depend on what the product page supplies.

**How do I export deals?** Use `dealsOnly` for Voila, or read the free DEALS JSON output after a mixed run.

**What happens with invalid input?** Parseable invalid selections produce free notes and SUMMARY. Apify may reject schema-invalid JSON before a run starts.

**How do I avoid duplicates?** Repeated inputs are deduplicated within a run. Retain the source identifiers when combining separate snapshots.

# Actor input Schema

## `banner` (type: `string`):

Voila collects regional online prices. Other banners collect regional weekly flyer entries, with dates and printed conditions. Flyer prices are advertised amounts and can be bundle totals; physical-store shelf prices are not verified.

## `searchTerms` (type: `array`):

Voila: each term is searched in each delivery region. Flyers: filter product names by any supplied term; leave empty for all current named flyer entries.

## `startUrls` (type: `array`):

Public https://voila.ca product, category or promotions links. Region selection always comes from locations or storeIds, never from URL query parameters.

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

Optional Voila retailer product IDs, such as 267314EA or 507464EA. These are SKUs, not UPC barcodes.

## `locations` (type: `array`):

Full Canadian postal codes. Voila resolves a delivery-region catalogue. Banner flyers use the source-resolved postal area. These are online/flyer observations, not verified physical-store shelf prices.

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

Voila online mode only: exact delivery-region UUIDs from previous output, taking precedence over locations. Flyer mode requires locations; physical store numbers are not supported.

## `maxItemsPerSearch` (type: `integer`):

Voila: new rows per source and delivery region. Flyers: new entries per postal-code selection across current flyers.

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

Global product/region or flyer-item cap. $0.004 per observation plus a $0.005 start charge: 20 observations cost $0.085 at base pricing.

## `dealsOnly` (type: `boolean`):

Keep products with a sale or a public promotion, including Scene+ points and conditional multi-buy offers. Points are not deducted from prices.

## `includeDetails` (type: `boolean`):

Voila only: add product-page description, label fields, ingredients and nutrition when supplied, with no extra product fee. Flyer entries always include their public description and printed conditions.

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

Automatic default: default datacenter proxy first, then Canadian residential after refusal. Explicit disable, groups, country or custom proxies are respected and never silently overridden.

## Actor input object example

```json
{
  "banner": "voila",
  "searchTerms": [
    "milk"
  ],
  "locations": [
    "M5V 2T6"
  ],
  "maxItemsPerSearch": 20,
  "maxItems": 20,
  "dealsOnly": false,
  "includeDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `products` (type: `string`):

No description

## `dealsView` (type: `string`):

No description

## `dealsOnly` (type: `string`):

No description

## `summary` (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 = {
    "banner": "voila",
    "searchTerms": [
        "milk"
    ],
    "locations": [
        "M5V 2T6"
    ],
    "maxItemsPerSearch": 20,
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("vant47/sobeys-voila-scraper").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 = {
    "banner": "voila",
    "searchTerms": ["milk"],
    "locations": ["M5V 2T6"],
    "maxItemsPerSearch": 20,
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("vant47/sobeys-voila-scraper").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 '{
  "banner": "voila",
  "searchTerms": [
    "milk"
  ],
  "locations": [
    "M5V 2T6"
  ],
  "maxItemsPerSearch": 20,
  "maxItems": 20
}' |
apify call vant47/sobeys-voila-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,vant47/sobeys-voila-scraper"
        }
    }
}
```

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/itztO3kGhTSMgQ84I/builds/A54GrFU8kLv8JhZ34/openapi.json
