# Uber Eats Scraper — Restaurants, Menus & Prices (`tortuga/ubereats-scraper`) Actor

Scrape Uber Eats restaurants and stores delivering to any address (search, cuisine, sort, filters) and full menus with item prices, options and offers. Track menu price changes on a schedule.

- **URL**: https://apify.com/tortuga/ubereats-scraper.md
- **Developed by:** [Trevor Ortega](https://apify.com/tortuga) (community)
- **Categories:** E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 store scrapeds

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?

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

## Uber Eats Scraper — Restaurants, Menus & Prices

Scrape **Uber Eats** the way a customer sees it at a given address: which restaurants, grocery and convenience stores deliver there, with rating, rating count, ETA, delivery fee, offers and availability, plus each store's cuisines, price level, address and coordinates, opening hours, phone and review snippets. In **menus** mode it outputs every menu item of a store with its price, was-price, offer, availability, popular flag and (optionally) all option groups with their extra prices. Turn on **Only new or changed items** and schedule it to track menu prices over time.

No login, no browser, no API key. Works in every Uber Eats country (US, Canada, UK, France, Australia, Mexico, Japan, ...).

### What data does Uber Eats Scraper extract?

**Stores** (one row per store, `type: "store"`):

| Field | Example |
|---|---|
| `name`, `url`, `storeUuid` | Art of Pizza, https://www.ubereats.com/store/art-of-pizza-state-%26-polk/gtJCq6f1Rhi0KtNllYN0BQ |
| `rating`, `ratingCount`, `ratingCountText` | 4.8, 1500, "1,500+" |
| `etaMinMinutes`, `etaMaxMinutes`, `etaText` | 23, 41, "23 min" |
| `deliveryFee`, `deliveryFeeText`, `currency` | 2.99, "$2.99 Delivery Fee", "USD" (when the site shows it for the address) |
| `promotion`, `promotions`, `badges` | "$5 off $25+", \["$5 off $25+"], \["Only on Uber Eats"] |
| `availability`, `isOrderable`, `isOpen` | "ACCEPTING_ORDERS", true |
| `priceLevel`, `cuisines` \* | "$$", \["Pizza", "Italian"] |
| `address`, `street`, `city`, `region`, `postalCode`, `country`, `latitude`, `longitude` \* | 727 S State St, Chicago, IL 60605 ... |
| `phone`, `hours`, `hoursTagline` \* | "+13128775335", \[{"days": "Every Day", "open": "11:00", "close": "22:00"}] |
| `reviews` \* | \[{"text": "Best deep dish...", "reviewerName": "Tina F.", "date": "2025-11-05"}] |
| `menuCount`, `menuItemCount`, `chainName`, `distanceText`, `diningModes` \* | 1, 128, null, "0.9 mi", \["DELIVERY", "PICKUP"] |
| `imageUrl`, `rank`, `searchTerm`, `deliveryAddress`, `scrapedAt` | position in the site's list for the search |

\* with **Include store details** (default on).

**Menu items** (one row per item, `type: "menuItem"`): `id` (store UUID + item UUID), `name`, `description`, `price`, `originalPrice` (struck-through was-price on discounted items), `priceText`, `currency`, `promotion` ("BOGO", "16% off", "3 for $17.70"), `isAvailable`, `isSoldOut`, `isPopular` and `popularLabel` ("#1 most liked", "Popular", "Featured"), `hasOptions`, `category` ("Sides"), `menu` ("Lunch/Dinner") and `menuHours`, `imageUrl`, `position`, store fields (`storeName`, `storeUrl`, `storeRating`, `storeAddress`, `storeCity`) and, with **Include item options**, `optionGroups`: `[{"name": "Sauce", "required": false, "minSelections": 0, "maxSelections": 1, "options": [{"name": "Ketchup", "price": 1.0, "isSoldOut": false}]}]`.

### How to scrape Uber Eats restaurants near an address

1. Mode **Restaurants / stores delivering to an address**.
2. **Delivery address**: a street address, ZIP/postcode or city. Add the city and, outside the US, the country (`10 Downing Street, London`) so the right place is chosen.
3. Optional **Search terms**: a cuisine (`pizza`, `sushi`), dish or brand. Empty = every store in the address's delivery feed (often 300-1,500 stores in big cities).
4. Optional **Sort** (recommended, highest rating, fastest delivery) and the site's filters: **Minimum rating**, **Price levels**, **Only stores with offers**.

Delivery fee, ETA and the list of stores are the ones Uber Eats shows for that address at the time of the run (fees are often hidden while a store is closed).

### How to scrape an Uber Eats menu with prices

Mode **Menus with item prices**, paste one or more **Store URLs** (any country). Every item of every menu (breakfast, lunch, ...) is output once, in menu order, with its own category; items repeated in "Featured items" / "Most popular" rows are flagged `isPopular`. Turn on **Include item options** to add sizes, toppings and add-ons with their prices (one extra request per item that has options). Without Store URLs, menus mode reads the first **Max stores** stores found for the address and search terms.

Grocery and convenience stores (supermarkets, pharmacies) return their storefront rows (best sellers, deals, featured aisles) rather than the whole catalogue.

### Input example

```json
{
  "mode": "stores",
  "address": "233 S Wacker Dr, Chicago, IL 60606",
  "searchTerms": ["pizza"],
  "sort": "rating",
  "minRating": "4.5",
  "maxItems": 100,
  "includeDetails": true
}
```

```json
{
  "mode": "menus",
  "storeUrls": ["https://www.ubereats.com/store/sizl-3517-n-spaulding-ave/uFeEbuTxU9eP38C1XBqcvg"],
  "includeOptions": true,
  "maxItems": 500
}
```

### Output example

```json
{
  "type": "store",
  "storeUuid": "71db3bee-8aa9-587a-bbc1-d4acedd05adf",
  "name": "Nonnina",
  "url": "https://www.ubereats.com/store/nonnina-new/cds77oqpWHq7wdSs7dBa3w",
  "rating": 4.9,
  "ratingCount": 32,
  "ratingCountText": "32",
  "priceLevel": null,
  "cuisines": ["Italian", "Pasta", "Comfort Food", "Pizza"],
  "etaMinMinutes": 14,
  "etaMaxMinutes": 31,
  "etaText": "14 min",
  "deliveryFee": null,
  "currency": "USD",
  "promotion": null,
  "availability": "NOT_ACCEPTING_ORDERS",
  "distanceText": "0.7 mi",
  "address": "340 North Clark Street, Chicago, IL 60654",
  "city": "Chicago",
  "latitude": 41.8887,
  "longitude": -87.6312,
  "phone": "+13128220077",
  "hours": [{"days": "Monday - Saturday", "open": "14:45", "close": "22:30"}],
  "menuItemCount": 44,
  "searchTerm": "pizza",
  "rank": 1,
  "deliveryAddress": "233 S Wacker Dr, Chicago, IL 60606-6300, USA",
  "scrapedAt": "2026-10-10T06:00:53+00:00"
}
```

```json
{
  "type": "menuItem",
  "id": "b857846e-e4f1-53d7-8fdf-c0b55c1a9cbe:5fd8e592-40b1-5b0b-9948-cca9a57eff75",
  "name": "French Fries",
  "description": "Crispy golden French fries, hot and salty, perfect for a savory snack or a tasty side.",
  "price": 3.99,
  "originalPrice": null,
  "priceText": "$3.99",
  "currency": "USD",
  "promotion": null,
  "isAvailable": true,
  "isPopular": true,
  "popularLabel": "Featured",
  "hasOptions": true,
  "category": "Sides",
  "menu": "Sizl Menu",
  "menuHours": "4:00 PM – 11:45 PM",
  "storeName": "Sizl (3517 N Spaulding Ave)",
  "storeUrl": "https://www.ubereats.com/store/sizl-3517-n-spaulding-ave/uFeEbuTxU9eP38C1XBqcvg",
  "optionGroups": [{"name": "Sauce", "required": false, "minSelections": 0, "maxSelections": 1,
                    "options": [{"name": "Ketchup", "price": 1.0}, {"name": "Garlic Sauce", "price": 2.0}]}]
}
```

### Monitor changes on a schedule

Turn on **Only new or changed items** and the Actor remembers what earlier runs of the same input returned. In menus mode each run outputs only menu items that are new or whose price, was-price, offer or availability changed (a price increase, an item selling out, a new BOGO deal); in stores mode only new stores or stores whose rating, delivery fee or offer changed. Unchanged rows are dropped before they are saved (and before option groups are fetched), so they are not charged. Save the input as a task (for example your competitors' store URLs with **Max results** large enough to cover their menus), then add a Schedule in Apify Console (Schedules → Create new, e.g. daily). Output rows carry `changeType` (`new` or `changed`), and changed ones carry `previousValues`, e.g. `{"price": 14.99}` for an item whose price went from $14.99 to its new price. Use **Monitoring key** to keep several schedules on the same input separate.

### Does it work without login or a browser?

Yes. It reads the same JSON the Uber Eats website loads, with plain HTTP requests: no account, no browser, no Uber API key. Each worker keeps its own cookies on one proxy IP, like a single visitor.

### Proxy note

The default Apify Proxy (datacenter) works and is the cheapest option. Each run makes one request per 80 stores of a search, one per store page (details or menu) and one per item with options. If a run reports bot-defense errors, switch the proxy to residential.

### How much does it cost to scrape Uber Eats?

Pay per result: $0.002 per store, $0.0015 extra per store page opened (store details), $0.0007 per menu item. 100 stores with details cost $0.35; a 120-item menu costs $0.084. Platform usage (compute, proxy) is billed separately and is small (a few cents per thousand rows). With monitoring on, only new or changed rows are charged.

### Is it legal to scrape Uber Eats?

The Actor collects only what Uber Eats shows publicly to every visitor without logging in: store listings, menus and prices, business contact details (phone, address) and the public review snippets shown on store pages with the reviewer's first name and initial. Nothing behind a login, no customer accounts, no orders. Review texts and reviewer names can be personal data in some jurisdictions (GDPR, CCPA): have a legitimate purpose, keep only what you need, and check Uber's terms and your local law before reusing the data. If you are unsure, consult a lawyer.

### Support

Missing a field or found a store that does not parse? Open an issue on the Actor page with the store URL and the run ID.

# Actor input Schema

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

`stores`: restaurants and shops that deliver to the address (one row per store, optionally only those matching the search terms). `menus`: one row per menu item (price, offer, availability, popular flag, options) for the Store URLs, or for the first stores found for the address.

## `address` (type: `string`):

Street address, ZIP/postcode or city, e.g. `233 S Wacker Dr, Chicago, IL` or `10 Downing Street, London`. Uber Eats shows the stores, delivery fees and ETAs for this address. Include the city (and country outside the US) so the right place is picked. Required in stores mode; optional in menus mode.

## `searchTerms` (type: `array`):

Searched like the Uber Eats search box: a cuisine (`pizza`, `sushi`), a dish (`burrito`) or a brand (`McDonald's`). Leave empty for all stores in the address's delivery feed.

## `storeUrls` (type: `array`):

Uber Eats store pages like https://www.ubereats.com/store/sizl-3517-n-spaulding-ave/uFeEbuTxU9eP38C1XBqcvg (any country). In menus mode every menu item is output; in stores mode one detailed row per store.

## `sort` (type: `string`):

Order of the store list, as on the website.

## `minRating` (type: `string`):

Only stores rated at least this much (the site's rating filter).

## `priceLevels` (type: `array`):

Only stores with these price levels ($ = cheapest). Empty = all.

## `onlyOffers` (type: `boolean`):

Only stores running a deal (BOGO, % off, free delivery), the site's Offers filter.

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

Stores mode: open every store page to add cuisines, price level, full address with coordinates, opening hours, phone, delivery fee, review snippets and menu size. One extra request per store, charged as `store-details`. Off = the search card only (name, rating, ETA, fee when shown, offers, image).

## `maxStores` (type: `integer`):

Menus mode without Store URLs: read the menus of this many stores from the address / search results.

## `includeOptions` (type: `boolean`):

Menus mode: add each item's option groups (sizes, toppings, sides, sauces) with their extra prices. One extra request per item that has options, so runs take longer.

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

Stop after this many rows (stores or menu items). Keeps cost predictable.

## `onlyNewOrChanged` (type: `boolean`):

Remember what earlier runs of this search returned and output only new items or items whose price/status changed. Use with a Schedule; unchanged items are not charged. Menu items are compared on price, was-price, offer and availability; stores on rating, delivery fee and offer. With this on, Max results is the number of rows checked each run (the watched window).

## `monitoringKey` (type: `string`):

Name for this watch, so several schedules on the same search stay separate; defaults to a key derived from the search input.

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

Apify Proxy (datacenter) works for Uber Eats and is the cheapest option; each worker keeps one IP with its own cookies. Switch to residential only if runs report bot-defense errors. Proxy usage is billed to your account.

## Actor input object example

```json
{
  "mode": "stores",
  "address": "233 S Wacker Dr, Chicago, IL 60606",
  "searchTerms": [
    "pizza"
  ],
  "sort": "recommended",
  "minRating": "any",
  "onlyOffers": false,
  "includeDetails": true,
  "maxStores": 10,
  "includeOptions": false,
  "maxItems": 100,
  "onlyNewOrChanged": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (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 = {
    "address": "233 S Wacker Dr, Chicago, IL 60606",
    "searchTerms": [
        "pizza"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("tortuga/ubereats-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 = {
    "address": "233 S Wacker Dr, Chicago, IL 60606",
    "searchTerms": ["pizza"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("tortuga/ubereats-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 '{
  "address": "233 S Wacker Dr, Chicago, IL 60606",
  "searchTerms": [
    "pizza"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call tortuga/ubereats-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tortuga/ubereats-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/4bSfkfS4FEhyH1MZX/builds/QWN2o047FepvH4JDb/openapi.json
