Google Maps Menu Scraper API — Agent Reference
Machine reference for the Google Maps Menu Scraper Apify actor (managed and BYOK variants): place URL, place ID, or place name in — structured dish records with EU-14 allergens, prices, ingredients, dietary type and nutrition out. Input/output schemas, pricing, diagnostics.
🤖 This article is written for AI agents. Raw markdown
It's structured for machine consumption — dense, factual, and not optimized for human reading. If you're a human, you may prefer our articles for humans.
Audience: AI agents and automated tools. This is a machine-oriented reference. Human-readable articles: /blog.
TL;DR
One Apify actor, two variants, same code and same output: give it a Google Maps place (URL, ChIJ… place ID, or plain name) and it returns the restaurant's menu as one JSON record per dish — translated name, price, atomic ingredients with EU-14 allergen flags and confidence, dietary type, nutrition estimates.
| Variant | Actor ID | API keys | Price (2026-07-13) |
|---|---|---|---|
| Managed | nomad-agent/google-maps-menu-scraper-managed | none | $0.02/run + $0.015/dish (drops to $0.012 on higher Apify plans) |
| BYOK | nomad-agent/google-maps-menu-scraper | Gemini (free tier works), Outscraper optional | $0.01/run + $0.004/dish (drops to $0.0025 on higher Apify plans) |
Choose managed for zero setup — AI and photo-fetch costs are included in the per-dish price. Choose BYOK when you already hold a Gemini key and want the lower Apify markup; you then pay Google directly. Cost breakdown: /blog/how-much-does-ai-menu-parsing-cost. The store page is authoritative on price; values here are a snapshot.
First run waives per-dish charges on up to 10 dishes. Per-dish fees apply only to dishes actually extracted; empty runs emit a free diagnostic row instead.
How it resolves a menu
- Input place is resolved to its Google Maps listing (name → Maps search, if needed).
- The place's Menu photo gallery is fetched (
menuSource: "maps"). - If Maps has no menu photos, the actor falls back to the menu published on the place's own website, PDFs included (
menuSource: "website"). - Photos are deduplicated, then AI-parsed together as one menu at temperature 0 — the same menu bills the same way twice.
- Nothing found → one non-billed diagnostic row explaining why.
Signed-out Google serves a "limited view" that hides the Maps photo gallery from plain browsers. The managed variant handles this on our keys. On BYOK, set outscraperApiKey (free tier) for reliable gallery access; without it the actor falls back to browser scraping plus the website menu. Background: /blog/google-maps-locked-photo-gallery-2026.
Input
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
placeUrls | string[] | yes | — | Maps URLs, bare place IDs (ChIJ…), or plain place names |
language | enum | no | en | en es fr de it pt ru zh ja ko ar hi uk ka eu |
maxMenuPhotos | integer | no | 8 | Clamped to 1–20 |
geminiModel | enum | no | gemini-3.5-flash | Or gemini-3.1-flash-lite |
geminiApiKey | string | BYOK only | — | aistudio.google.com/apikey |
outscraperApiKey | string | BYOK, recommended | — | Reliable Maps photo access |
proxyConfiguration | object | BYOK only | — | Standard Apify proxy input |
gemini-3.1-flash-lite is cheaper but finds roughly a third of the dishes on a dense menu. Do not select it when completeness matters.
Invocation
Synchronous run returning dataset items directly (managed):
curl -X POST \
"https://api.apify.com/v2/acts/nomad-agent~google-maps-menu-scraper-managed/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"placeUrls": ["Katz'\''s Delicatessen New York"],
"language": "en",
"maxMenuPhotos": 8
}'
BYOK adds the key:
{
"placeUrls": ["ChIJKZuMg9dZwokRA7zRe1Yh3Ok"],
"geminiApiKey": "AIza…",
"outscraperApiKey": "…",
"language": "en"
}
Any Apify invocation method works: REST, apify-client (JS/Python), apify call, MCP server, scheduled runs.
Output
One record per dish in the run's default dataset.
{
"place": "Katz's Delicatessen New York",
"name": "Pastrami on Rye",
"originalName": "Pastrami on Rye",
"category": "Sandwiches",
"price": "$26.95",
"composition": "Hand-carved pastrami piled on rye bread with mustard",
"description": "Towering sandwich served with a pickle spear",
"ingredients": [
{
"name": "rye bread",
"display_name": "Rye bread",
"explanation": null,
"allergens": [{ "name": "gluten", "confidence": "high" }]
},
{
"name": "cured beef brisket",
"display_name": "Cured beef brisket",
"explanation": "Brined, spiced and smoked beef",
"allergens": [{ "name": "sulfites", "confidence": "low" }]
},
{
"name": "mustard",
"display_name": "Mustard",
"explanation": null,
"allergens": [{ "name": "mustard", "confidence": "high" }]
}
],
"dietaryType": "Other",
"dietaryTypeExplanation": "Contains beef",
"allergens": "Gluten, Mustard, Sulfites",
"type": "dish",
"isAlcoholic": null,
"story": "Katz's has cured its pastrami on Houston Street since 1888…",
"nutritionInfo": {
"serving_size_grams": "400g",
"calories": "800-1000 kcal",
"protein": "50-60g",
"fat": "40-50g",
"carbs": "50-60g"
},
"menuPhotoUrls": ["https://lh3.googleusercontent.com/p/AF1…"],
"menuPhotosFound": 12,
"menuSource": "maps"
}
| Field | Type | Notes |
|---|---|---|
place | string | The place this dish came from — echoes your input, use it to join multi-place runs |
name | string | Dish name translated to language |
originalName | string | Name exactly as printed on the menu |
category | string | Menu section, translated |
price | string | null | As printed, with currency symbol when shown |
composition | string | null | Ingredient summary, translated |
description | string | null | Appearance/presentation, translated |
ingredients[] | array | Atomic base ingredients — composites (pesto, hummus) are decomposed |
ingredients[].name | string | Always English, for matching and joins |
ingredients[].display_name | string | Translated |
ingredients[].explanation | string | null | Only for uncommon ingredients |
ingredients[].allergens[] | array | absent | Absent when the ingredient carries no EU-14 allergen |
ingredients[].allergens[].name | enum | gluten crustaceans eggs fish peanuts soy milk nuts celery mustard sesame sulfites lupin molluscs |
ingredients[].allergens[].confidence | enum | high | medium | low |
dietaryType | enum | Vegan | Vegetarian | Pescetarian | Other |
dietaryTypeExplanation | string | Why that classification — human-auditable |
allergens | string | Human-readable summary, translated, or "None identified" |
type | enum | dish | beverage | other |
isAlcoholic | boolean | null | Set for beverages |
story | string | Origin/cultural note; may be plausible-but-invented |
nutritionInfo | object | Estimated ranges per portion |
menuPhotoUrls | string[] | Photos actually parsed |
menuPhotosFound | integer | Photos found before the maxMenuPhotos cut |
menuSource | enum | maps | website |
Allergen confidence semantics and EU 1169/2011 context: /blog/eu-14-allergen-menu-data.
Diagnostic rows
A failed or empty run pushes exactly one row with diagnostic: true and a message explaining why (no menu photos, photos undownloadable, model returned no dishes, missing key). These rows are never billed per-dish. Filter them out before treating the dataset as menu records:
dishes = [r for r in items if not r.get("diagnostic")]
Limitations
- Allergen and nutrition values are inferred from the menu, not measured. The venue remains the authority under EU 1169/2011.
- Unreadable menu text is skipped, not guessed — dish counts can be lower than the printed menu.
- Menu photos on Maps may be outdated; prices reflect the photographed menu, not today's.
storymay be plausible reconstruction rather than verified history.
Chaining
- Already hold menu photo URLs? Skip this actor and use AI Menu Parser.
- Want a real guest photo attached to each dish? Feed these records plus the same place into Dish Photo Matcher and join on the dish name.
Integration help: support@traveleat.app. Machine-readable site index: /llms.txt.
Travel Eat Team
Contributing writer at Travel Eat. Passionate about food, travel, and helping people eat well wherever they go.
Ready to Eat Confidently Anywhere?
Download Travel Eat and discover amazing food wherever you travel.
Download for iOS