# Uber Eats Scraper - Menu Prices, Sold Out & Change Monitor (`neverempty/uber-eats-menu-scraper`) Actor

For menu price tracking and restaurant market research: the full Uber Eats menu of any store URL in any country, every item with category, price in the store's own currency, sold out flag and badges, plus rating, delivery fee and time. Monitor mode returns only stores whose prices or items changed.

- **URL**: https://apify.com/neverempty/uber-eats-menu-scraper.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** E-commerce, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.60 / 1,000 store returneds

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

## Uber Eats Scraper - Menu Prices, Sold Out & Change Monitor

Get the full menu of any **Uber Eats** store from its store URL, in **any country**: every item with its **category, name, description, price in the store's own currency, sold out flag, popular badges and "liked by" percentage**, plus the store's **rating, number of ratings, delivery fee text, delivery time and open / orderable status**. Turn on **monitor mode** and each run returns **only the stores where an item price changed, a new item appeared, an item was removed, or an item went sold out or came back**, with the previous price.

Built for price tracking: machine schedules that check the same stores every day pay a small check fee for stores that did not change, instead of the full row.

Unofficial. Reads the same public store data the Uber Eats store page loads in a browser. No login, no Uber account, no search pages.

### What you get

One row per store:

| Column | Example |
|---|---|
| `storeName`, `chain`, `cuisines`, `priceLevel` | Chipotle Mexican Grill (525 S Orlando Ave) ; Chipotle Mexican Grill ; Mexican, Healthy ; 1 (number of price signs, 1 to 4) |
| `rating`, `ratingCount`, `ratingCountText` | 4.4 ; 7000 ; `7000+` (Uber Eats shows rounded counts; `ratingCount` is the number before the `+`) |
| `address`, `city`, `region`, `postalCode`, `country`, `latitude`, `longitude`, `phone` | 525 S Orlando Ave, Winter Park, FL 32789 ; US ; +14076283207 |
| `currency` | USD, GBP, EUR, JPY, AUD, CAD, MXN, TWD, CLP, ... - the store's own currency |
| `isOpen`, `isOrderable`, `hoursToday`, `unavailableMessage` | true ; true ; Open until 10:30 PM ; `Opens at 11:00 AM` |
| `deliveryStatusText`, `pickupStatusText` | `20 min` ; `5 min • 0 mi` (Uber's own short text; some stores put the fee here, e.g. `35 min • £2`) |
| `etaMinMinutes`, `etaMaxMinutes` | 20 ; 31 (`null` when Uber says delivery to the delivery point is unavailable - `deliveryStatusText` then says so) |
| `deliveryFeeText` | `$0 delivery fee (new users)` or `3.49 € Delivery Fee` - exactly what Uber shows a signed-out visitor for the delivery point (`null` when delivery there is unavailable) |
| `deliveryPointLatitude`, `deliveryPointLongitude`, `deliveryPointSource` | the delivery point used for fee and time; `store-location`, `input`, or `not-read` (then the delivery columns are `null`) |
| `menus` | the store's menus with their hours, e.g. Breakfast `Fri 4:00 AM – 10:29 AM` / Lunch / Dinner |
| `menuItemCount`, `soldOutItemCount`, `itemsWithoutPrice`, `menuComplete` | 68 ; 0 ; 0 ; true |
| `menuItems` | every item (see below) |
| `changeType`, `changes`, `priceChangedCount`, `newItemCount`, `removedItemCount`, `soldOutChangedCount`, `previousCheckedAt` | with a watch (see monitor mode) |

Each entry in `menuItems`:

| Field | Example |
|---|---|
| `menu`, `category`, `otherCategories` | Chipotle Menu ; Entrees ; \[] |
| `itemUuid`, `name`, `description` | c030eced-... ; Burrito Bowl ; Your choice of freshly grilled meat ... |
| `price`, `currency`, `priceSource` | 11.95 ; USD ; `price-field` |
| `priceNote` | text Uber shows instead of a price, e.g. `Priced by add-ons` (then `price` is `null`) |
| `soldOut`, `available` | false ; true |
| `badges` | `#1 most liked`, `Popular` |
| `likePercent`, `likeCount` | 77 ; 3506 (Uber's "77% of customers liked this based on 3506 reviews") |
| `calories`, `hasOptions`, `imageUrl` | 1020 ; true ; https://tb-static.uber.com/... |

### Prices and currencies: what we checked

- `price` is the store's own price in the store's own `currency`, as a plain number (11.95 USD, 12.95 GBP, 1800 JPY, 9.99 EUR, 17100 CLP). Nothing is converted.
- Uber Eats formats the price **text** on its pages for the country of the visitor's IP address. We measured it: seen from Japan, a US store's $5.50 item is shown as "¥6" and an Australian store's A$50.70 as "JPY 51". This Actor never reads the price from that kind of text when Uber sends the number: it uses Uber's own price number (in hundredths of the currency unit) and the store's currency code.
- For some stores Uber sends only the price text (no number). Then the text is used only if its currency sign or code matches the store's currency and it has the currency's normal number of decimals (for example `£7.95`, `¥1,380`, `CLP 17,100`, `€ 12,00`). Anything else (a foreign sign, a rounded `$6`) gives `price: null` - never a guess. `priceSource` tells which way each price was read (`price-field` or `price-label`), and `itemsWithoutPrice` counts the items left without a price.
- Checked on 2026-09-25: for 35 items in 7 stores (USD, GBP, JPY, EUR, AUD, TWD, MXN) the price and currency matched the menu prices the store page publishes for search engines (schema.org `MenuItem` offers) - 35 of 35. For 35 items in 7 stores whose prices come only as text (USD, GBP, JPY, EUR, TWD, CLP, ARS), 31 prices matched the text on the store page; the other 4 (a Taiwan store whose text starts with the Chinese words for "sold out") were left as `null`, not guessed.
- The delivery fee text is passed on only when it does not carry another currency's sign.

### Input

| Field | What it does |
|---|---|
| `storeUrls` | Uber Eats store URLs, one per line, from any country (`https://www.ubereats.com/store/<name>/<ID>`, `/gb/store/...`, `/jp/store/...`, `/fr/store/...` and so on), or the 22-character ID at the end of the URL, or the store UUID. Up to 500 per run. Empty = three example stores (US, Japan, France). |
| `deliveryLatitude`, `deliveryLongitude` | Optional delivery point for the delivery fee, delivery time and availability, used for every store in the run. Empty = each store is checked with its own address as the delivery point (the shortest time and lowest fee Uber offers for it). Menu prices do not depend on it. |
| `onlyChanges` | Monitor mode. Return only stores whose menu items changed. The first run returns every store as the starting point. |
| `watchName` | Name of the remembered menus, so different store lists can be watched on their own schedules. |
| `resetMonitoringState` | Forget what the watch remembered for the stores in this run and start over. |

Example - one store:

```json
{ "storeUrls": ["https://www.ubereats.com/store/chipotle-mexican-grill-525-s-orlando-ave/pgYJrIerSMO7M1ZJruL9ig"] }
```

Example - a daily price monitor for a list of stores:

```json
{ "storeUrls": ["https://www.ubereats.com/store/chipotle-mexican-grill-525-s-orlando-ave/pgYJrIerSMO7M1ZJruL9ig", "https://www.ubereats.com/gb/store/tapete/eRHiU3AmTJKQs5Yv8zmMTQ"], "onlyChanges": true, "watchName": "competitors" }
```

### Monitor mode: what counts as a change

Each changed store row carries `changes`, one entry per item change:

| `change` | Meaning |
|---|---|
| `price-changed` | the item's price differs from the last check (`previousPrice` → `price`, with `currency`) |
| `new-item` | an item that was not on that menu at the last check |
| `removed-item` | an item that was on that menu at the last check and is gone now |
| `sold-out` / `back-in-stock` | the item's sold out flag changed |

Not compared, because they move on their own and would be reported as fake changes: delivery fee, delivery time, open / closed right now, rating, number of ratings, likes, badges and the "Featured items" carousel at the top of the store page (we measured it: reading the same Chipotle store 5 times, the 20 carousel items were swapped for 20 others in 2 of the reads).

- Every change is **confirmed by reading the store a second time**; only changes that show up the same way in both reads are reported. If the second read fails, nothing is reported, charged or remembered for that store and a free `unreadable` row says so.
- Menus that Uber shows only at certain hours (a Breakfast menu, for example) are remembered separately. If a menu is not in the answer today, its items are **not** reported as removed, and when it comes back its items are not reported as new.
- Grocery and retail stores (a pharmacy, a supermarket) show only the first items of each aisle on the store page: those rows have `menuComplete: false`. For them monitor mode reports price and sold-out changes of items seen in both checks, never "new" or "removed".
- A price change is compared only when both prices were read in the same currency.

### How it behaves when something goes wrong

- **The store ID does not exist**: a free `not-found` row. Not retried.
- **Uber Eats does not serve the store to the public** (it answers "request is not allowed" or "inactive account" about that store, the same on two different connections): a free `not-available` row. Not retried further.
- **Uber Eats shows no menu items for the store** (closed for good or paused): a free `no-menu` row.
- **The answer could not be read** after asking again (twice directly, twice through a datacenter IP, then a residential IP - at most 5 residential requests per run): a free `unreadable` row. Never reported as "no data". The store is not remembered, so a later run returns it.
- **A check page / CAPTCHA** (an HTML check page or Uber's JSON "botdefense" reCAPTCHA challenge): this Actor does not solve or bypass it and does not ask again from another connection. It stops at once with a free `blocked` row; the remaining stores are not requested and nothing is charged for them (the run start fee is charged only if a store was already returned in that run). Ordinary 403/429/5xx answers that are not check pages are asked again from another IP address.
- **The store was read but the second read with the delivery point failed**: the menu is still returned, and the delivery columns (fee, time, status texts) are `null` with `deliveryPointSource: "not-read"` - they are never filled from a read made without the delivery point.
- **Your maximum charge per run is reached**: it stops before reading stores it could not bill, says so in a free `budget-reached` row and does not remember them.
- **Bad input**: a free `bad-input` row, nothing is requested.
- A run that could not read anything and charged nothing ends with a failed status, so your integration sees it.

### Pricing

Pay per event:

- **Run start** - once per run that returns at least one store (in monitor mode: once per run that read and compared at least one store).
- **Store returned** - per store row (the whole menu is included in the row).
- **Store checked** - monitor mode only, per store that was read and had no menu change (the store is not returned as a row).

Rows that explain a missing store, an unreadable answer, bad input, no change or a reached limit are free. The exact prices are on the Pricing tab.

### Notes and limits

- Data is what the Uber Eats store page loads, in English (`en`) where Uber has an English text; item names and descriptions are the store's own text.
- `isOpen` is Uber's own flag and can be `true` for a store that opens later today - read `hoursToday`, `unavailableMessage` and `deliveryStatusText` for the current state.
- `deliveryFeeText` is the text Uber shows to a signed-out visitor, so it often shows a new-user offer (`$0 delivery fee (new users)`). Service fees and small order fees are not included.
- Some item names appear in several categories (for example "Most Popular" and "Burgers"): the item is listed once per menu, with the first category in `category` and the rest in `otherCategories`.
- Speed: about 2.5 seconds per store (measured: 100 stores in 255 seconds). Without a delivery point a store is read twice the first time (once to learn its address, once with its address as the delivery point); in monitor mode the address is remembered, so later checks read each store once (plus one confirmation read when something changed). The default run timeout is 1 hour.
- Two schedules writing the same `watchName` at the same moment can overwrite each other's memory for the same store; give each schedule its own watch name.
- This Actor does not search Uber Eats (Uber's robots.txt disallows search pages). Bring the store URLs you want to read.

### Support

Found a store or an item that is read wrongly? Open an issue in the **Issues** tab with the store URL and the run ID.

# Actor input Schema

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

Uber Eats stores, one per line: the store page URL from any country (https://www.ubereats.com/store/<name>/<ID>, https://www.ubereats.com/gb/store/…, /jp/store/…, /fr/store/… and so on), the 22-character ID at the end of that URL, or the store UUID. Open the store on ubereats.com and copy the address bar. Search, category and brand pages are not read. Up to 500 stores per run. If empty, three example stores (US, Japan, France) are used.

## `deliveryLatitude` (type: `number`):

Latitude of the address you want the delivery fee, delivery time and availability for (for example 40.7580). Give it together with the longitude; it is used for every store in the run. If empty, each store is checked with its own address as the delivery point, which shows the lowest delivery time and fee Uber Eats offers for that store. Menu prices do not depend on it.

## `deliveryLongitude` (type: `number`):

Longitude of the delivery point (for example -73.9855). Give it together with the latitude.

## `onlyChanges` (type: `boolean`):

Return only stores where a menu item's price changed, a new item appeared, an item was removed, or an item became sold out or back in stock since the last run with the same watch name. Each changed store lists the item changes with the previous price. The first run returns every store as the starting point. An unchanged store is not returned as a row; it is charged only the small store-checked fee. Delivery fee, delivery time and rating are not compared (they change with the time of day).

## `watchName` (type: `string`):

Name of the remembered menus used to compare runs (letters, digits, dot, dash, underscore; up to 40). Setting it (or turning on monitor mode) fills changeType and changes. Use a different name for each list of stores you track on its own schedule. With monitor mode on and no name, the name "default" is used.

## `resetMonitoringState` (type: `boolean`):

Start this watch over for the stores in this run: forget their remembered menus before this run, so every store is returned as a first check.

## Actor input object example

```json
{
  "storeUrls": [
    "https://www.ubereats.com/store/chipotle-mexican-grill-525-s-orlando-ave/pgYJrIerSMO7M1ZJruL9ig",
    "https://www.ubereats.com/fr/store/eats-burger-salade/0rr_z6pqQXeC6uUUF--TPQ"
  ],
  "onlyChanges": false,
  "resetMonitoringState": false
}
```

# Actor output Schema

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

One row per Uber Eats store: name, address, coordinates, phone, cuisines, rating and number of ratings, currency, open and orderable flags, delivery fee text and delivery time for the delivery point, menus with their hours, and every menu item (category, name, description, price, sold out, badges, likes). With a watch: changeType and the list of item changes (price changed with the previous price, new item, removed item, sold out, back in stock). A missing store, a store no longer on Uber Eats, an answer that could not be read, an unusable input, a run with no change or a run that hit its maximum charge comes back as a free row that says why.

# 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 = {
    "storeUrls": [
        "https://www.ubereats.com/store/chipotle-mexican-grill-525-s-orlando-ave/pgYJrIerSMO7M1ZJruL9ig",
        "https://www.ubereats.com/fr/store/eats-burger-salade/0rr_z6pqQXeC6uUUF--TPQ"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/uber-eats-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 = { "storeUrls": [
        "https://www.ubereats.com/store/chipotle-mexican-grill-525-s-orlando-ave/pgYJrIerSMO7M1ZJruL9ig",
        "https://www.ubereats.com/fr/store/eats-burger-salade/0rr_z6pqQXeC6uUUF--TPQ",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/uber-eats-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 '{
  "storeUrls": [
    "https://www.ubereats.com/store/chipotle-mexican-grill-525-s-orlando-ave/pgYJrIerSMO7M1ZJruL9ig",
    "https://www.ubereats.com/fr/store/eats-burger-salade/0rr_z6pqQXeC6uUUF--TPQ"
  ]
}' |
apify call neverempty/uber-eats-menu-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/uber-eats-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/8q51dDSAeO8IZZPPQ/builds/vQ24DTZpSDbeLINOV/openapi.json
