# DoorDash Stores, Menus & Reviews (`superslowsloth/doordash-stores`) Actor

Every DoorDash store in a city with rating, review count, delivery fee, ETA, distance and address, plus each store full menu with prices and item ids, and its customer reviews.

- **URL**: https://apify.com/superslowsloth/doordash-stores.md
- **Developed by:** [Superslow Sloth](https://apify.com/superslowsloth) (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 $0.42 / 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?

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

## DoorDash Stores, Menus & Reviews

Give it a US, Canadian or Australian city and get every
[DoorDash](https://www.doordash.com) restaurant DoorDash lists there — rating,
review count, delivery fee, delivery estimate, distance and full postal
address. Switch the mode and the same input returns each store's **whole menu**
with prices, descriptions and DoorDash item ids, or its **customer reviews**
with the text, the stars, the date and the reviewer's name.

No account, no cookies, no login.

### Three modes, one dataset

| Mode | One row is | Where it comes from |
|---|---|---|
| **Stores** | a restaurant | one request for the whole city |
| **Menu items** | a dish | one request per store |
| **Reviews** | a customer review | one request for the whole city |

Rows from every mode share `store_id`, `store_name` and `store_url`, so a
dataset that holds more than one mode still joins on the store.

### Input

| Field | Notes |
|---|---|
| `mode` | `stores`, `menu-items` or `reviews`. |
| `location` | `"San Francisco, CA"`, `"Austin, TX"`, or a `doordash.com/food-delivery/...` URL. **Not** a bare zip code — see below. |
| `storeUrls` | Specific stores instead of a city search. Overrides `location`. |
| `cuisine` | Asks DoorDash for its own cuisine page for the city — `mexican`, `sushi`, `fast food`. A different, usually longer list, not a filter. |
| `searchTerm` | Keeps only stores whose name or cuisine tags contain this text. Applied here, not sent to DoorDash. |
| `maxResults` | Row cap. |
| `includeStoreStatus` | Stores mode only. Adds one request per store to fill `is_open`. |

#### Why a zip code is refused

DoorDash keys its public city pages on a city **and** a state.
`/food-delivery/94110-restaurants/` is a redirect to DoorDash's country index,
which renders no store list at all (measured 2026-08-25). A zip is therefore
rejected with that explanation before the run starts, rather than turning into
a paid run that returns nothing.

#### Why the search term is applied here

DoorDash's own store search refuses a client that has not picked a delivery
address — it renders with no store feed. There is no cold search endpoint to
forward a term to, so `searchTerm` narrows what the city page returned. To
widen the pool before narrowing it, set `cuisine` as well.

### What one row contains

**Stores**

| Field | Notes |
|---|---|
| `store_id` | DoorDash's numeric id, as a string. It is an identifier, not a quantity — kept as text so a spreadsheet cannot round it. |
| `store_name`, `store_url` | |
| `cuisine_tags` | DoorDash's own labels — `["Mexican", "Burritos"]`. DoorDash occasionally puts a price bucket (`"$$$"`) here instead of a cuisine; that is what it publishes and it is not corrected. |
| `rating`, `review_count` | Null on a row that came from a store URL — see below. |
| `delivery_fee` | The string DoorDash displays (`"$0 delivery fee"`), not a number: it is often a promotion rather than a price, and parsing it to a float would throw that away. |
| `delivery_time_minutes`, `delivery_time_display` | A number where DoorDash gives one, and its own text where it gives a range (`"32 - 42 min"`). |
| `distance` | From the searched location (`"1.8 mi"`). Null on a store-URL row: there is no consumer location to measure from. |
| `address`, `city`, `state`, `latitude`, `longitude` | |
| `price_range` | `"$$"`, `"$$$"`. Only the store page reports it. |
| `is_open` | Accepting delivery orders right now. **Only the store page reports this** — null on a city search unless `includeStoreStatus` is on. |
| `offers_pickup`, `is_sponsored` | `is_sponsored` marks paid placement in the city feed. |

**Menu items**: `item_id`, `item_name`, `item_description`, `price`,
`price_display`, `category` (the menu section).

`price` is filled only when DoorDash's price slot holds one plain amount.
DoorDash reuses that slot for bundle offers — `"2 for $17.79"`,
`"Buy 1, Get 1 Free"` — and neither number in those is the item's price, so
`price` is null and `price_display` keeps DoorDash's own text.

**Reviews**: `review_id`, `review_rating`, `review_text`, `review_date`,
`reviewer_display_name`, `review_source`.

#### Nulls are real

A field DoorDash did not report is `null`, never `0` and never `false`. The two
that catch people out:

- The **city feed carries no open/closed flag at all**, so `is_open` is null
  there. `false` would say DoorDash told us the store was shut.
- The **store page zeroes its rating block** for a client with no delivery
  address. A `0.0` rating on a restaurant with 1,362 reviews would be a lie, so
  it is published as null and the rating comes from the city feed instead.

Both are why the stores mode is worth running from a city even when you already
know the store: the city feed is the richer of the two sources.

#### `review_id` is derived, not DoorDash's

DoorDash publishes no review identifier. `review_id` is a stable digest of the
store, the reviewer, the date and the text: identical on every run and unique
within a store, which is what deduplication needs. It is not a DoorDash id and
cannot be handed back to DoorDash.

#### `item_id` can be null

The store page publishes its menu twice — as the rendered menu feed, which
carries item ids, and as schema.org, which does not. Where the feed arrives
empty the schema.org menu is used instead, and those rows carry `null` ids
rather than invented ones. Names, descriptions, prices and sections are the
same either way.

### Ceilings worth knowing before you set `maxResults`

A DoorDash city page lists roughly 40–50 stores and up to two published reviews
per store. So one location cannot yield much beyond ~50 store rows or ~100
review rows however high the cap is set. Add a `cuisine` to reach a different
page, or run several locations.

### Proxy

Residential **pinned to the US**, and neither half is optional. DoorDash
answers a datacenter address with HTTP 403.

Measured on the Apify platform, 2026-08-25: an unpinned residential run was
refused 403 on all four attempts across four exit addresses. The same input
with `apifyProxyCountry: "US"` returned 25 stores on the first try. DoorDash
operates in the US, Canada and Australia only, so an exit anywhere else is
refused on sight — set `CA` or `AU` for those storefronts.

An HTTP 200 whose body carries no page payload is treated as a block and
retried on a fresh address, not reported as a store with no data.

### Pricing

Pay per event: a small actor-start fee, plus one event per row named for what
the row is (`store-scraped`, `menu-item-scraped`, `review-scraped`). The start
fee is charged only after the input parses, so a run that fails on a typo costs
nothing.

# Actor input Schema

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

Stores returns one row per restaurant. Menu items returns one row per dish, with its price, description, section and DoorDash item id - this fetches each store's own page, so it is one request per store. Reviews returns one row per customer review, with the text, the star rating, the date and the reviewer's display name; DoorDash publishes up to two reviews per store, so a reviews run is capped by how many stores it covers.

## `location` (type: `string`):

A US, Canadian or Australian city with its state or province - "San Francisco, CA", "Austin, TX". A https://www.doordash.com/food-delivery/... URL works too. A bare zip code does NOT: DoorDash has no page for one - /food-delivery/94110-restaurants/ redirects to its country index - so a zip is refused with that explanation rather than run into an empty result. Ignored when store URLs are supplied.

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

Scrape these specific stores instead of searching a city, for example https://www.doordash.com/store/tadka-indian-restaurant-san-francisco-1363236/. Only the number at the end of the URL selects the store, so a URL with an out-of-date name in it still works. Supplying these overrides the city search.

## `cuisine` (type: `string`):

Ask DoorDash for its own cuisine page for this city - "mexican", "sushi", "fast food". This is a different, usually longer, list than the city's default one rather than a filter applied to it.

## `searchTerm` (type: `string`):

Keep only stores whose name or cuisine tags contain this text. Applied to what the city page returned, NOT sent to DoorDash: DoorDash's own store search refuses a client that has not set a delivery address, so there is no cold search endpoint to send it to. Use the cuisine field to reach a wider list before narrowing with this.

## `maxResults` (type: `integer`):

Stop after this many rows. A DoorDash city page carries about 40-50 stores, and each of those carries up to two reviews, so a stores or reviews run for one location cannot exceed roughly 50 or 100 rows however high this is set - raise the ceiling by adding a cuisine.

## `includeStoreStatus` (type: `boolean`):

Stores mode only. DoorDash's city page does not say whether a store is accepting orders right now, and only the store's own page does - so turning this on adds one request per store. Leave it off unless is\_open matters; with it off, is\_open is null rather than guessed.

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

Residential, pinned to the US, and neither part is optional. DoorDash answers a datacenter address with HTTP 403. Measured on the Apify platform 2026-08-25: an UNPINNED residential run was refused 403 on all four attempts across four exit addresses, and the same input with the country pinned to US returned 25 stores on the first try. DoorDash operates in the US, Canada and Australia only, so an exit outside those countries is refused on sight - use CA or AU for those storefronts.

## Actor input object example

```json
{
  "mode": "stores",
  "location": "San Francisco, CA",
  "maxResults": 50,
  "includeStoreStatus": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `rows` (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 = {
    "location": "San Francisco, CA",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("superslowsloth/doordash-stores").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 = {
    "location": "San Francisco, CA",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("superslowsloth/doordash-stores").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 '{
  "location": "San Francisco, CA",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call superslowsloth/doordash-stores --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,superslowsloth/doordash-stores"
        }
    }
}

```

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/QEciZLTnfxpfiPsXG/builds/U6rZUJuHbampJMet8/openapi.json
