Flashfood Scraper - Discounted Groceries, Prices & Availability avatar

Flashfood Scraper - Discounted Groceries, Prices & Availability

Pricing

from $1.80 / 1,000 deal or store records

Go to Apify Store
Flashfood Scraper - Discounted Groceries, Prices & Availability

Flashfood Scraper - Discounted Groceries, Prices & Availability

Scrape Flashfood discounted groceries and stores by location or store. Extract current and original prices, savings, quantity, best-before date, department, and store details. Track NEW, UPDATED, REAPPEARED, and EXPIRED items with incremental runs, resume, and MCP export.

Pricing

from $1.80 / 1,000 deal or store records

Rating

0.0

(0)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

0

Bookmarked

5

Total users

2

Monthly active users

4 days ago

Last modified

Categories

Security

Limited permissions

Share

Flashfood Scraper: Grocery Deals, Prices & Store Info

Flashfood Scraper reads the discounted grocery items and the stores behind them, directly from the same service the official Flashfood app uses, for any location in the US or Canada. Search around one or more points and get every store in range plus its surplus deals (price, original price, discount percent, quantity available, best-before date), or paste store links or ids for a direct per-store read. It also tracks availability across runs (NEW, UPDATED, REAPPEARED, EXPIRED), can resume an interrupted pull, and can pipe results into your apps through MCP connectors.

Why This Scraper?

  • Two ways to find deals. Search by location (city, postal code, or exact coordinates) or paste store links or ids directly. No need to guess store urls.
  • Full deal detail per item. Price, original price, discount percent, quantity available, best-before date and department in one record, with the parent store flattened in for convenience.
  • Multi-location coverage. Give several locations in one run and each gets a fair share of the record budget, so a wide area does not starve any single city.
  • Store records on demand. Turn on store output to also get name, banner, address, hours and pickup instructions for every store you touch.
  • Availability monitoring. Recurring runs classify every record as NEW, UPDATED, UNCHANGED, REAPPEARED or EXPIRED, with state you can name or share across campaigns.
  • MCP connector export. Send results into Notion or other connectors as an extra delivery step, without changing the dataset.
  • Structured failures. A blocked or broken run fails loudly with a clear message instead of quietly returning an empty dataset.

Use Cases

  • Deal-alert apps and bots: monitor one or more cities on a schedule and notify users only about NEW and UPDATED discounted items.
  • Budget grocery shopping and meal planning: pull current deals near you to plan a shop around the biggest discounts and soonest best-before dates.
  • Food-waste and sustainability research: track how much surplus food is listed, at what discount, and how quickly it disappears (EXPIRED) across regions.
  • Grocery banner and pricing intelligence: compare which store banners list the most deals, at what markdown, in a given metro area.
  • Community and food-assistance programs: find EBT-eligible discounted items near a neighborhood to route people to available low-cost food.

Data You Get

Sample shape: values are illustrative placeholders, not from a live listing.

FieldExample
kind"item" (also "store" when store output is on)
recordId / itemId"6198765432abcdef0fedcba9"
urlFlashfood store link. The website has no per-store page, so it opens the locations page; use storeId or apiUrl to identify the store
apiUrlMachine-readable link to the store the deal belongs to, built from storeId
title"Sample Brand Multigrain Bread"
price2.49
originalPrice4.99
discountPercent50.1
currency"USD" (also "CAD")
quantityAvailable5
bestBefore"2026-11-05T00:00:00+00:00"
department / legacyDepartment"Bakery" / "Bakery"
imageUrlproduct photo link
listedAt"2026-10-30T09:15:00.000Z"
isSnapEligible / storageTreatmentfalse / "NONE"
storeId / storeName"6123456789abcdef01234567" / "Sample Grocery Market"
banner"Sample Banner Co."
street / city"100 Example Ave" / "Sample City"
stateCode / countryCode"MI" / "US"
latitude / longitude42.33 / -83.05
imageGallerylist of extra product photo links (may be empty)
changeType / changedFields"NEW" / [] (incremental mode only; values NEW, UPDATED, UNCHANGED, REAPPEARED, SOLD_OUT, EXPIRED)
firstSeenAt / lastSeenAt"2026-01-01T00:00:00+00:00" (incremental mode only)
rawthe complete source object

Store records (kind=store, when store output is turned on) carry the store's own name, banner, address, coordinates, hours, EBT support, and localized pickup and checkout instructions, plus their own raw source object.

How to Use

  1. Pick a mode: search (locations) or url (store links or ids).
  2. In search mode, list one or more locations (a city, postal code, or lat,lng) and a radius; in url mode, list store links or ids (see the URL mode shapes below).
  3. Turn on store output if you also want one record per store, and set Max items to control run size and cost.
  4. Click Start, then download the dataset as JSON, CSV or Excel, or read it through the API.

