# Wolt Restaurant Menu Scraper (`teodor_banea/wolt-restaurant-menu-scraper`) Actor

Scrape Wolt restaurants, grocery stores and full menus with prices across 961 cities in 30 countries. Search by keyword, list a whole city, or pull menus from venue URLs. Every item carries its venue, category, dietary labels and VAT. Clean JSON, no browser.

- **URL**: https://apify.com/teodor\_banea/wolt-restaurant-menu-scraper.md
- **Developed by:** [Banea Teodor](https://apify.com/teodor_banea) (community)
- **Categories:** E-commerce, Developer tools, Automation
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 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.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Wolt Restaurant & Menu Scraper

Extract restaurants, full menus and live prices from **Wolt** across **961 cities in 30 countries**.
Search venues by keyword, pull everything that delivers to a location, or hand it venue URLs and get
every menu item with its price, dietary labels and VAT. Output is flat, typed JSON, ready for a
spreadsheet or a pricing pipeline.

### Why use this Actor?

- **Real menu prices, not estimates.** Every item comes with its price in major currency units, the
  currency code, the pre-discount price when something is on offer, VAT percentage, and the unit
  price for retail items.
- **Dietary and allergen labels included.** Vegan, gluten-free and the rest come straight through, so
  you can slice a market by dietary segment without inferring anything from item names.
- **EU price-transparency field.** Where Wolt publishes it, `lowestPrice` carries the lowest price in
  the last 30 days, which is what the Omnibus directive asks retailers to show.
- **Menu rows carry their venue.** Each item includes the venue it belongs to, with its city, country,
  address and rating, so a single export answers "what does every sushi place in this city charge,
  and how well is it rated" with no joining.
- **Fast and cheap.** No browser. The Actor talks to Wolt's own JSON API, so a city venue list is one
  request and a complete restaurant menu is one more.
- **A schema that matches our Bolt Food scraper.** Venue and menu fields use the same names and units
  across both platforms, so you can stack them in one table and compare like for like.

### Coverage

961 cities across 30 countries, read live from Wolt's own city list at the start of every run, so new
cities work the day Wolt adds them. The largest markets:

| Country | Cities | Country | Cities |
|---|---|---|---|
| Finland | 109 | Croatia | 36 |
| Germany | 79 | Romania | 35 |
| Greece | 79 | Serbia | 31 |
| Slovakia | 78 | Kazakhstan | 27 |
| Denmark | 71 | Israel | 25 |
| Hungary | 66 | Poland | 25 |
| Czechia | 61 | Slovenia | 18 |
| Norway | 58 | Lithuania | 16 |
| Sweden | 51 | Latvia | 14 |

Plus Azerbaijan, Georgia, Albania, Austria, Estonia, Bulgaria, Iceland, Cyprus, Luxembourg, North
Macedonia, Kosovo and Malta. City names are matched ignoring case and accents.

### The three modes

| Mode | What you give it | What you get |
|---|---|---|
| **Search venues by keyword** | Search terms + a location | Venues matching each term, with ratings, fees and ETAs |
| **Browse city discovery page** | A location | The venues Wolt surfaces for that city |
| **Venue URLs → full menus** | Wolt venue page URLs | Every menu item of those venues, with prices |

Turn on **Include full menus** in the first two modes to get menu items instead of venue rows. Each
menu row then carries the venue it came from, so nothing is lost by not receiving a separate venue
list.

**Grocery stores need one extra switch.** Wolt Market and similar large venues keep their catalog
behind category pages, so a normal menu request returns nothing at all for them. Turn on **Expand
large store catalogs** to walk those pages, up to 60 per store. A run without it warns you by name
when it hits such a venue, so a partial result is never presented as a complete one. Restaurants
never need it.

### What does it cost?

You pay per result, plus the Apify platform usage the run consumes. Current rates are on this
Actor's page in the Apify Store.

Nothing is capped by default, so a run returns everything it finds. Use these to size one:

| What you want | Settings | Results |
|---|---|---|
| Quick look | Search `pizza`, Helsinki, max 10 venues | 10 venues |
| Venues in a city | Discovery, Helsinki | ~200 venues |
| Menus of 25 restaurants | Discovery + menus, max 25 venues | ~5,000 items |
| One grocery store's catalog | Venue URL + expand catalogs | ~1,000+ items |
| One competitor's full menu | Venue URLs, 1 URL | ~200 items |

Set **Maximum results** for a hard ceiling on how many results a run can produce. **Maximum venues**
and **Maximum menu items per venue** narrow it further. All three are optional and default to no
limit.

### Input

| Field | Type | Description |
|---|---|---|
| `mode` | select | `search`, `discovery`, or `venueUrls` |
| `searchQueries` | string\[] | Keywords, e.g. `["pizza", "sushi"]` (search mode) |
| `venueUrls` | URL\[] | Wolt venue pages (`venueUrls` mode) |
| `city` | string | City name, e.g. `Helsinki` |
| `latitude` / `longitude` | string | Exact point; overrides `city` |
| `includeMenus` | boolean | Also scrape every venue's full menu |
| `expandCatalogs` | boolean | Walk category pages so grocery stores return items |
| `language` | string | Language for menu text, e.g. `en`, `fi`, `de` |
| `maxVenues` | integer | Optional cap on venues; `0` (default) = no limit |
| `maxMenuItemsPerVenue` | integer | Optional cap per venue; `0` (default) = the whole menu |
| `maxItemsTotal` | integer | Optional hard ceiling on results; `0` (default) = no limit |
| `maxConcurrency`, `maxRequestsPerMinute` | integer | Speed limits; the defaults suit every mode |

#### Example input

```json
{
    "mode": "search",
    "searchQueries": ["pizza", "sushi"],
    "city": "Helsinki",
    "includeMenus": true
}
```

That scrapes the full menu of every pizza and sushi venue in Helsinki. Add `"maxVenues": 25` or
`"maxItemsTotal": 3000` if you want to bound it.

### Output

Menu items and venues each have a fixed set of fields. A field with no value is `null` rather than
missing, so every row has the same shape and CSV exports never shift columns.

#### Menu item

```json
{
    "venueId": "59437df72673d01cc5ff4a1f",
    "venueSlug": "uuno-jatkasaari",
    "venueName": "Uuno Jätkäsaari",
    "url": "https://wolt.com/en/fin/helsinki/restaurant/uuno-jatkasaari",
    "venueCity": "helsinki",
    "venueCountry": "FIN",
    "venueAddress": "Atlantinkatu 20 B",
    "venueRating": 9.4,
    "searchQuery": "pizza",
    "itemId": "6512e0f4a1b2c3d4e5f60718",
    "name": "Margherita",
    "description": "Tomato, mozzarella, basil",
    "categoryName": "Pizza",
    "price": 12,
    "originalPrice": null,
    "lowestPrice": null,
    "isDiscounted": false,
    "currency": "EUR",
    "vatPercentage": 14,
    "unitInfo": null,
    "unitPrice": null,
    "dietaryPreferences": ["vegetarian"],
    "isWoltPlusOnly": false,
    "optionGroupCount": 2,
    "language": "en",
    "isAutotranslated": false,
    "scrapedAt": "2026-08-24T11:20:00.000Z",
    "runId": "oTj3oUFU2hecG69aZ"
}
```

#### Venue

```json
{
    "venueId": "5c3f4d1e8f2a4b0001a2b3c4",
    "slug": "makikupla",
    "name": "Mäkikupla",
    "url": "https://wolt.com/en/fin/helsinki/restaurant/makikupla",
    "city": "helsinki",
    "country": "FIN",
    "currency": "EUR",
    "address": "Torkkelinkatu 2",
    "latitude": 60.187,
    "longitude": 24.951,
    "ratingScore": 9.2,
    "ratingVolume": 500,
    "priceRange": 2,
    "online": true,
    "delivers": true,
    "deliveryEstimateMinutes": 30,
    "deliveryEstimateRange": "25-35",
    "deliveryBasePrice": 1.49,
    "tags": ["pizza"],
    "imageUrl": "https://imageproxy.wolt.com/venue/...",
    "source": "discovery",
    "searchQuery": null,
    "scrapedAt": "2026-08-24T11:20:00.000Z",
    "runId": "oTj3oUFU2hecG69aZ"
}
```

#### Where things land

Everything goes to the run's **default dataset**, and that is the only dataset the Actor writes. Each
row there is one result. Which row type you get depends on the mode: menu items when menus are
scraped, venue records otherwise. Never a mix, so one export is always one shape.

Failed requests are reported in the run log with their URL, label and reason.

Prices are numbers in **major** currency units (`12` for €12.00, converted from the minor units Wolt
returns) with the currency in a separate field, never a formatted string you have to parse. Ratings
are on Wolt's 0–10 scale.

### Tips and tricks

- **Track prices over time with `venueId` + `itemId`.** Join runs on that pair to get a clean price
  history per dish, and use `runId` to keep merged exports separable.
- **Use several search terms.** Wolt's search returns up to about 100 venues per query and has no
  pagination, so `["pizza", "burger", "sushi", "vegan"]` covers far more of a city than one term.
  Venues are de-duplicated across queries, so you are never charged twice for the same venue.
- **Watch `isAutotranslated`.** Wolt machine-translates menus into the language you ask for. If you
  are comparing item names across markets, that flag tells you which text is original.
- **Set `maxItemsTotal` deliberately.** It is the hard ceiling on a run. Leaving it at `0` means the
  run returns everything it finds.

### Notes and limits

- Only public data is collected: the same venue, menu and price information any visitor sees on
  wolt.com. No login and no personal data.
- Runs go through Apify Proxy datacenter IPs, which Wolt's API serves normally.
- Menus reflect what is orderable at the moment of the run. Large Wolt Market venues serve their
  catalog as category pages rather than one response; enable `expandCatalogs` for those, and the run
  warns by name for any venue it had to leave partial.
- Wolt gates some of its older endpoints on an app version. This Actor uses the endpoints wolt.com
  itself consumes, but if every request starts failing at once, that is the likely cause. Please open
  an issue and it will be updated.

### Support

Found a bug or need another field? Open an issue on the Actor's **Issues** tab.

# Actor input Schema

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

Choose how to find venues: search Wolt by keyword, browse a city's discovery page, or extract full menus from specific venue URLs.

## `searchQueries` (type: `array`):

Enter one or more search terms (e.g. "pizza", "sushi", "burger"). Each query returns up to 100 venues — Wolt's search has no pagination, so use several queries to cover more of a city.

## `venueUrls` (type: `array`):

Enter Wolt venue page URLs (e.g. https://wolt.com/en/fin/helsinki/restaurant/makikupla). The full menu with prices is extracted for each venue. Only used in "Venue URLs → full menus" mode.

## `city` (type: `string`):

Enter the city to scrape (e.g. "Helsinki", "Berlin", "Budapest"). Wolt operates in ~960 cities across 30 countries; the name is matched ignoring case and accents.

## `latitude` (type: `string`):

Advanced: exact latitude to search from, e.g. "60.1699". Overrides City when set together with Longitude.

## `longitude` (type: `string`):

Advanced: exact longitude to search from, e.g. "24.9384". Overrides City when set together with Latitude.

## `includeMenus` (type: `boolean`):

Also scrape the full menu (all items with prices) of every venue found by search/discovery. A typical venue has 150–300 menu items, so this multiplies the number of results — lower "Maximum venues" to control cost.

## `expandCatalogs` (type: `boolean`):

Wolt Market and other large grocery venues keep their catalog behind category pages, so a normal menu request returns nothing for them. Turn this on to walk those categories (up to 60 extra requests per store). Restaurants never need it, and it can multiply your result count.

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

Two-letter language code for menu texts (e.g. "en", "fi", "de"). Wolt auto-translates menus; the output marks translated items with isAutotranslated.

## `maxMenuItemsPerVenue` (type: `integer`):

Optional cap on menu items per venue. Leave at 0 to get every venue's full menu.

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

Optional cap on how many venues are scraped (and how many menus are fetched when menus are on). Leave at 0 to take every venue Wolt returns.

## `maxItemsTotal` (type: `integer`):

Optional hard ceiling on billed results (venues, or menu items when menus are on). Leave at 0 for no limit. This is the most reliable way to bound what a run can cost.

## `maxConcurrency` (type: `integer`):

How many requests run in parallel. The default is safe; lower it if you see rate-limit errors.

## `maxRequestsPerMinute` (type: `integer`):

Throttle the overall request rate.

## Actor input object example

```json
{
  "mode": "search",
  "searchQueries": [
    "pizza"
  ],
  "venueUrls": [
    {
      "url": "https://wolt.com/en/fin/helsinki/restaurant/makikupla"
    }
  ],
  "city": "Helsinki",
  "includeMenus": false,
  "expandCatalogs": false,
  "language": "en",
  "maxMenuItemsPerVenue": 0,
  "maxVenues": 0,
  "maxItemsTotal": 0,
  "maxConcurrency": 10,
  "maxRequestsPerMinute": 120
}
```

# Actor output Schema

## `results` (type: `string`):

Menu items with prices, or venue records, depending on the mode. Menu rows carry the venue they belong to, including its city, country, address and rating. Every field is present on every row, so no cleaning step is needed to get a stable shape.

# 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 = {
    "searchQueries": [
        "pizza"
    ],
    "venueUrls": [
        {
            "url": "https://wolt.com/en/fin/helsinki/restaurant/makikupla"
        }
    ],
    "city": "Helsinki"
};

// Run the Actor and wait for it to finish
const run = await client.actor("teodor_banea/wolt-restaurant-menu-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 = {
    "searchQueries": ["pizza"],
    "venueUrls": [{ "url": "https://wolt.com/en/fin/helsinki/restaurant/makikupla" }],
    "city": "Helsinki",
}

# Run the Actor and wait for it to finish
run = client.actor("teodor_banea/wolt-restaurant-menu-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 '{
  "searchQueries": [
    "pizza"
  ],
  "venueUrls": [
    {
      "url": "https://wolt.com/en/fin/helsinki/restaurant/makikupla"
    }
  ],
  "city": "Helsinki"
}' |
apify call teodor_banea/wolt-restaurant-menu-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,teodor_banea/wolt-restaurant-menu-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/oUBUh30m6fMpUSDd7/builds/deTuKosGjQ75X6enl/openapi.json
