Too Good To Go Scraper: Stores, Surprise Bags & Availability avatar

Too Good To Go Scraper: Stores, Surprise Bags & Availability

Pricing

from $1.80 / 1,000 bag or store records

Go to Apify Store
Too Good To Go Scraper: Stores, Surprise Bags & Availability

Too Good To Go Scraper: Stores, Surprise Bags & Availability

Scrape Too Good To Go (TGTG) stores and surprise bags by location or item: price, value, savings, quantity, pickup windows, ratings, favorites and store details. Availability monitoring (NEW, UPDATED, REAPPEARED, EXPIRED), detail enrichment, incremental runs, MCP export.

Pricing

from $1.80 / 1,000 bag or store records

Rating

0.0

(0)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

2

Bookmarked

6

Total users

1

Monthly active users

2 days ago

Last modified

Categories

Share

Too Good To Go Scraper turns Too Good To Go into a clean surprise-bag data feed. Get bag listings around any location with price, value, savings, quantity available, pickup window, ratings, favorite counts, diet tags and full store details (address, geo coordinates, website), pulled from the same mobile service the official app uses, not a map screenshot. Search around one or many points, or paste bag links or ids for a direct single-item read. Turn on availability monitoring for recurring runs, add detail enrichment for a fuller description per bag, then export to JSON, CSV or Excel, read it through the API, or pipe it into the apps you already use.

Why This Scraper?

  • Real mobile-app data. Bag listings carry exact quantity available, pickup window, price and value in one record, not a scraped map screenshot.
  • Location search around one or many points. Each location gets an even share of the item budget, so a multi-city run stays balanced instead of one city eating the whole budget.
  • Item mode for direct reads. Paste bag links or numeric ids for a fully enriched single-item record; search filters are ignored in this mode since each item is read directly.
  • Availability monitoring. Recurring runs classify every record NEW, UPDATED, UNCHANGED, REAPPEARED or EXPIRED, with per-campaign state you can name or share across runs.
  • Detail enrichment on demand. Switch on a fuller description and tab data per bag in search mode; billed only for the records where that extra read actually succeeds.
  • Zero setup. The first run establishes store access automatically and reuses it on every later run; no account or credentials are required to start.
  • Structured failures. A run that is blocked or refused everywhere stops with a clear message instead of quietly returning an empty dataset.

Use Cases

  • Food-waste shoppers and deal-alert bots: track new surprise bags near one or many neighborhoods as they go live, with price and savings on every listing.
  • Availability monitoring on a schedule: run search mode daily and get only NEW and UPDATED bags since the last check, without re-downloading everything.
  • Sustainability and market research: measure how much discounted surplus food is listed, at what savings, across cities, countries or store categories.
  • Store and brand tracking: paste known store item links to watch price, value and stock changes for specific locations over time.
  • Notion and app integrations: pipe live listings into the apps you already use through the MCP connector export, on top of the dataset export.

Data You Get

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

FieldExample
kind"bag"
itemId"12345678"
url"https://www.toogoodtogo.com/en-us/item/12345678"
title"Sample Bakery - Mitte (Surprise Bag)"
itemName"Surprise Bag"
category"Baked goods"
price / currency3.99 / "EUR"
value12.0
savingsPercent66.7
quantityAvailable3
pickupStart / pickupEnd"2026-01-01T16:30:00Z" / "2026-01-01T18:00:00Z"
favoriteCount214
ratingAverage / ratingCount4.7 / 96
positiveRatingReasons["Friendly staff", "Great value"]
dietTags["vegetarian"]
itemTags["3 left"]
inSalesWindowtrue
distanceKm1.24
storeName / branch"Sample Bakery" / "Mitte"
city / country"Berlin" / "Germany"
latitude / longitude52.52 / 13.405
changeType"NEW" (also "UPDATED", "REAPPEARED", "EXPIRED"; incremental mode only)

