# Wolt Scraper - Restaurants, Menus, Prices & Delivery Details (`abotapi/wolt-restaurants-scraper`) Actor

Scrape Wolt restaurants and full menus by city or URL. Extract 60+ fields including restaurant names, addresses, GPS coordinates, opening hours, ratings, tags, menu items, images, prices, delivery zones, fees, minimum orders and merchant details.

- **URL**: https://apify.com/abotapi/wolt-restaurants-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** E-commerce, Automation, Developer tools
- **Features:** Limited permissions
- **Stats:** 25 total users, 4 monthly users, 92.2% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 results

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

## Wolt Scraper: Restaurant Listings, Menus and Delivery Data

Wolt Scraper turns Wolt into a structured restaurant and menu data feed. Search by city name or paste Wolt links to collect venue details such as address, GPS coordinates, contact info, opening and delivery hours, ratings, delivery fees, order minimum and the merchant's legal block, plus, on request, the venue's full menu with categories, items, images, prices and discounts. Filter by cuisine, minimum rating or review count, then export to JSON, CSV or Excel, or read the results straight through the API.

### Why This Scraper?

- **70+ named fields per venue**, covering address, GPS, contact info, opening and delivery hours, ratings, delivery fees, order minimum, tipping configuration and the merchant's legal block (registered name, business ID, registered address).
- **Full menu surface, on or off per run.** Categories and items with images and blurhashes, current and original prices, discount and lowest-price fields, VAT rate, dietary tags, alcohol content and per-unit pricing for items sold by weight.
- **Brand identity fields** (brand slug, brand name, brand logo image and its blurhash) so the same chain scraped across different cities can be matched up later.
- **Delivery polygon as full GeoJSON**, plus opening hours and delivery hours kept as two separate weekly schedules.
- **Two ways to find venues:** search by city name, with a geocoding fallback for places outside Wolt's own city directory, or paste Wolt venue and city-listing URLs directly.
- **Cuisine, minimum rating and minimum review count filters**, plus a sort order, to narrow a city listing before every venue is enriched.
- **Built for schedules.** Incremental mode tracks new, updated and no-longer-listed venues across recurring runs.

### Use Cases

- **Food delivery market research:** compare menu prices, delivery fees and order minimums for the same cuisine across several cities.
- **Franchise and chain tracking:** consolidate a brand's presence and pricing across every city it operates in, using the brand slug shared by all its venues.
- **Competitive menu intelligence:** track price changes, discounts and new menu items on competitor restaurants over time with incremental mode.
- **Compliance and legal research:** collect the merchant legal block (registered business name, business ID, registered address) that Wolt publishes for each venue.
- **Local directories and apps:** build a directory of restaurants with hours, ratings, delivery zones and contact details for a city or region.

### Data You Get

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

| Field | Example |
| --- | --- |
| `venueId` / `slug` | `"00000000000000000000000a"` / `"sample-burger-place"` |
| `name` / `description` | `"Sample Burger Place"` / `"Best burgers in town."` |
| `brandSlug` / `brandName` | `"sample-burger"` / `"Sample Burger"` |
| `address` / `city` / `countryCode` | `"Sample Street 1"` / `"Helsinki"` / `"FIN"` |
| `phone` / `website` | `"+35800000000"` / `"https://example.com"` |
| `woltUrl` | `"https://wolt.com/en/fin/helsinki/restaurant/sample-burger-place"` |
| `latitude` / `longitude` | `60.0` / `24.0` |
| `currency` | `"EUR"` |
| `deliveryBasePrice` / `deliveryBasePriceFormatted` | `199` / `"€1.99"` |
| `orderMinimum` / `orderMinimumFormatted` | `1000` / `"€10.00"` |
| `serviceFeeMin` / `serviceFeeMax` / `serviceFeePercentage` | `45` / `99` / `3` |
| `isWoltPlus` / `isPickupFriendly` | `true` / `false` |
| `priceRange` | `1` (1 to 4 wallet icons) |
| `foodTags` | `["burger", "fries"]` |
| `categories` | `[{ "name": "American", "slug": "american", "primary": true }]` |
| `rating` / `ratingScore` / `ratingVolume` | `3` / `8` / `200` |
| `workingHours` | 7-day array of `{ day, formatted_times }` |
| `deliveryHours` | 7-day array, kept separate from `workingHours` |
| `deliveryGeoRange` | full GeoJSON `Polygon` |
| `tipping` | `{ currency, tip_amounts, max_amount, min_amount, type }` |
| `merchant` | `{ id, name, business_id, street_address, city, post_code, country }` |
| `digitalServicesAct` | merchant self-certification and reporting fields |
| `menuCategoryCount` / `menuItemCount` | `13` / `96` |
| `scrapedAt` | `"2026-01-01T00:00:00.000Z"` |
| `changeType` | incremental mode only, see below |