URL mode shapes. Each entry in storeInputs (or its alias urls) can be:

ShapeExample
Store by idapp.shopper.flashfood.com/api/v1/stores/629cec56061cc242985326fd
Store items by idapp.shopper.flashfood.com/api/v1/items/?storeIds=629cec56061cc242985326fd
Stores near a pointapp.shopper.flashfood.com/api/v1/stores?latitude=42.33&longitude=-83.04&maxDistance=10000 (maxDistance in meters, optional)
Bare store id629cec56061cc242985326fd
Website locations pageflashfood.com/en/locations carries no store id, so it is skipped with a warning in the log

Behaviour change: url mode with the field left out or empty now scrapes one example per shape (the form is pre-filled with all of them), instead of one store. Provide your own entries to scrape only those.

Search one city (defaults):

{
"mode": "search",
"locations": ["Detroit, Michigan"],
"maxItems": 20
}

Several locations, wider net:

{
"mode": "search",
"locations": ["Toronto, Ontario", "42.33,-83.05"],
"radiusKm": 25,
"maxItems": 100
}

A specific store by id or link:

{
"mode": "url",
"storeInputs": ["0123456789abcdef01234567"]
}

Recurring deal monitoring:

{
"mode": "search",
"locations": ["Detroit, Michigan"],
"incrementalMode": true,
"stateKey": "detroit-daily",
"emitExpired": true,
"maxItems": 200
}

Run it from your code

Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/flashfood-grocery-deals-scraper").call(run_input={"mode": "search", "locations": ["Detroit, Michigan"]})
for deal in client.dataset(run["defaultDatasetId"]).iterate_items():
print(deal["title"], deal["price"], deal["originalPrice"])

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/flashfood-grocery-deals-scraper').call({ mode: 'search', locations: ['Detroit, Michigan'] });
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.

Filters, store-only runs and the network sweep

Search mode accepts content filters that run before any row is counted, billed or tracked: countries (CA, US), banners, keywords (word-prefix match on the item name), departments (English or French), minDiscountPercent, maxPrice, expiringWithinDays and maxStores (nearest first). storesOnly returns store records only (with itemCount), billed as ordinary dataset items; fullNetwork sweeps a built-in list of about 45 Canadian and US metro areas instead of your locations. radiusKm goes up to 400.

Item records also carry name (same as title), gtin when the source exposes a product code, savings, daysToExpiry and departmentFr; store records carry address, website and itemCount. In incremental mode UPDATED rows add previousPrice, previousQuantity and unitsSoldSinceLastRun. With Emit expired records on, an item that vanished from a store read in the same run is returned as SOLD_OUT (turn off with emitSoldOut), anything else gone as EXPIRED. An item that is still listed but no longer matches your filters is never reported as sold out or expired. The dataset has three views: Results, Stores (kind=store rows) and Changes (incremental mode).

Locations, radius and the 100-store cap

A single search query returns at most 100 stores around a point, nearest first, and the source ignores paging. For a small town this is normally everything nearby; for a dense metro area, cover it with several nearby locations (or a smaller radius) rather than one huge radius. Place names are resolved to coordinates automatically; lat,lng pairs skip that step.

Tip: with store output on, each location emits its store records first and they count toward that location's share of Max items. In a busy area the store records alone can use up the whole budget before any deal is returned (for example, 30 stores in range with Max items 20 returns 20 store records and no deals). Raise Max items, or leave store output off when you only want deals. Store records are also not de-duplicated across locations, so two overlapping locations return the same store twice.

Tip: if a place name cannot be resolved to coordinates (a typo, or the geocoding provider is briefly unreachable), that location is skipped for the run and a warning is logged, but the run still continues and succeeds with the locations that did resolve. This means a run can look like a complete, healthy scan (no failure, no empty dataset) even though one location's stores were never read at all. On a monitoring run with Emit expired records turned on, when at least one other location did resolve, the skipped location's previously-tracked deals are returned (and billed) as EXPIRED, because the skip is not counted as an unfinished location and the run is treated as a complete scan. If you rely on EXPIRED for billing or inventory decisions, check the run log for "could not be resolved to coordinates" warnings before trusting an EXPIRED result, and prefer lat,lng coordinates for locations that must always be scanned.