Detail-enriched bags (search mode with detail enrichment on, or any url-mode record) also carry enrichedFromDetail and detail_tabs, the item's own description tabs. Every record also carries soldOutAt, purchaseEnd, lastItemAt, maxPerOrder, valueCurrency, favorite, packagingOption, canUserSupplyPackaging, the sub-category and category icon, food-handling and collection notes, buffet/edible/single-item flags, the rating breakdown (collection experience, food quality, contents variety, food quantity, monthly rating count), the positive-review highlights (positiveRatingReasons), the pickup point as its own address, and the store's description, logo and cover images, storeId, storeWebsite, storeTimeZone, storeFavoriteCount and local-hero flag. With Include reviews on, the nested reviews list (plus reviewCount and reviewTotal) carries the per-user reviews. Incremental runs add changedFields, firstSeenAt and lastSeenAt. The full upstream item object (raw) and the full store block (storeRaw) are always included, so no upstream field is ever dropped.

How to Use

  1. Pick a mode: search (around one or more locations) or url (paste bag links or numeric ids).
  2. For search mode, add one or more locations and any filters you need (diet categories, in-stock only, We Care, favorites). For url mode, paste item links or ids; filters are ignored there.
  3. Set Max items (and Max pages for search mode) to control run size and cost, and switch on Detail enrichment if you want the fuller description per bag.
  4. Click Start, then download the dataset as JSON, CSV or Excel, or read it through the API.

Search around one location, in-stock bags only:

{
"mode": "search",
"locations": ["Berlin"],
"radiusKm": 5,
"withStockOnly": true,
"maxItems": 20
}

Multiple cities, a diet filter, and detail enrichment on:

{
"mode": "search",
"locations": ["Berlin", "Paris"],
"dietCategories": ["vegetarian"],
"fetchDetails": true,
"maxItems": 40
}

Paste bag links or ids directly (add "reviewsOnly": true to get one row per review instead):

{
"mode": "url",
"itemInputs": ["123456789", "https://www.toogoodtogo.com/en-us/item/987654321"],
"reviewsOnly": true
}

Recurring monitoring with availability changes:

{
"mode": "search",
"locations": ["10115 Berlin"],
"incrementalMode": true,
"stateKey": "berlin-mitte-daily",
"emitExpired": true,
"maxItems": 0
}

Run it from your code

Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/toogoodtogo-surprise-bag-scraper").call(
run_input={"mode": "search", "locations": ["Berlin"], "maxItems": 20})
for bag in client.dataset(run["defaultDatasetId"]).iterate_items():
print(bag["title"], bag["price"], bag["quantityAvailable"])

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/toogoodtogo-surprise-bag-scraper').call({
mode: 'search', locations: ['Berlin'], maxItems: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();

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

Detail enrichment and billing

In search mode, detail enrichment is optional: turn on fetchDetails to fetch each bag's own detail page for a fuller description and tab data, billed as the detail-enrichment event only for the records where that extra read succeeds. If the read fails, the base record still ships, uncharged. In url mode there is no toggle: every pasted link or id is a direct single-item lookup, so it is always read from its detail page and always billed the detail-enrichment event when that read succeeds, the same as a search-mode record with detail enrichment switched on.

Reviews

Turn on Include reviews (fetchReviews) to attach each item's per-user reviews (star rating, text, date, author and the highlighted reasons) as a nested reviews list on the record (also exposed one-per-row in the Reviews output tab). Works in both search and URL mode; maxReviews caps the reviews per item (default 20, 0 skips review collection). Reviews are billed by the detail-enrichment event, one unit per started block of 10 delivered reviews, counted only for records that actually reach the dataset. The per-item detail request this needs is included; when only reviews are switched on in search mode, no additional per-item detail unit applies.

Output reviews only (reviewsOnly) flips the dataset to one row per review instead of one row per bag: search mode walks the area, URL mode reads the given items, and each review row carries the item and store references (itemId, storeName, itemName, url). The enrichment reads this mode needs are performed automatically; you never need to touch Detail enrichment or Include reviews alongside it. Reviews here are ordinary dataset records, billed only as dataset items: no detail or review surcharges.