When "Fetch full menu" is on, the same record also carries `menuCategories` (each with `id`, `slug`, `name`, `image`, `blurhash`, `itemCount`, `subcategoryCount`) and `menuItems`, an array where each item carries over 40 fields: name, description, image, blurhash, currency, current and original price in cents, discount amount and percent, a lowest-price-in-the-last-30-days pair, VAT rate and VAT category, dietary preferences, tags, allowed delivery methods, alcohol content in per mille, per-unit pricing for weighed items, cutlery and visibility flags, and quantity limits. With "Fetch full menu" off, these fields are all `null` and the record is venue-only.

### How to Use

1. Pick a mode: `search` (by city name) or `url` (paste Wolt links).
2. Fill in `cities` or `urls`, and optionally set a cuisine filter, a minimum rating or a minimum review count.
3. Toggle "Fetch full menu" and "Include rating fields", set a language, and cap the run with `maxVenues` or `maxVenuesPerCity`.
4. Click **Start**, then download the dataset as JSON, CSV or Excel, or read it through the API.

**Find restaurants in one city:**

```json
{
  "mode": "search",
  "cities": ["Helsinki"],
  "fetchMenu": true,
  "maxVenues": 25
}
```

**Multi-city, cuisine filter, rating floor:**

```json
{
  "mode": "search",
  "cities": ["Helsinki", "Stockholm", "Berlin"],
  "primaryCategories": ["pizza", "italian"],
  "minRating": 8,
  "minRatingVolume": 50,
  "fetchMenu": true,
  "maxVenuesPerCity": 10,
  "maxVenues": 30
}
```

**Direct URLs, single venues:**

```json
{
  "mode": "url",
  "urls": [
    "https://wolt.com/en/aut/graz/restaurant/kfc-graz",
    "https://wolt.com/en/fin/helsinki/restaurant/sample-burger-place"
  ],
  "fetchMenu": true
}
```

**Direct URLs, city listing pages:**

```json
{
  "mode": "url",
  "urls": [
    "https://wolt.com/en/aut/graz/restaurants",
    "https://wolt.com/en/fin/helsinki/restaurants"
  ],
  "fetchMenu": false,
  "maxVenues": 100
}
```

**Supported URL shapes** (URL mode). Leaving `urls` untouched scrapes five distinct examples (a venue, two city listings, a category and a brand); the remaining rows below are alternate forms that resolve to the same targets, so they are shown in the prefill but not run by default. Clear the field to use only your own links.

| Shape | Example | Behaviour |
|---|---|---|
| Venue | `https://wolt.com/en/aut/graz/restaurant/kfc-graz` | One venue. |
| City listing | `https://wolt.com/en/aut/graz/restaurants` | Venues of that city. |
| City listing, other country | `https://wolt.com/en/fin/helsinki/restaurants` | Venues of that city. |
| City home | `https://wolt.com/en/aut/graz` | Same as the city listing. |
| Category | `https://wolt.com/en/aut/graz/category/pizza` | City venues filtered to that category. |
| Brand | `https://wolt.com/en/aut/graz/brand/kfc` | City venues of that brand. |
| Legacy venue | `https://wolt.com/en/aut/graz/venue/kfc-graz` | Mapped to the venue slug. |
| Other language | `https://wolt.com/de/aut/graz/restaurant/kfc-graz` | Any prefix such as `de` or `de-at` works. |
| Other-language city page | `https://wolt.com/fi/fin/helsinki/restaurants` | Same as the English city page, in another language. |
| Sorted city | `https://wolt.com/en/aut/graz/restaurants?sorting=rating` | `sorting=` is applied as the sort order unless `sortBy` is set. |
| Venue with query | `https://wolt.com/en/aut/graz/restaurant/kfc-graz?lang=de` | Query string ignored. |
| Discovery | `https://wolt.com/en/discovery` | Has no city; skipped with a warning in the run log. |

