# MealDeal Scraper (`king_carl/mealdeal-scraper`) Actor

Scrapes visible pre-checkout food delivery quotes across Uber Eats, DoorDash, and Grubhub.

- **URL**: https://apify.com/king\_carl/mealdeal-scraper.md
- **Developed by:** [Carl Okpala](https://apify.com/king_carl) (community)
- **Categories:** Other
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, NaN bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use 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

## MealDeal Actor

Custom Apify Actor for the Person B MealDeal workstream. It opens food-delivery platforms with Crawlee and Playwright, tries to build the requested cart, extracts visible item subtotals, and returns one normalized `MealDealResult`.

### Validate The Scraper

Run the normal code checks:

```powershell
npm run typecheck
npm test
npm run build
docker build -t mealdeal-actor .
````

Run the deterministic scraper test:

```powershell
npm run test:scraper
```

`test:scraper` points the real Actor platform flows at fixture pages in `fixtures/`. This proves the scraper can extract restaurant names, matching menu items, subtotal, delivery fee, service fee, small order fee, tax, discount, promo text, ETA, choose the cheapest platform by item subtotal, and return normalized quotes.

### Live-Site Smoke Test

Live delivery sites may block headless browsers, require login, show CAPTCHA, or hide checkout totals. Those are expected live-site outcomes; the Actor compares visible item subtotals instead of trying to checkout or inventing prices.

For normal Docker testing, keep `debug=false` to avoid large screenshot writes on Windows bind mounts.

### Apify Deployment

This Actor is configured as `mealdeal-scraper` in `.actor/actor.json`.

In **Windows PowerShell**, from this Actor folder:

```powershell
cd "C:\Users\Mr. Paul\Downloads\MealDeal Actor"
apify validate-schema
apify push
```

The Actor input supports Apify Proxy through `proxyConfiguration`. Leave `useApifyProxy` disabled for the first smoke test, then enable it in Apify Console if a platform blocks cloud traffic.

DoorDash and Uber Eats may still require a real user-visible browser session for some cart data. The supported production path is for the extension/backend to pass `userVisibleSnapshots` captured from the user's already logged-in tab. When a snapshot is supplied for a selected platform, the Actor compares that quote and skips live scraping for that platform.

### DoorDash Store Actors

DoorDash has strong browser security. MealDeal now uses external Apify Store actors for DoorDash menu prices when possible, then normalizes the result into the same `PlatformQuote` shape.

The tested working direct-store path is:

```text
crawlerbros/doordash-restaurant-scraper
```

Other actors are kept as fallbacks when the direct actor does not return usable menu data. `memo23/doordash-reviews-cheerio` currently requires renting the paid Store actor in Apify Console before this account can run it; the CLI cannot auto-rent it for you.

For best DoorDash results, provide a DoorDash store URL:

```json
{
  "address": "2550 Van Ness Avenue, San Francisco, CA",
  "restaurantName": "Halal City",
  "query": "Rice Platters",
  "cartItems": [{ "name": "Rice Platters", "quantity": 2 }],
  "platforms": ["doordash"],
  "doorDashStoreUrls": [
    "https://www.doordash.com/store/halal-city---soma-san-francisco-34620533"
  ],
  "doorDashUseExternalActors": true,
  "maxCandidatesPerPlatform": 3,
  "debug": false
}
```

This returns a DoorDash `quoteLevel` of `menu`: it is a menu-derived item subtotal, not checkout fees, tax, or final total.

### DoorDash Verification

Do not try to bypass DoorDash human verification. The supported local path is to use your own browser session after you manually complete verification or sign in.

In **Windows PowerShell**, from this Actor folder:

```powershell
cd "C:\Users\Mr. Paul\Downloads\MealDeal Actor"
npm run session:doordash
```

Chrome opens. In **Chrome**, complete DoorDash verification and sign in if needed. When DoorDash is usable, go back to **PowerShell** and press Enter. This saves cookies in `profiles\doordash`, which is ignored by Git.

Then run a local DoorDash smoke test in **PowerShell**:

```powershell
cd "C:\Users\Mr. Paul\Downloads\MealDeal Actor"

$storage = "storage-doordash-profile"
Remove-Item -Recurse -Force $storage -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path "$storage\key_value_stores\default" | Out-Null

@{
  address="525 Market St, San Francisco, CA"
  restaurantName="McDonald's"
  query="Big Mac"
  cartItems=@(@{ name="Big Mac"; quantity=1 })
  platforms=@("doordash")
  maxCandidatesPerPlatform=3
  debug=$true
  platformBrowserUserDataDirs=@{ doordash=(Resolve-Path "profiles\doordash").Path }
} | ConvertTo-Json -Depth 6 | Set-Content -Encoding UTF8 "$storage\key_value_stores\default\INPUT.json"

$env:APIFY_LOCAL_STORAGE_DIR = (Resolve-Path $storage).Path
$env:CRAWLEE_STORAGE_DIR = (Resolve-Path $storage).Path
npm run dev
Remove-Item Env:\APIFY_LOCAL_STORAGE_DIR -ErrorAction SilentlyContinue
Remove-Item Env:\CRAWLEE_STORAGE_DIR -ErrorAction SilentlyContinue

Get-Content "$storage\datasets\default\000000001.json"
```

Use `npm run dev` for this profile test. Docker runs Linux Chrome under Xvfb, so it cannot use an interactive Windows Chrome session reliably.

# Actor input Schema

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

Full delivery address used across all platforms.

## `restaurantName` (type: `string`):

Optional target restaurant name.

## `query` (type: `string`):

Food or cuisine query, such as Chicken Pad Thai or sushi.

## `cartItems` (type: `array`):

Items the Actor should try to add to cart.

## `platforms` (type: `array`):

Selected delivery platforms to scrape or compare from user-visible snapshots.

## `doorDashStoreUrls` (type: `array`):

Optional DoorDash store URLs. When supplied, MealDeal calls tested Apify Store actors to extract DoorDash menu prices instead of relying only on the blocked DoorDash browser flow.

## `doorDashUseExternalActors` (type: `boolean`):

Use external Apify Store actors for DoorDash menu data when possible. Disable only when testing the custom Playwright DoorDash flow.

## `userVisibleSnapshots` (type: `array`):

Optional normalized cart snapshots captured from a user's logged-in browser tab by the extension. When provided for a selected platform, the Actor compares this quote and skips live scraping for that platform.

## `maxCandidatesPerPlatform` (type: `integer`):

Maximum restaurant candidates to evaluate per platform.

## `debug` (type: `boolean`):

When enabled, saves debug screenshots to the run key-value store.

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

Optional Apify Proxy or custom proxy configuration for live scraping.

## `platformProxyConfigurations` (type: `object`):

Optional advanced per-platform proxy configurations keyed by ubereats, doordash, or grubhub.

## `proxyUrl` (type: `string`):

Optional advanced custom proxy URL used for all live scraping. Prefer Proxy configuration on Apify.

## `platformProxyUrls` (type: `object`):

Optional advanced per-platform custom proxy URLs keyed by ubereats, doordash, or grubhub.

## Actor input object example

```json
{
  "platforms": [
    "ubereats",
    "doordash",
    "grubhub"
  ],
  "doorDashUseExternalActors": true,
  "maxCandidatesPerPlatform": 3,
  "debug": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Default dataset containing one MealDealResult item with the input, platform quotes, best quote, savings, warnings, and timestamp.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("king_carl/mealdeal-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("king_carl/mealdeal-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call king_carl/mealdeal-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=king_carl/mealdeal-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "MealDeal Scraper",
        "description": "Scrapes visible pre-checkout food delivery quotes across Uber Eats, DoorDash, and Grubhub.",
        "version": "0.2",
        "x-build-id": "hmYYcknCXgWy3hTqC"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/king_carl~mealdeal-scraper/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-king_carl-mealdeal-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/king_carl~mealdeal-scraper/runs": {
            "post": {
                "operationId": "runs-sync-king_carl-mealdeal-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/king_carl~mealdeal-scraper/run-sync": {
            "post": {
                "operationId": "run-sync-king_carl-mealdeal-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "address",
                    "query",
                    "cartItems",
                    "platforms"
                ],
                "properties": {
                    "address": {
                        "title": "Delivery address",
                        "type": "string",
                        "description": "Full delivery address used across all platforms."
                    },
                    "restaurantName": {
                        "title": "Restaurant name",
                        "type": "string",
                        "description": "Optional target restaurant name."
                    },
                    "query": {
                        "title": "Food query",
                        "type": "string",
                        "description": "Food or cuisine query, such as Chicken Pad Thai or sushi."
                    },
                    "cartItems": {
                        "title": "Cart items",
                        "type": "array",
                        "description": "Items the Actor should try to add to cart.",
                        "items": {
                            "type": "object",
                            "properties": {
                                "name": {
                                    "title": "Item name",
                                    "type": "string",
                                    "description": "Requested menu item name.",
                                    "editor": "textfield"
                                },
                                "quantity": {
                                    "title": "Quantity",
                                    "type": "integer",
                                    "description": "Requested item quantity.",
                                    "default": 1,
                                    "editor": "number"
                                }
                            },
                            "required": [
                                "name"
                            ]
                        }
                    },
                    "platforms": {
                        "title": "Platforms",
                        "type": "array",
                        "description": "Selected delivery platforms to scrape or compare from user-visible snapshots.",
                        "items": {
                            "type": "string"
                        },
                        "default": [
                            "ubereats",
                            "doordash",
                            "grubhub"
                        ]
                    },
                    "doorDashStoreUrls": {
                        "title": "DoorDash store URLs",
                        "type": "array",
                        "description": "Optional DoorDash store URLs. When supplied, MealDeal calls tested Apify Store actors to extract DoorDash menu prices instead of relying only on the blocked DoorDash browser flow.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "doorDashUseExternalActors": {
                        "title": "Use DoorDash Apify Store actors",
                        "type": "boolean",
                        "description": "Use external Apify Store actors for DoorDash menu data when possible. Disable only when testing the custom Playwright DoorDash flow.",
                        "default": true
                    },
                    "userVisibleSnapshots": {
                        "title": "User-visible quote snapshots",
                        "type": "array",
                        "description": "Optional normalized cart snapshots captured from a user's logged-in browser tab by the extension. When provided for a selected platform, the Actor compares this quote and skips live scraping for that platform.",
                        "items": {
                            "type": "object"
                        }
                    },
                    "maxCandidatesPerPlatform": {
                        "title": "Max candidates per platform",
                        "type": "integer",
                        "description": "Maximum restaurant candidates to evaluate per platform.",
                        "default": 3
                    },
                    "debug": {
                        "title": "Debug mode",
                        "type": "boolean",
                        "description": "When enabled, saves debug screenshots to the run key-value store.",
                        "default": false
                    },
                    "proxyConfiguration": {
                        "title": "Proxy configuration",
                        "type": "object",
                        "description": "Optional Apify Proxy or custom proxy configuration for live scraping.",
                        "default": {
                            "useApifyProxy": false
                        }
                    },
                    "platformProxyConfigurations": {
                        "title": "Platform proxy configurations",
                        "type": "object",
                        "description": "Optional advanced per-platform proxy configurations keyed by ubereats, doordash, or grubhub."
                    },
                    "proxyUrl": {
                        "title": "Custom proxy URL",
                        "type": "string",
                        "description": "Optional advanced custom proxy URL used for all live scraping. Prefer Proxy configuration on Apify."
                    },
                    "platformProxyUrls": {
                        "title": "Platform custom proxy URLs",
                        "type": "object",
                        "description": "Optional advanced per-platform custom proxy URLs keyed by ubereats, doordash, or grubhub."
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