Resume and recurring updates

  • Resume (resumeFromRunId) continues one interrupted run: paste its run or dataset ID and the actor skips everything already collected there, so you don't pay twice.
  • Incremental mode (incrementalMode) is for running the same search or item list again and again. Each record is classified NEW, UPDATED (with changedFields), or UNCHANGED (suppressed and not billed unless emitUnchanged is on). EXPIRED only happens after a run that scans its whole scope from start to end, without being resumed, without hitting Max items, and without any part of that scope failing to load; only then are records missing from that complete scan tombstoned as EXPIRED (with emitExpired on). REAPPEARED can then only happen on a later run that finds one of those tombstoned records again; it never happens on a first run, and never happens unless a prior complete scan already produced an EXPIRED tombstone for that record. stateKey names or shares the stored state; leave it empty to let the actor derive one automatically from the search and filter settings.

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; Notion gets a rich page-per-item export, other connectors get a best-effort write or digest.

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 (item links or ids).
locationsarrayno default (prefill: Berlin)Where to search: a city or place name, or exact coordinates as lat,lng. One entry per location.
radiusKminteger5Search radius in km, 1 to 100 (search mode).
dietCategoriesarrayno default, no prefill (empty selection)Only items matching these diets: vegan, vegetarian, halal, kosher, pescatarian, gluten-free (search mode).
searchPhrasestringno default (empty)Only stores whose name or description matches this text (search mode).
withStockOnlybooleanfalseHide sold-out listings (search mode).
weCareOnlybooleanfalseOnly stores carrying the We Care label (search mode).
favoritesOnlybooleanfalseOnly stores the account has favorited (search mode).
itemInputsarrayno default (prefill: 123456789)Numeric item ids or any link carrying the id (url mode).
accessTokenstringno default (empty)Paste a token pair from a community CLI login for your own account; optional, a saved session is reused automatically.
refreshTokenstringno default (empty)Paired with accessToken; the actor exchanges it for a fresh token automatically when one expires.
sessionCookiestringno default (empty)Optional cookie header returned alongside a pasted token pair.
emailstringno default (empty)Starts an in-run email login: confirm the login email on a desktop browser while the run polls, up to 4 minutes.
loginPollingIdstringno default (empty)From a previous login attempt, pairs with loginPin to complete a two-phase login.
loginPinstringno default (empty)The PIN from the login email, completing a login started in an earlier run.
allowAutoSignupbooleantrueOn by default: prepares store access automatically when no saved session exists, and reuses it later.
fetchDetailsbooleanfalseSearch mode: fetch each item's detail endpoint for a fuller description and tab data, billed as detail-enrichment. Url mode always fetches detail regardless of this toggle.
fetchReviewsbooleanfalseFetch each item's reviews (star ratings with text, date, author and reasons) as a nested list, also exposed one-per-row in the Reviews tab. Adds one detail-enrichment unit per started block of 10 delivered reviews.
maxReviewsinteger20Cap on reviews attached per item when fetchReviews is on (0 skips review collection).
reviewsOnlybooleanfalseReturn one row per review instead of bag rows; review rows are billed only as dataset items, no surcharges.
maxItemsinteger20Stop after this many records (0 = no limit; the run then stops at Max pages or when results run out). In reviews-only mode this counts review rows.
maxPagesinteger100Page walk bound per location, 1 to 100; the run stops at Max items first (search mode).
resumeFromRunIdstringno default (empty)Paste a previous run or dataset ID to continue a large pull without returning items already collected there.
incrementalModebooleanfalseTurn on for daily or recurring monitoring with change detection.
stateKeystringno default (empty)Name a monitoring campaign, or deliberately share state across runs (incremental mode).
ignoreFieldsForChangesarrayno default (empty)Top-level fields that must never make a row UPDATED (incremental mode). Ignored fields are still delivered and stored.
emitUnchangedbooleanfalseAlso return and bill UNCHANGED records (incremental mode).
emitExpiredbooleanfalseAlso return and bill EXPIRED records, produced only after a complete scan (incremental mode).
mcpConnectorsarrayno default (empty)Optional: send a summary of each record to apps you authorized under Integrations.
notionParentPageUrlstringno default (empty)Notion connector only: page under which item pages are created.
maxNotifyListingsinteger50Cap on items written to each connector per run. Does not affect the dataset.
proxyConfigurationobjectno default (prefill: Apify Proxy on)Apify Proxy settings.

Output Example

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