URLs without a language prefix (for example `https://wolt.com/aut/graz/restaurant/kfc-graz`) are not valid on wolt.com and are skipped with a warning.

#### Dataset views

The dataset has four tabs in the Apify Console: **Restaurants** (overview of every venue), **Venue details** (hours, fees, tipping, merchant block), **Menu items** (the menu per venue) and **Changes (incremental)** (`changeType`, `changedFields` and first/last seen dates, for scheduled runs).

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/wolt-restaurants-scraper").call(run_input={"mode": "search", "cities": ["Helsinki"]})
for venue in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(venue["name"], venue["address"])
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/wolt-restaurants-scraper').call({ mode: 'search', cities: ['Helsinki'] });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

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

#### Resume vs incremental mode

These are two different things.

**Resume** (`resumeFromRunId`) continues one specific interrupted run, for example `{ "mode": "search", "cities": ["Helsinki"], "maxVenues": 500, "resumeFromRunId": "PREVIOUS_RUN_ID" }`. Paste a previous run ID or dataset ID and this run skips venues already collected there, returning only the remaining new ones.

**Incremental mode** (`incrementalMode`) is for scheduling this actor daily or weekly against the same search and getting only what changed, for example `{ "mode": "search", "cities": ["Helsinki"], "incrementalMode": true }`. The actor remembers the previous run itself, no run ID needed. The first run returns everything as `NEW`. Later runs return only `NEW` and `UPDATED` venues (with `changedFields`); unchanged venues are suppressed and not billed unless `emitUnchanged` is on. `EXPIRED` rows are only produced when `emitExpired` is on and a run scans its whole tracked search completely, meaning it was not capped by `maxVenues` or `maxVenuesPerCity`, not a `resumeFromRunId` run, and every city or URL resolved and loaded cleanly. `REAPPEARED` only ever appears for a venue that an earlier run had already marked `EXPIRED` under that same condition; if `emitExpired` has never been turned on for a search, no venue can be marked gone, so `REAPPEARED` will never occur. `stateKey` names or shares the stored state; leaving it blank derives one automatically from the cities, URLs, filters and menu settings, so different searches never mix state.

**What counts as a change.** A venue is `UPDATED` when any of its output fields changes (details, opening hours, delivery fees, menu items, prices and discounts); `changedFields` lists them. Excluded by default, while still present in the output: `scrapedAt`, `deliveryEstimateMinutes`, `deliveryTimeRangeMin`, `deliveryTimeRangeMax`, `serpSortable`, `estimateMinutes`, `estimateRange`, `online` and `delivers` (live counters that change on every scrape). Use `ignoreFieldsForChanges` to exclude more, with a dot for nested fields that applies to every list element (for example `menuItems.blurhash`). Changing the ignore list never produces a one-time wave of `UPDATED` rows.

Combining `incrementalMode` with `resumeFromRunId` bootstraps a fresh incremental baseline from a large resumed pull, but only on a genuinely first run for that state key. If incremental state already exists for it, the run fails rather than silently mixing the two.