Resume and recurring updates

  • Resume (resumeFromRunId) continues one interrupted run: paste its run or dataset ID and the actor skips every deal already collected there, so you don't pay twice for the same deals. Store records (store output on) are the exception: they are returned again on the resumed run. If the previous run or dataset cannot be read, a warning is logged and the run starts from scratch.
  • Incremental mode (incrementalMode) is for running this same search again and again (for example daily) and getting only what changed. Each record is classified NEW, UPDATED (with changedFields), UNCHANGED (suppressed and not billed unless emitUnchanged is on), REAPPEARED or EXPIRED (only after a run that scanned the whole tracked scope, and only with emitExpired). stateKey names or shares the stored state. With incremental mode off, output is exactly as before.

Tip: resumeFromRunId and incrementalMode can be combined, but a record that is skipped because resume already has it is not re-checked against the live source that run: its stored fingerprint and last-seen time stay exactly as they were when it was last actually scraped, even if its price or quantity has since changed. That real change only surfaces on a later run of the same monitoring campaign that does not use resumeFromRunId. Reserve resumeFromRunId for finishing one interrupted pull, and let the next plain scheduled run refresh the campaign's state.

Tip: Emit expired records pushes every record the current run determines is gone from a fully-scanned scope, and that batch is not capped by Max items; Max items governs how many NEW/UPDATED/UNCHANGED/REAPPEARED records the scan itself collects. If a monitoring campaign accumulates a large tracked baseline (many stores, many deals, over weeks), a run where most of that baseline has genuinely disappeared can push far more EXPIRED rows than Max items in one go. Budget for that when sizing a long-running campaign, or split a large area into several smaller stateKey campaigns.

Tip: with several locations, each location stops at its share of Max items (Max items divided by the number of locations). If a location stops at its share but the run as a whole stays under Max items, the run still counts as a complete scan, so that location's live deals that were not reached are returned as EXPIRED. On monitoring runs with Emit expired records, use one location per stateKey campaign or set Max items to 0.

Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.

What gets written to the connector: a condensed, human-readable summary of each record, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.

  1. Authorize a connector once under Apify → Settings → Integrations (Notion, Linear, Airtable, or Apify).
  2. Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
  3. For Notion, also set notionParentPageUrl to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

Input Parameters

ParameterTypeDefaultDescription
modestringsearchsearch (locations) or url (store links or ids).
locationsarraynone (the form is pre-filled with Detroit, Michigan; a search run with no location searches it)Search mode: city or place names, or lat,lng coordinates.
radiusKminteger40Search mode: how far from each point to look, 1 to 400 km.
storeInputsarrayempty (the form is pre-filled with one example per shape; url mode with the field left out or empty scrapes those)Url mode only (ignored in search mode): store ids, links carrying one, or stores-near-a-point links.
countriesarray(none)Search mode: keep only stores in CA and/or US.
bannersarray(none)Search mode: keep only these store chains (partial, case and accent-insensitive).
keywordsarray(none)Search mode: keep items with a name word starting with the keyword.
departmentsarray(none)Search mode: keep these departments, English or French.
minDiscountPercentinteger(none)Search mode: keep items discounted by at least this percent.
maxPricestring(none)Search mode: keep items priced at or below this amount.
expiringWithinDaysinteger(none)Search mode: keep items whose best-before date is within this many days.
maxStoresinteger(none)Search mode: read at most this many stores, nearest first.
storesOnlybooleanfalseSearch mode: return store records only, with itemCount.
fullNetworkbooleanfalseSearch mode: sweep a built-in list of about 45 metro areas instead of your locations.
emitSoldOutbooleantrueIncremental mode with Emit expired: return vanished items from a store read this run as SOLD_OUT.
urlsarrayempty (same pre-filled examples as storeInputs)Alias for storeInputs, used when storeInputs is not given; accepted under the name other Apify actors use for url mode.
maxItemsinteger20Stop after this many records (0 = no limit).
includeStoresbooleanfalseAlso emit one record per store, kind=store.
countryHintstring(none)CAD or USD. When set, every item's currency uses it instead of the value derived from the store's country.
resumeFromRunIdstring(none)Continue one interrupted run, skipping already-collected record ids.
incrementalModebooleanfalseReturn only new and changed records on recurring runs.
stateKeystring(none)Name or share an incremental-mode monitoring campaign.
emitUnchangedbooleanfalseAlso return (and bill) unchanged records.
emitExpiredbooleanfalseAlso return (and bill) expired records, after a complete scan.
ignoreFieldsForChangesarray[]Incremental mode: extra output field names that should not make a record UPDATED (still returned in output).
mcpConnectorsarray(none)Optional: send a summary of each record to apps you authorized under Integrations.
notionParentPageUrlstring(none)Notion connector only: page under which records are created.
maxNotifyListingsinteger50Cap on items written to each connector per run.
proxyConfigurationobjectApify ProxyConnection settings (see Plan Requirement).

Output Example

Sample shape: values are illustrative placeholders, not from a live listing.