{
"kind": "bag",
"itemId": "12345678",
"url": "https://www.toogoodtogo.com/en-us/item/12345678",
"title": "Sample Bakery - Mitte (Surprise Bag)",
"itemName": "Surprise Bag",
"subtitle": "Baked goods, bread and pastries",
"description": "A mix of the day's unsold baked goods, contents revealed at pickup.",
"itemType": "MAGIC_BAG",
"category": "Baked goods",
"price": 3.99,
"currency": "EUR",
"value": 12.0,
"valueCurrency": "EUR",
"savingsPercent": 66.7,
"quantityAvailable": 3,
"maxPerOrder": 2,
"pickupStart": "2026-01-01T16:30:00Z",
"pickupEnd": "2026-01-01T18:00:00Z",
"soldOutAt": null,
"purchaseEnd": "2026-01-01T18:00:00Z",
"favoriteCount": 214,
"favorite": false,
"ratingAverage": 4.7,
"ratingCount": 96,
"ratingMonthCount": 6,
"ratingCollectionExperience": 4.6,
"ratingFoodQuality": 4.5,
"ratingContentsVariety": 3.8,
"ratingFoodQuantity": 4.1,
"positiveRatingReasons": ["Friendly staff", "Great value"],
"dietTags": ["vegetarian"],
"itemTags": ["3 left"],
"packagingOption": "BRING_YOUR_OWN",
"canUserSupplyPackaging": true,
"imageUrl": "https://images.example/cover.jpg",
"inSalesWindow": true,
"distanceKm": 1.24,
"storeId": "10001",
"storeName": "Sample Bakery",
"branch": "Mitte",
"street": "Sample Street 12",
"city": "Berlin",
"postalCode": "10115",
"country": "Germany",
"countryCode": "DE",
"latitude": 52.52,
"longitude": 13.405,
"storeWebsite": "https://sample-bakery.example",
"storeTimeZone": "Europe/Berlin",
"storeFavoriteCount": 1893,
"storeDescription": "A neighborhood bakery ...",
"storeRaw": {},
"raw": {},
"enrichedFromDetail": true,
"detail_tabs": [{ "title": "What's inside", "body": "Assorted baked goods." }],
"reviews": [
{
"reviewId": "r-1001",
"itemId": "12345678",
"storeId": "10001",
"ratingValue": 5,
"reviewText": "Great bread, friendly staff.",
"reviewDate": "2026-01-02T10:00:00Z",
"authorName": "Sample Customer",
"reviewReasons": ["Delicious food"]
}
],
"reviewCount": 1,
"reviewTotal": 27
}

Plan Requirement

The source requires a free account session; the actor prepares and reuses one automatically, so no manual login is required to start. The default connection setting works out of the box: the actor requests a residential-tier connection automatically when none is specified, and falls back gracefully if that tier isn't available on your plan. An explicit connection group or custom URLs you set are always honored as given. Detail enrichment adds a per-item request and is billed through the existing detail-enrichment event only for records where the detail read succeeded; url-mode records always take this path. Reviews are billed through the detail-enrichment event, one unit per started block of 10 delivered reviews, counted only on records that actually reach the dataset; in reviews-only output the review rows are ordinary dataset records and no surcharge applies.

FAQ

How much does it cost?

You pay per record returned, plus a one-time actor-start charge, with detail enrichment billed only for the records where that extra read succeeds, and reviews billed one unit per 10 delivered reviews. The Pricing tab shows the current rates. Use Max items to cap the cost of any run.

This actor collects only publicly available store and surprise-bag listing data. You are responsible for how you use it: follow Too Good To Go's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution.

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

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

Url mode reads each pasted link or id directly from its own detail page, so it is always fully enriched, the same as a search-mode record with Detail enrichment switched on. The Detail enrichment toggle only controls whether search mode makes that extra per-item request; it has no effect in url mode.

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

If the source can't establish an account session, or every request it made came back rejected, the run stops with a clear message so "no bags right now" is never confused with "nothing could be read". A genuinely empty search (no bags found, nothing rejected) still succeeds with an empty dataset.

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.

🔗 More scrapers you might like

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

🥦 Flashfood Scraper
Scrape Flashfood discounted groceries and stores by location or store. Extract current...
🥦 ResQ Club Scraper
Scrape ResQ Club surplus food offers in Finland, Sweden and Estonia: name, price, current...
🥦 FoodHero Scraper
Scrape FoodHero surplus grocery deals across Canada by area, store or offer ID. Extract...
🧩 Isetan Mitsukoshi
Scrape the Isetan Mitsukoshi department store online shop (mistore.jp). Search by keyword...
🥦 Instacart Scraper
Scrape Instacart grocery catalogs with per-store prices. Run keywords across several...
🛒 DoorDash Scraper
Extract structured doordash.com data at scale. Search by keyword or paste store URLs to...

👉 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.