FoodHero Scraper at a glance
FoodHero Scraper is a ready-to-run Apify actor from abotapi for collecting ecommerce data. Its documented output includes record type, open in app, deal id, product and brand, which you can export as JSON, CSV or Excel or read through the Apify API. It costs $2.00 per 1,000 results on the Apify Free plan and from $1.20 on paid plans, with smaller per-run or optional feature charges listed on Apify.
- Runs on
- Apify cloud: console, API, schedules and integrations
- Data type
- Ecommerce, Developer Tools, Automation
- Pricing
- $2.00 per 1,000 results (Free plan), from $1.20 on paid plans
- Output
- Documented below · JSON, CSV, Excel
- Inputs
- 22 options
Scrape FoodHero surplus grocery deals across Canada by area, store or offer ID. Extract products, brands, regular and discounted prices, savings, portions left, best-before dates and CO2 savings, plus store names, chains, addresses, coordinates and types.
What FoodHero Scraper can do
- Two ways to find deals. Search any Canadian area by coordinates or place name, or paste a store or offer id directly. Leave the area empty to cover every FoodHero city in Canada at once.
- Full deal economics on every row. Regular price, deal price, discount percent, portions left, best-before date and CO2 saved, all on one flat record.
- A pasted store returns something. Every deal it lists right now (a store with many deals returns many rows, and Max items bounds how many), or a single store row when it currently has none, so a store you named that FoodHero still lists does not come back empty just because it has no surplus today.
- Optional store enrichment. In search mode, add each store's phone and website to every row, only when you turn it on, so a plain run stays fast.
- Built for schedules. Incremental mode returns only new, changed and reappeared deals on recurring runs, and EXPIRED rows are only produced after a run that read every area to the end without a limit or a read error stopping it (see the notes under "Resume and recurring updates").
- Resume without paying twice. Point Resume at an interrupted run and the deals it already saved are skipped.
Choose what to collect
These inputs let you configure FoodHero Scraper - Grocery Deals, Prices & Stores. Open the actor on Apify to enter your values and review the current options.
| Input | What it controls |
|---|---|
modestring · required | Search walks the FoodHero deals feed around each area you give (or every covered Canadian city when you give none). URL mode reads only the store or offer ids you paste. |
locationsarray | Search mode only. One entry per area: coordinates as lat,lng (45.5019,-73.5674), or a Canadian place name (Toronto, Montreal, Vancouver) which is looked up for you and cached. Leave empty to cover every FoodHero city in Canada at once. |
radiusKminteger | Search mode only. Keep only stores within this distance of each area, measured on the store's own coordinates. Ignored in URL mode. |
searchStoreNamestring | Keep only stores whose name contains this text, matched without case (for example Sobeys, IGA, Safeway, Foodland, Thrifty). Applies in BOTH search and URL mode. |
includeSoldOutOffersboolean | Off by default, so only deals with portions left are returned. Turn on to also return deals that are listed but already gone (zero left). Applies in BOTH search and URL mode. |
minDiscountPercentinteger | Optional. Keep only deals discounted by at least this percentage. Applied to the rows the run walks (the feed has no server-side filter), so rows below it are not returned. Leave empty for no filter. Applies in BOTH search and URL mode. |
maxPricenumber | Optional. Keep only deals whose current (discounted) price is at or below this amount in Canadian dollars. Applied to the rows the run walks. Leave empty for no filter. |
Show 15 more inputs
| Input | What it controls |
|---|---|
departmentNamesarray | Optional. Keep only deals in these departments, matched without case against the departmentName column (for example Bakery, Produce, Dairy). Leave empty for all departments. |
storeTypearray | Optional. Keep only rows from stores of these types, matched without case against the storeType column (for example Grocery). Leave empty for all store types. |
enrichStoreDetailsboolean | Off by default. Turn on to fetch each store's detail page and add its phone, website, description and opening hours to every row. This makes one extra request per store, so it is slower. |
urlsarray | URL mode only. One target per entry. FoodHero has no public web page for a store, so paste its 24-character id: a bare id is read as a store, or prefix it - store/ or offer/ - to be explicit. A store returns every deal it lists right now, up to Max items (or one store row when it lists none); an offer returns that one deal. |
maxItemsinteger | Stop after this many records (0 = no limit; the run then stops when the deals feed runs out for every area). |
maxPagesinteger | Optional. Deals are read 50 at a time; 0 means as deep as the feed goes for each area, bounded by Max items. |
resumeFromRunIdstring | Paste a previous run ID or dataset ID to continue a collection that was interrupted, without returning the records it already saved. Use this for ONE unfinished run; for recurring updates use Incremental mode below instead. |
incrementalModeboolean | Turn this on for daily or recurring monitoring. The first run returns every matching record as NEW. Later runs normally return only NEW, UPDATED and REAPPEARED records. Turn on Emit unchanged or Emit expired only when you also want those rows returned (and billed). State is kept separately for each search and filter setup; use State key to name or deliberately share a monitoring campaign. |
stateKeystring | Optional. Name this monitoring campaign to keep its state stable, or deliberately share state across differently configured runs. Leave empty to let the actor derive a key automatically from the search and filter settings. |
emitUnchangedboolean | Off by default. Turn on to also return records that have not changed since the last run, marked UNCHANGED. This returns, and bills, extra rows you already have. |
emitExpiredboolean | Off by default. Turn on to also return records that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search. |
ignoreFieldsForChangesarray | Incremental mode only. Field names (for example itemLeft) whose changes should NOT make a deal count as UPDATED. The fields stay in the output; they are just left out of change detection and changedFields. Changing this list does not cause a wave of UPDATED rows. distanceKm, openingHours and pickUpInstructions are always ignored. |
mcpConnectorsarray | Optionally send collected deals into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify, Settings, API & Integrations, then select it here. Notion gets a rich page-per-deal export; other connectors get a best-effort write or digest. Leave empty to skip; never changes the dataset output. |
notionParentPageUrlstring | URL or id of the Notion page under which deal pages are created. Required to enable the Notion export; ignored by other connectors. |
maxNotifyListingsinteger | Cap on deals written to each connector per run. Does not affect the dataset. |
Fields in the output
The documented dataset includes the fields below. Availability can depend on the source page and selected mode.
recordTypeRecord type (text)urlOpen in app (link)offerIdDeal ID (text)productNameProduct (text)productBrandBrand (text)productDescriptionDescription (text)productSizeSize (text)departmentNameDepartment (text)priceRegular price (number)discountPriceDeal price (number)discountPercentDiscount % (number)currencyCurrency (text)Show 30 more fields
itemLeftPortions left (number)isSoldOutSold out (boolean)isLastCallLast call (boolean)isNewNew (boolean)isFrozenFrozen (boolean)bestBeforeBest before (epoch) (number)bestBeforeAtBest before (text)co2VolumeCO2 saved (kg) (number)productImageUrlPhoto (image)storeIdStore ID (text)storeUrlStore link (link)storeNameStore (text)storeTypeStore type (text)chainIdChain ID (text)addressAddress (text)cityCity (text)stateProvince (text)postalCodePostal code (text)countryCountry (text)latitudeLatitude (number)longitudeLongitude (number)distanceKmDistance (km) (number)phoneStore phone (text)websiteStore website (link)productCountProducts at store (number)storeOfferCountDeals at store (number)changeTypeChange (text)changedFieldsChanged fields (array)firstSeenAtFirst seen (text)lastSeenAtLast seen (text)Example output
An example from this actor’s documentation. Values are illustrative; this is not a fresh live result.
{
"recordType": "offer",
"offerId": "64f1a2b3c4d5e6f7a8b9c0d1",
"url": "https://foodhero.app.link/offer/64f1a2b3c4d5e6f7a8b9c0d1",
"productName": "Frozen Berry Medley",
"productBrand": "Sample Orchards",
"productDescription": "Mixed berries, family pack",
"productSize": "600 G",
"departmentName": "Frozen",
"price": 12.99,
"discountPrice": 6.49,
"discountPercent": 50,
"currency": "CAD",
"itemLeft": 4,
"isSoldOut": false,
"isLastCall": false,
"isFrozen": true,
"isNew": true,
"co2Volume": 0.12,
"bestBefore": 1798761600,
"bestBeforeAt": "2027-01-01T00:00:00+00:00",
"storeId": "64f1a2b3c4d5e6f7a8b9c0aa",
"storeUrl": "https://foodhero.app.link/store/64f1a2b3c4d5e6f7a8b9c0aa",
"storeName": "Sample Grocery Market",
"storeType": "Grocery store",
"chainId": "64f1a2b3c4d5e6f7a8b9c0bb",
"address": "12 Sample Street, Springfield, ON, K1A0B1",
"city": "Springfield",
"state": "ON",
"postalCode": "K1A0B1",
"country": "CA",
"latitude": 45.42,
"longitude": -75.7,
"distanceKm": 2.3,
"productCount": 180,
"storeOfferCount": 42
}Ways to use this data
- Deal-hunting apps and browser extensions: surface today's steepest surplus discounts for a city or a shortlist of favourite stores.
- Food-waste and sustainability research: track CO2 saved and portions rescued across Canadian grocery chains over time.
- Price and discount monitoring: watch a handful of stores every morning and get back only what is new or has changed.
- Retail and chain analytics: compare discount depth, sell-through and department mix across banners and provinces.
- Local deal roundups and community boards: pull today's best-before-soon bargains near a neighbourhood.
Before you run
Start with a small input and check the resulting records against your expected fields. Source content and actor options can change; the current Apify listing is the reference for availability and pricing.
Questions about this actor
What data does FoodHero Scraper return?
The documented output for FoodHero Scraper includes record type, open in app, deal id, product, brand, description, size and department. Availability of each field depends on the source page and the selected mode.
How much does FoodHero Scraper cost?
It costs $2.00 per 1,000 results on the Apify Free plan and from $1.20 on paid plans, with smaller per-run or optional feature charges listed on Apify. The Apify listing shows the current rate for every plan.
Can I get only new or changed deals on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. From the second run on, you get back only NEW, UPDATED and REAPPEARED deals, and unchanged ones are not billed. A change to a store's opening hours or pick-up note does not by itself make a deal UPDATED (those columns, like distanceKm, are always left out of change detection).
What is the difference between Resume and Incremental mode?
Resume finishes ONE interrupted run: paste that run's ID and the deals it already saved are skipped, so you do not pay twice for the same collection. Incremental mode is for running the same search again and again (daily, say) and getting back only what changed since the previous run. Use Resume for a run that stopped partway through; use Incremental mode for recurring monitoring.
Why did I get EXPIRED or UPDATED rows for deals that did not change?
Two known cases in incremental mode. If a place name in locations could not be looked up, that area is skipped with only a log warning, and its tracked deals can come back as EXPIRED; use lat,lng coordinates instead. If store details are on and a store's lookup failed, its rows lose phone and website and come back as UPDATED with only those fields in changedFields. Both are described under "Resume and recurring updates" above.
Can I use it with AI agents or MCP?
Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.