{
"kind": "item",
"recordId": "6198765432abcdef0fedcba9",
"itemId": "6198765432abcdef0fedcba9",
"url": "https://www.flashfood.com/en/stores/6123456789abcdef01234567",
"title": "Sample Brand Multigrain Bread",
"price": 2.49,
"originalPrice": 4.99,
"discountPercent": 50.1,
"currency": "USD",
"quantityAvailable": 5,
"bestBefore": "2026-11-05T00:00:00+00:00",
"department": "Bakery",
"legacyDepartment": "Bakery",
"imageUrl": "https://img.example.com/sample-product.jpg",
"listedAt": "2026-10-30T09:15:00.000Z",
"isSnapEligible": false,
"storageTreatment": "NONE",
"storeId": "6123456789abcdef01234567",
"storeName": "Sample Grocery Market",
"banner": "Sample Banner Co.",
"street": "100 Example Ave",
"city": "Sample City",
"stateCode": "MI",
"countryCode": "US",
"latitude": 42.33,
"longitude": -83.05,
"raw": {},
"changeType": "NEW",
"changedFields": [],
"firstSeenAt": "2026-01-01T00:00:00+00:00",
"lastSeenAt": "2026-01-01T00:00:00+00:00"
}

Plan Requirement

The default proxy setting uses Apify Proxy, starting on the datacenter pool, and the actor rotates connections automatically. The source answers only requests that carry the app's public key header, which the actor sends; no challenge page was seen. Whether the datacenter pool alone is always enough has not been independently measured, so a paid Apify plan with proxy access is recommended. If the default connection is refused, the run logs:

The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable.

and, once pages are then served that way: "Served over the backup connection (the default connection was unavailable)." When no page can be loaded at all, the run fails with a message saying either that the proxy connection was refused for this account (a paid plan is required, or check your own proxy URLs), that residential proxies are unavailable and the backup connection could not load the pages, or that the requests failed and the problem is a connection problem, not an empty search. Backup connections are a user-paid fallback; you can also supply your own proxy URLs in proxyConfiguration.

FAQ

How much does it cost?

Billing uses two events: the dataset item event (apify-default-dataset-item) is charged once per record returned, whether a deal or a store row, including UNCHANGED and EXPIRED or SOLD_OUT rows when you opt into emitting them; and the actor start event (apify-actor-start) is charged when the run starts, one per GB of run memory. Suppressed unchanged rows are not billed. The Pricing tab shows the current rates. Use Max items to cap the cost of a search-mode run.

This actor reads only the publicly served catalogue the official app itself reads, with no account or login required. You are responsible for how you use it: follow Flashfood's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Keep run volumes reasonable.

Can I get only new or changed deals on a schedule?

Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new, updated and reappeared records, and unchanged ones are not billed unless you turn that on too.

Why did my run only cover part of a city?

One search query returns at most 100 stores around a point, nearest first, and the source ignores paging. Add more locations, or shrink the radius and add locations around the gap, to cover a large metro area completely.

A location returned nothing. Did I get an empty area, or did something fail?

Both are possible and they look different. A location with genuinely no stores in range returns cleanly with zero records for it. A location whose name could not be resolved to coordinates is skipped with a warning in the run log ("could not be resolved to coordinates"); the run still finishes successfully using the locations that did resolve. Check the log if a normally-active area came back empty, and prefer lat,lng for locations that must always be read.

Why did my run fail instead of returning an empty dataset?

If every request to the source is refused or unreadable, the run stops with a clear message so "no deals right now" is never confused with "nothing could be read". Run it again in a few minutes. If only some stores or batches could be read, you still get what was found.

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.

🔗 Want more grocery data?

Pair this actor with these related scrapers from the same team:

🥦 FoodHero Scraper
Scrape FoodHero surplus grocery deals across Canada by area, store or offer ID. Extract...
🥦 Instacart Scraper
Scrape Instacart grocery catalogs with per-store prices. Run keywords across several...
🥦 ResQ Club Scraper
Scrape ResQ Club surplus food offers in Finland, Sweden and Estonia: name, price, current...
🥦 Coles AU Scraper
Scrape Coles.com.au grocery products and customer reviews. Search by keyword, browse...
🥦 ALDI AU Scraper
Scrape ALDI Australia (aldi.com.au) products: name, brand, price, was-price, savings...
🧩 Too Good To Go Scraper
Scrape Too Good To Go (TGTG) stores and surprise bags by location or item: price, value...

👉 Browse all abotapi scrapers

💬 Support

  • 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
  • 🛠️ Need another site, extra fields or a private build? Email contact@abotapi.com.
  • ⭐ Enjoying it? A quick review on the actor page helps other users find it.