### 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

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` (by city) or `url` (paste Wolt links). |
| `cities` | array of strings | none in the schema (prefill: `["Helsinki"]`); falls back to `["Helsinki"]` if left empty in search mode | City names or free-text places. Wolt's own city directory is matched first, anything else falls back to geocoding. Search mode only. |
| `urls` | array of strings | five distinct example URLs (see below) | Wolt venue, city, category or brand URLs. URL mode only. |
| `primaryCategories` | array of strings | none | Cuisine slugs (pizza, sushi, vegan, kebab, etc.). Empty means all. |
| `minRating` | integer, 0 to 10 | none | Minimum venue score on the 0-10 scale. Leave empty for no minimum. |
| `minRatingVolume` | integer | `0` | Minimum review count to include. |
| `sortBy` | string | `default` | Order of venues within each city: `default` (Wolt's own ranking), `rating`, `delivery-price`, `delivery-estimate`, `distance` or `preparation-estimate`. |
| `fetchMenu` | boolean | `true` | When on, every venue is enriched with the full menu. Off for faster venue-only runs. |
| `includeReviews` | boolean | `true` | When on, populates `rating`, `ratingScore` and `ratingVolume`. |
| `language` | string | `en` | ISO language code passed to Wolt for menu and venue text. |
| `maxVenues` | integer | `50` | Hard cap on total venues across the whole run. `0` means unlimited. |
| `maxVenuesPerCity` | integer | `0` | Per-city cap. `0` means unlimited per city. |
| `maxItems` | integer | none | Alias of `maxVenues`; when set it takes precedence. |
| `latitude` | number | none | Search around an exact point (use with `longitude`), added to any `cities`. Leave `cities` empty to search only that point. |
| `longitude` | number | none | Longitude of the exact point; see `latitude`. |
| `categoryFilter` | string | none | Free-text cuisine or tag filter (for example `burger`, `pizza`, `sushi`); keeps venues whose category or tag name contains it. |
| `includeDetails` | boolean | `true` | Turn off for basic listing rows without the menu fetch. |
| `resumeFromRunId` | string | none | Continue one specific interrupted run. See "Resume vs incremental mode" above. |
| `incrementalMode` | boolean | `false` | Cross-run change detection for recurring runs. Adds `changeType` to every row. See above. |
| `stateKey` | string | none, auto-derived | Names an incremental-mode monitoring campaign. Leave empty to derive one automatically from cities, URLs, filters and menu settings. |
| `emitUnchanged` | boolean | `false` | Incremental mode only. Also return, and bill, venues that have not changed since the last run. |
| `ignoreFieldsForChanges` | string\[] | none | Incremental mode only. Fields (dotted paths for nested) that never count as a change; they stay in the output. |
| `emitExpired` | boolean | `false` | Incremental mode only. Also return, and bill, venues no longer found, once a run has fully scanned the tracked search. |
| `proxyConfiguration` | object | `{ "useApifyProxy": true }` | Proxy settings. The default Apify proxy is tried first; if a request is refused, the actor retries it over Residential geo-targeted at the searched city's own country, or over a backup connection when your plan has no residential access. See "Plan Requirement". |
| `mcpConnectors` | array | none | Optional: send a summary of each record to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | none | Notion connector only: page under which item pages are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. Does not affect the dataset. |

### Output Example

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

```json
{
  "venueId": "00000000000000000000000a",
  "slug": "sample-burger-place",
  "name": "Sample Burger Place",
  "description": "Best burgers in town.",
  "brandSlug": "sample-burger",
  "brandName": "Sample Burger",
  "brandLogoImage": "https://imageproxy.wolt.com/mes-image/00000000-0000-0000-0000-000000000000/00000000-0000-0000-0000-000000000000",
  "brandLogoBlurhash": "LKO2?V%2Tw=w]~RBVZRi};RPxuwH",
  "coverImage": "https://imageproxy.wolt.com/assets/000000000000000000000000",
  "coverBlurhash": "jkSsmKKXhXhq;;KXJAKXHtlmJjKW",
  "address": "Sample Street 1",
  "city": "Helsinki",
  "cityId": "0000000000000000000000aa",
  "citySlug": "helsinki",
  "postCode": "00100",
  "countryCode": "FIN",
  "phone": "+35800000000",
  "website": "https://example.com",
  "woltUrl": "https://wolt.com/en/fin/helsinki/restaurant/sample-burger-place",
  "latitude": 60.0,
  "longitude": 24.0,
  "timezone": "Europe/Helsinki",
  "currency": "EUR",
  "deliveryMethods": ["homedelivery"],
  "deliveryBasePrice": 199,
  "deliveryBasePriceFormatted": "€1.99",
  "serviceFeeMin": 45,
  "serviceFeeMax": 99,
  "serviceFeePercentage": 3,
  "orderMinimum": 1000,
  "orderMinimumFormatted": "€10.00",
  "deliveryGeoRange": { "type": "Polygon", "coordinates": "..." },
  "isPickupFriendly": false,
  "isWoltPlus": true,
  "ncdAllowed": true,
  "groupOrderEnabled": true,
  "priceRange": 1,
  "foodTags": ["burger", "fries"],
  "categories": [{ "name": "American", "slug": "american", "primary": true }],
  "rating": 3,
  "ratingScore": 8,
  "ratingVolume": 200,
  "workingHours": [
    { "day": "Monday", "formatted_times": "10:00-22:00" },
    { "day": "Tuesday", "formatted_times": "10:00-22:00" }
  ],
  "deliveryHours": [
    { "day": "Monday", "formatted_times": "10:00-22:00" }
  ],
  "tipping": {
    "currency": "EUR",
    "tip_amounts": [100, 200, 500],
    "max_amount": 10000,
    "min_amount": 50,
    "type": "pre_tipping_amount"
  },
  "merchant": {
    "id": "0000000000000000000000ab",
    "name": "Sample Operator GmbH",
    "business_id": "FN 000000 a",
    "street_address": "Sample Address 1",
    "city": "Sample City",
    "post_code": "00000",
    "country": "Sample Country"
  },
  "digitalServicesAct": {
    "name": "Sample Operator GmbH",
    "business_id": "FN 000000 a",
    "address": "Sample Address 1, 00000 Sample City, Sample Country",
    "self_certification": "...",
    "report_item_url": "https://www.example.com/report"
  },
  "assortmentId": "00000000000000000000000b",
  "primaryLanguage": "fi",
  "selectedLanguage": "en",
  "availableLanguages": [
    { "language": "fi", "name": "Finnish", "autotranslated": false },
    { "language": "en", "name": "English", "autotranslated": true }
  ],
  "menuCategoryCount": 13,
  "menuItemCount": 96,
  "menuCategories": [
    {
      "id": "00000000000000000000000c",
      "slug": "burgers-1",
      "name": "Burgers",
      "image": "https://wolt-menu-images-cdn.wolt.com/menu-images/sample.jpg",
      "blurhash": "UkSFbBRj.Tx[sRjZW=WX%~odI9R+XAbIn#n$",
      "itemCount": 7,
      "subcategoryCount": 0
    }
  ],
  "menuItems": [
    {
      "id": "00000000000000000000000d",
      "categoryId": "00000000000000000000000c",
      "categoryName": "Burgers",
      "categorySlug": "burgers-1",
      "name": "Sample Burger",
      "description": "Beef patty, lettuce, tomato.",
      "image": "https://wolt-menu-images-cdn.wolt.com/menu-images/sample.jpg",
      "blurhash": "UkSFbBRj.Tx[sRjZW=WX%~odI9R+XAbIn#n$",
      "currency": "EUR",
      "currentPrice": "EUR 9.90",
      "currentPriceCents": 990,
      "originalPriceCents": 1090,
      "discounted": true,
      "discountAmount": 100,
      "discountPercent": 9,
      "lowestPriceCents": 990,
      "lowestPriceWindowDays": 30,
      "dietaryPreferences": [],
      "vatPercentage": 14,
      "tags": [],
      "allowedDeliveryMethods": ["homedelivery"],
      "isAlcohol": false,
      "isWoltPlusOnly": false
    }
  ],
  "scrapedAt": "2026-01-01T00:00:00.000Z"
}
```

### Plan Requirement

A paid Apify plan is recommended. Venue pages, menus and most city listings load over the default proxy setting. Wolt's city listing endpoint refuses some datacenter exits, so the actor starts on the proxy you configure and, when a request is refused, retries it over Residential geo-targeted at the searched city's own country. Selecting Residential in `proxyConfiguration` up front avoids the wasted first attempt. Free Apify plans include no residential proxies, so on a free plan some city listings can fail to load.

What you will see in the run log:

- Warning when the selected connection is unavailable: "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."
- Info once pages are served that way: "Served over the backup connection (the default connection was unavailable)."
- The run fails (instead of returning a silent empty dataset) when no page could be loaded at all. The message says either that the proxy connection was refused for this account and a paid plan is required, that residential proxies are unavailable and the backup connection could not load the requested Wolt pages, or that the requests failed and this is a connection problem, not an empty search. That connection message appears only when a request really failed. When nothing loaded because the input matched nothing, the run also fails, with a specific message instead: a city name that matches no Wolt city, a URL city that Wolt does not have, URLs that are not scrapable Wolt pages (discovery or language-less), URL mode with no URLs, or venues Wolt reports as not found.

You can also supply your own proxy URLs in `proxyConfiguration`.

### FAQ

#### How much does it cost?

You pay per venue returned; turning on "Fetch full menu" makes each venue record larger but does not add a separate charge. The **Pricing** tab shows the current rates. Use `maxVenues` or `maxVenuesPerCity` to cap the cost of any run.

#### Is it legal to scrape Wolt?

This actor collects only publicly available restaurant and menu data. You are responsible for how you use it: follow Wolt's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Menu names, prices and hours are generally facts, but photos and brand assets may carry separate rights.

#### Can I get only new or changed restaurants on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Later runs return only `NEW` and `UPDATED` venues; unchanged ones are suppressed and not billed. `REAPPEARED` rows only ever occur for a venue that a previous run had already marked `EXPIRED`, which itself only happens when **Emit expired venues** was on for a run that scanned its search completely. If you never turn on **Emit expired venues**, you will never see `REAPPEARED`.

#### What's the difference between Resume and Incremental mode?

`resumeFromRunId` continues one specific interrupted run and is a one-time thing: paste a run or dataset ID and this run skips venues already collected there. `incrementalMode` is for running the exact same search again and again on a schedule; the actor remembers state itself, no ID needed. See "Resume vs incremental mode" above for the full behavior, including how the two combine.

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

An empty dataset with a successful run is normal when a city search or filter combination genuinely matches nothing, for example a cuisine filter, minimum rating or minimum review count that excludes every venue in a city, or a URL that doesn't resolve to any venues. When at least one other page loaded, a city that cannot be resolved, a city listing that Wolt refuses or fails to return, or an unrecognized URL is skipped with a warning in the run log rather than failing the run, so a short dataset can come from these too; check the log. If nothing at all loaded, the run fails with a message naming the unmatched city, the unusable URL or the venue that was not found. Individual venues that come back not found, or still fail after retries, are skipped and counted in the run log; they do not fail the whole run. The run fails outright (the specific reason is in the run log) when the actor cannot load Wolt's own city directory at all (used to resolve city names and city-listing URLs), when a pasted "Resume from a previous run" ID does not match a run or dataset your account can read, when Incremental mode is combined with Resume for a state key that already has saved incremental state, or on any other unexpected error.

#### 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 e-commerce data?

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

<table>
<tr><td>🛒 <a href="https://apify.com/abotapi/grabfood-restaurants-scraper"><b>GrabFood Restaurant Scraper</b></a><br>Scrape GrabFood restaurants across Southeast Asia, including SG, MY, TH, VN, PH, ID, KH...</td><td>🛒 <a href="https://apify.com/abotapi/doordash-scraper"><b>DoorDash Scraper</b></a><br>Extract structured doordash.com data at scale. Search by keyword or paste store URLs to...</td></tr>
<tr><td>✈️ <a href="https://apify.com/abotapi/naver-map-scraper"><b>Naver Map Scraper</b></a><br>Scrape Naver Map places by keyword or URL: names, categories, ratings, phones, addresses...</td><td>🛒 <a href="https://apify.com/abotapi/zomato-scraper"><b>Zomato Scraper</b></a><br>Scrape restaurants and reviews from zomato.com. Get names, cuisines, ratings and votes...</td></tr>
<tr><td>🥦 <a href="https://apify.com/abotapi/resq-club-surplus-food-scraper"><b>ResQ Club Scraper</b></a><br>Scrape ResQ Club surplus food offers in Finland, Sweden and Estonia: name, price, current...</td><td>🧩 <a href="https://apify.com/abotapi/opentable-reviews-scraper"><b>OpenTable Reviews Scraper</b></a><br>Scrape full OpenTable.com restaurant reviews by keyword, area, or restaurant profile...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/wolt-restaurants-scraper/issues/open). 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.

# Actor input Schema

## `mode` (type: `string`):

Pick how to find venues. Search uses a list of cities or free-text place queries. URL mode pastes ready-made Wolt URLs (single venue or city restaurant page).

## `cities` (type: `array`):

City names or free-text places (e.g. "Helsinki", "Graz, Austria", "Stockholm"). Slugs from Wolt's own city directory are matched first; anything else falls back to geocoding. Leave blank in URL mode.

## `latitude` (type: `number`):

Search around an exact point (use with Longitude). Added to any cities listed; leave Cities empty to search only this point.

## `longitude` (type: `number`):

Longitude of the search point (use with Latitude).

## `urls` (type: `array`):

Ready-made Wolt URLs. Accepted: venue pages (/restaurant/<slug> or legacy /venue/<slug>), city pages (/<city> and /<city>/restaurants, optionally ?sorting=), category pages (/<city>/category/<slug>) and brand pages (/<city>/brand/<slug>), with any language prefix such as en, de or de-at. A discovery URL has no city and is skipped with a warning; language-less URLs are not valid. Multi-URL supported. Leaving this field untouched scrapes five distinct examples (a venue, two city listings, a category and a brand); the other prefilled links are alternate forms of the same targets, so they are shown but not run by default.

## `primaryCategories` (type: `array`):

Filter city listings by primary cuisine tag. Examples: pizza, burgers, sushi, vegan, kebab, asian, italian, breakfast, dessert, bbq, indian. Leave empty to include all.

## `minRating` (type: `integer`):

Minimum Wolt rating score (0-10 scale). Records below this are skipped. Leave empty for no minimum.

## `minRatingVolume` (type: `integer`):

Minimum number of ratings a venue must have to be included. Leave empty or 0 for no minimum.

## `categoryFilter` (type: `string`):

Keep only venues whose category or tag name contains this text, e.g. burger, pizza, sushi, asian. Case-insensitive.

## `sortBy` (type: `string`):

Order of venues within each city. Wolt's algorithmic default is the venue order Wolt's app shows. Other options sort the SERP client-side using the sortable scores Wolt returns per venue.

## `fetchMenu` (type: `boolean`):

When ON, every venue is enriched with the full menu (categories, items, prices, images, blurhashes, deal flags). Adds ~1 HTTP request per venue. Turn off for fast venue-only runs.

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

ON: full records with menu. OFF: basic listing rows without the menu fetch (same as turning Fetch menu off).

## `includeReviews` (type: `boolean`):

When ON, the rating score, rating bucket (1-5), and review count are populated on every record. Turn off if you don't need any rating data in the output.

## `language` (type: `string`):

ISO language code passed to Wolt for menu and venue text (en, de, fi, sv, fr, es, etc.). Defaults to en.

## `maxVenues` (type: `integer`):

Hard cap on total venues across the whole run. 0 means unlimited.

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

Alias of Max venues; when set it takes precedence. 0 means unlimited.

## `maxVenuesPerCity` (type: `integer`):

Per-city cap. Wolt city pages return 100-200 venues; lower this for fast smoke runs. 0 means unlimited per city.

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

City search needs a residential exit — the search endpoint refuses datacenter addresses. Leave this on the default and the actor will retry a refused request over Residential, geo-targeted at the city's own country; select Residential here to start there and skip the wasted attempt. Venue URLs and menus work fine on the default proxy.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a large crawl of venues without returning or charging for venues already collected there. Use this after an interrupted run, or when continuing a large city/catalogue pull in another run. For recurring daily monitoring of the same search, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring. The first run returns all matching venues as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED venues. A venue is UPDATED when any venue field changes (details, opening hours, delivery fees, menu items, prices and discounts), listed in changedFields; these live counters are excluded automatically: scrapedAt, deliveryEstimateMinutes, deliveryTimeRangeMin, deliveryTimeRangeMax, serpSortable, estimateMinutes, estimateRange, online and delivers; you can exclude more with Ignore fields below. Turn on "Emit unchanged" or "Emit expired" only when you also want those venues returned (and billed). State is kept separately for each city/URL/filter/menu setup; use State key when you want to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from the cities/URLs/filters/menu settings — different searches then never mix state with each other.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return venues that have not changed since the last run, marked UNCHANGED. This returns — and bills — extra rows you already have, so leave it off unless you specifically want the full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return venues 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 — not when Max venues capped it or when Resume was used. This returns — and bills — extra synthetic rows, so leave it off unless you need expiry tracking.

## `ignoreFieldsForChanges` (type: `array`):

Field names that must NOT make a venue count as UPDATED. They stay in the output, they just do not trigger a change or appear in changedFields. Use a dot for nested fields and it applies to every element of a list, for example menuItems.blurhash or menuItems.imageUrl. Already excluded automatically (live counters that change on every scrape): scrapedAt, deliveryEstimateMinutes, deliveryTimeRangeMin, deliveryTimeRangeMax, serpSortable, estimateMinutes, estimateRange, online, delivers. Changing this list never causes a one-time wave of UPDATED rows.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "cities": [
    "Helsinki"
  ],
  "urls": [
    "https://wolt.com/en/aut/graz/restaurant/kfc-graz",
    "https://wolt.com/en/aut/graz/restaurants",
    "https://wolt.com/en/fin/helsinki/restaurants",
    "https://wolt.com/en/aut/graz",
    "https://wolt.com/en/aut/graz/category/pizza",
    "https://wolt.com/en/aut/graz/brand/kfc",
    "https://wolt.com/en/aut/graz/venue/kfc-graz",
    "https://wolt.com/de/aut/graz/restaurant/kfc-graz",
    "https://wolt.com/fi/fin/helsinki/restaurants",
    "https://wolt.com/en/aut/graz/restaurants?sorting=rating",
    "https://wolt.com/en/aut/graz/restaurant/kfc-graz?lang=de",
    "https://wolt.com/en/discovery"
  ],
  "minRatingVolume": 0,
  "sortBy": "default",
  "fetchMenu": true,
  "includeDetails": true,
  "includeReviews": true,
  "language": "en",
  "maxVenues": 50,
  "maxVenuesPerCity": 0,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `detail` (type: `string`):

No description

## `menu` (type: `string`):

No description

## `changes` (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 = {
    "mode": "search",
    "cities": [
        "Helsinki"
    ],
    "urls": [
        "https://wolt.com/en/aut/graz/restaurant/kfc-graz",
        "https://wolt.com/en/aut/graz/restaurants",
        "https://wolt.com/en/fin/helsinki/restaurants",
        "https://wolt.com/en/aut/graz",
        "https://wolt.com/en/aut/graz/category/pizza",
        "https://wolt.com/en/aut/graz/brand/kfc",
        "https://wolt.com/en/aut/graz/venue/kfc-graz",
        "https://wolt.com/de/aut/graz/restaurant/kfc-graz",
        "https://wolt.com/fi/fin/helsinki/restaurants",
        "https://wolt.com/en/aut/graz/restaurants?sorting=rating",
        "https://wolt.com/en/aut/graz/restaurant/kfc-graz?lang=de",
        "https://wolt.com/en/discovery"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/wolt-restaurants-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 = {
    "mode": "search",
    "cities": ["Helsinki"],
    "urls": [
        "https://wolt.com/en/aut/graz/restaurant/kfc-graz",
        "https://wolt.com/en/aut/graz/restaurants",
        "https://wolt.com/en/fin/helsinki/restaurants",
        "https://wolt.com/en/aut/graz",
        "https://wolt.com/en/aut/graz/category/pizza",
        "https://wolt.com/en/aut/graz/brand/kfc",
        "https://wolt.com/en/aut/graz/venue/kfc-graz",
        "https://wolt.com/de/aut/graz/restaurant/kfc-graz",
        "https://wolt.com/fi/fin/helsinki/restaurants",
        "https://wolt.com/en/aut/graz/restaurants?sorting=rating",
        "https://wolt.com/en/aut/graz/restaurant/kfc-graz?lang=de",
        "https://wolt.com/en/discovery",
    ],
    "proxyConfiguration": { "useApifyProxy": True },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/wolt-restaurants-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 '{
  "mode": "search",
  "cities": [
    "Helsinki"
  ],
  "urls": [
    "https://wolt.com/en/aut/graz/restaurant/kfc-graz",
    "https://wolt.com/en/aut/graz/restaurants",
    "https://wolt.com/en/fin/helsinki/restaurants",
    "https://wolt.com/en/aut/graz",
    "https://wolt.com/en/aut/graz/category/pizza",
    "https://wolt.com/en/aut/graz/brand/kfc",
    "https://wolt.com/en/aut/graz/venue/kfc-graz",
    "https://wolt.com/de/aut/graz/restaurant/kfc-graz",
    "https://wolt.com/fi/fin/helsinki/restaurants",
    "https://wolt.com/en/aut/graz/restaurants?sorting=rating",
    "https://wolt.com/en/aut/graz/restaurant/kfc-graz?lang=de",
    "https://wolt.com/en/discovery"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/wolt-restaurants-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/wolt-restaurants-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/G2tsr3xcdcXvQodkL/builds/UW3In3TclqTvaipDI/openapi.json
