# Google Maps Scraper (`vortex_data/google-maps`) Actor

Stop wasting your budget on slow, resource-heavy browser-based scrapers. This is the fastest, most cost-effective, and data-rich Google Maps scraper on Apify, designed for high-scale lead generation and market research.

- **URL**: https://apify.com/vortex\_data/google-maps.md
- **Developed by:** [VortexData](https://apify.com/vortex_data) (community)
- **Categories:** Lead generation, Automation, Developer tools
- **Stats:** 10 total users, 7 monthly users, 98.7% runs succeeded, 2 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $1.00 / 1,000 place 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/platform/actors/running/actors-in-store#pay-per-event

## 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

## Google Maps Scraper — fast HTTP, 30+ fields per place

> **The fastest, cheapest, most data-rich Google Maps scraper on Apify.**
> Pure HTTP via `curl_cffi` Chrome TLS impersonation. Zero browser. Quad-tree subdivision unlocks scraping more than Google's hard 120-results-per-area limit.

---

### 📖 Introduction

This Actor extracts business data from Google Maps **without launching a browser**. Where most Google Maps scrapers boot a headless Chromium for every page (slow, memory-hungry, expensive on Apify compute units), this one talks directly to the same internal `/search?tbm=map` JSON endpoint that Google's own JavaScript hits — using `curl_cffi` to perfectly impersonate a real Chrome TLS handshake so anti-bot doesn't notice the difference.

I built it because I wanted to scrape **whole cities**, not just neighbourhoods. Google's web interface caps a single map viewport at ~120 results no matter how far you scroll. The Actor breaks that ceiling with **quad-tree viewport subdivision**: when a viewport saturates (≥18 of the first 20 results are new), it auto-splits into 4 child viewports at one zoom level deeper and recurses up to a configurable depth. With `maxSubdivisionDepth=4` you get up to 256 viewports per seed — enough to drain Google's index across an entire metro area.

On top of that the Actor parses **every field reachable through the search XHR** — 46 distinct fields per place including the structured address (street/city/state/postal/country), the full amenities tree (`additionalInfo` with Accessibility / Service options / Payments / Atmosphere), place tags (`Identifies as women-owned`, `LGBTQ+ friendly`, `Latino-owned`), owner info, current open/closed status with next opening time, hotel-specific data (stars, price, check-in/out, amenities), restaurant menu URLs, plus code, sponsored-result detection, and more. Optional **website-contacts enrichment** visits each business's site and pulls emails + 8 social-media handles.

#### Why pick this Actor

| | This Actor | Browser-based Google Maps scrapers |
|---|---|---|
| Engine | `curl_cffi` Chrome TLS impersonation | Headless Chromium |
| Memory | 256–512 MB | 2–4 GB |
| Speed | **51 places in 3.7 s** (local, no proxy) | 60–120 s for the same set |
| Cost on Apify | pennies per 1 000 places | 5–10× more compute units |
| Coverage per area | **unlimited** via quad-tree subdivision | capped at Google's ~120 per viewport |
| Fields per place | **30–46 fields** | 15–25 typical |
| Built-in filters | 5 free post-fetch filters | usually missing |
| Resumability | auto-resumes on Apify migration | often re-scrapes from scratch |

---

### 🎯 Use cases

- 🧲 **Lead generation** — 84 %+ phone & website coverage, optional email + social-media enrichment from each business's site, unclaimed-listing detection (`claimThisBusinessUrl`), built-in `withoutWebsite` filter for cold-outreach lists.
- 📊 **Competitor monitoring** — quad-tree scans entire metro areas; structured `addressParts` makes geo-grouping trivial; `placeTags` segment by ownership and demographics.
- 📈 **Market analysis** — full `additionalInfo` amenities tree segments places by accessibility, payment methods, dining options, atmosphere; `categories` array surfaces secondary classifications Google hides behind the primary one.
- 🏨 **Travel & hospitality** — hotel-specific block: `hotelStars`, `hotelPrice`, `hotelCheckInDate` / `hotelCheckOutDate`, `hotelAmenities`, full multi-paragraph `longDescription`.
- 🔎 **Local SEO audits** — `ownerName`, `claimThisBusinessUrl`, `kgmid` (Knowledge Graph IDs), canonical Maps URL, `placeId` cross-reference.
- 🗺 **POI database building** — every place ships with `placeId` + `fid` + `cid` + `kgmid` for stable cross-system referencing.
- 🌐 **Multilingual datasets** — `additionalLanguages` re-runs the same area in extra languages to catch translations and regional categories.

---

### ⚙️ Input

You can feed the Actor in three ways — combine any of them in one run:

- **Search terms** + **location** — the most common path. Example: `["restaurant", "cafe", "bakery"]` near *"Brooklyn, New York"*. Each term is searched independently across all viewports & languages.
- **Direct Google Maps URLs** — pass `/maps/place/...` URLs in `startUrls` to scrape specific places without going through search.
- **Place IDs** — pass a list of Google Place IDs (`ChIJ…`) in `placeIds`; each is fetched directly.

#### Coverage controls

- `maxCrawledPlacesPerSearch` — hard cap per term (default `500`).
- `maxPlacesPerViewport` — per-viewport cap; Google maxes at ~120.
- `enableSubdivision` + `maxSubdivisionDepth` — quad-tree splitting (the magic that unlocks > 120 per area).
- `multiZoomDelta` — search each seed at zoom-N..zoom+N for +30–70 % extra unique places.
- `language` + `additionalLanguages` — re-search same area in multiple `hl=` codes.
- `customGeolocation` — GeoJSON Polygon / MultiPolygon / Point with `radiusKm`.
- Discrete geo composite: `countryCode` / `state` / `county` / `city` / `postalCode` (used when `locationQuery` is empty).

#### Built-in filters (post-fetch, free)

- `placeMinimumStars` — drop places below `two` / `twoAndHalf` / … / `fourAndHalf`.
- `websiteFilter` — `allPlaces` / `withWebsite` / `withoutWebsite`.
- `skipClosedPlaces` — drop permanently/temporarily closed.
- `searchMatching` — `all` / `only_includes` / `only_exact` (title vs term).
- `categoryFilterWords` — keep only matching categories.

#### Optional add-on: website-contacts enrichment

- `extractContactsFromWebsite` (default `false`) — visit each business's site and extract `emails`, `additionalPhones`, and 8 social-media URL fields (Facebook, Instagram, LinkedIn, Twitter/X, YouTube, TikTok, Pinterest, WhatsApp). Domain-level cache means chain stores share one fetch.

#### Performance

- `concurrency` — parallel viewport tasks (default `8`).
- `proxyConfiguration` — Apify residential proxy is **strongly recommended**.

---

### 📤 Output

One row per unique place, deduped by `placeId` across all viewports, languages, and search terms. Three pre-built dataset views are available in the Apify Console: **Overview**, **Lead generation**, **Hotels**.

```json
{
  "title": "Bird & Branch Coffee Roasters",
  "subTitle": "Family-run specialty coffee shop",
  "description": "Signature coffee drinks including burnt marshmallow, turmeric, & nut milks…",
  "categoryName": "Coffee shop",
  "categories": ["Coffee shop", "Cafe", "Corporate gift supplier"],

  "address": "359 W 45th St, New York, NY 10036, United States",
  "addressParts": {
    "street": "359 W 45th St",
    "city": "New York",
    "state": "New York",
    "postalCode": "10036",
    "neighborhood": "Manhattan",
    "countryCode": "US"
  },
  "location": {"lat": 40.7602998, "lng": -73.9907758},
  "entranceLocation": {"lat": 40.7602896, "lng": -73.9908287},

  "phone": "+1 917-265-8444",
  "phoneUnformatted": "+19172658444",
  "website": "http://www.birdandbranch.com/",
  "emails": ["hello@birdandbranch.com"],
  "instagrams": ["https://www.instagram.com/birdandbranchnyc"],
  "facebooks": ["https://www.facebook.com/birdandbranchnyc"],

  "totalScore": 4.6,
  "openingHoursToday": {"day": "Wednesday", "hours": "7 AM–7:30 PM"},
  "currentStatus": "Closed · Opens 7 AM",
  "nextOpensAt": "06:30",
  "permanentlyClosed": false,
  "temporarilyClosed": false,

  "placeId": "ChIJTVhsxFNYwokRXgPwYnY0vgI",
  "fid": "0x89c25853c46c584d:0x2be347662f0035e",
  "cid": "197653116721562462",
  "kgmid": "/g/11hbtg2w_k",
  "url": "https://www.google.com/maps/search/?api=1&query=…&query_place_id=ChIJ…",

  "timezone": "America/New_York",
  "ownerName": "Bird & Branch Coffee Roasters",
  "placeTags": ["Identifies as women-owned", "Identifies as Asian-owned"],
  "additionalInfo": {
    "Accessibility": [{"Wheelchair-accessible car park": true}],
    "Service options": [{"Dine-in": true}, {"Takeout": true}],
    "Payments": [{"NFC mobile payments": true}, {"Credit cards": true}]
  },
  "imagesCount": 944,
  "imageUrl": "https://lh6.googleusercontent.com/…/photo.jpg",
  "menu": "https://slicelife.com/restaurants/…/menu",

  "language": "en",
  "rank": 1,
  "scrapedAt": "2026-05-02T15:42:18Z",
  "isAdvertisement": false
}
````

For hotels you also get `hotelStars`, `hotelPrice`, `hotelCheckInDate`, `hotelCheckOutDate`, `hotelAmenities`, and the full multi-paragraph hotel `longDescription`. After every run a JSON summary is written to the **OUTPUT** key of the default key-value store (totals, durations, filters applied, dataset URL).

Export formats from the Apify Console: JSON / JSON Lines / CSV / Excel / HTML / RSS, or paginated API access.

***

### 💡 Miscellaneous

#### 💰 Pricing — pay-per-event

The Actor charges per result extracted, with optional bonus events when richer data is found:

| Event | When it fires | Suggested price |
|---|---|---|
| `place-scraped` | every place pushed to the dataset | `$0.0005` per result ($0.50 / 1 000) |
| `place-with-emails` | website enrichment yielded ≥ 1 email | `$0.0015` ($1.50 / 1 000) |
| `place-with-socials` | website enrichment yielded ≥ 1 social URL | `$0.0005` ($0.50 / 1 000) |

Without enrichment you pay only `place-scraped`. With enrichment you pay extra **only for results that actually included contacts** — never charged for failed fetches.

#### 🧠 Tips for maximum coverage

- Use **multiple search terms** instead of one — Google's category tagging is brittle, e.g. `["coffee", "cafe", "espresso bar", "coffee roaster"]`. The Actor dedupes by `placeId` so overlap is free.
- Increase `maxSubdivisionDepth` from 4 → 5 for very dense urban scrapes (each level multiplies viewports by 4).
- Set `multiZoomDelta=1` to add 30–70 % more unique places at the cost of 3× the search requests.
- For non-English regions add `additionalLanguages: ["es", "fr", "zh"]` — Google returns slightly different translations and categories per `hl=`.

#### ❓ FAQ

**How does this compare to the official Google Places API?**
The Places API caps each query at 60 results, costs $17 per 1 000 calls, and exposes fewer fields than this Actor. Quad-tree subdivision plus richer extraction gives this Actor a 30× cost advantage at city scale.

**Will I get blocked by Google?**
Not with Apify residential proxies (the default). Chrome TLS impersonation rotates per request, sticky sessions per viewport keep IPs anchored, and intelligent backoff handles 429/503. We routinely run thousands of requests without captchas.

**Does this extract reviews / photos / popular times?**
**No.** Google's review listing endpoint, photo metadata endpoint, and popular-times XHR all require browser-bound session tokens that JavaScript constructs from in-memory state — impossible from HTTP-only requests. If you need them, use a browser-based scraper (which will be 5–10× more expensive). For the 95 % of users who need leads, addresses, contacts, ratings, hotel data, and place identifiers, this Actor is the optimal choice.

**Can I resume an interrupted run?**
Yes. State is checkpointed every 30 s and on Apify's `PERSIST_STATE` event. Migrated runs auto-resume without re-pushing duplicates.

**Which fields are 100 % reliable?**
`title`, `categories`, structured address (`street`/`city`/`state`/`postalCode`/`countryCode`), `location`, `placeId`, `fid`, `cid`, `kgmid`, `totalScore`, `url`, `timezone`, `ownerName`, `imagesCount`, `language`, `rank`, `scrapedAt`. Phone & website are present on 84 %+ of places.

#### 🔌 Integrations

Works with the full Apify integration ecosystem: webhooks (`ACTOR_RUN_SUCCEEDED`/`FAILED`), Apify Schedules (cron), Apify Tasks (saved input presets), the Apify CLI for CI/CD, the Apify MCP server (drive from Claude / GPT / any LLM), plus Make, Zapier, n8n, Airtable, Google Sheets, Slack, Gmail.

#### 🛠 Tech stack

`curl_cffi` (Chrome TLS impersonation, fingerprint rotation) + `apify` SDK ≥ 3.3 + `crawlee` ≥ 1.5 + Python 3.12. No Node.js. No browser. No headless detection risk.

#### 📝 Honest limitations

The Actor does **not** extract review text, individual reviews, full week opening hours, popular times histograms, per-photo URL lists, Q\&A, or `reviewsCount` for non-hotels — Google has progressively locked these endpoints down to require browser-bound session tokens that aren't reconstructable from HTTP. Everything else is fair game.

#### 💬 Your feedback

Built and maintained by an independent developer. Bug reports and feature requests welcome on the **Issues** tab of this Actor — every report gets a response. If this Actor saves you time or money, please leave a ⭐️ review on the Apify Store.

#### 📅 Changelog

See [CHANGELOG.md](./CHANGELOG.md) for the full version history.

# Actor input Schema

## `searchStringsArray` (type: `array`):

Search queries to scrape, e.g. \['restaurant', 'cafe', 'pub']. Each term searches independently across all seed viewports and languages. You can also use direct place IDs in the format `place_id:ChIJ…`.

## `locationQuery` (type: `string`):

Free-text location, e.g. 'New York, USA' or 'Manhattan'. Resolved via OpenStreetMap Nominatim. Ignored if customGeolocation is set. If empty, falls back to the discrete geo fields below (countryCode/state/city/postalCode).

## `startUrls` (type: `array`):

Direct google.com/maps/place/... URLs. Scraped without going through search.

## `placeIds` (type: `array`):

List of Google Place IDs (format `ChIJ…`). Each one is fetched directly without going through search.

## `maxCrawledPlacesPerSearch` (type: `integer`):

Hard cap per search term across all viewports & languages. Quad-tree subdivision stops when this is hit. Default 500 — increase for dense city-scale scraping.

## `maxPlacesPerViewport` (type: `integer`):

Per-viewport cap. Google itself returns ≤120 per viewport scroll; values above 120 have no effect for one viewport.

## `enableSubdivision` (type: `boolean`):

When a viewport saturates (≥18 new of 20 first-page results), split into 4 quadrants and recurse. This is what unlocks scraping >120 results per area. Leave on.

## `maxSubdivisionDepth` (type: `integer`):

Max recursive splits. Each level multiplies viewports by 4 (depth=4 → up to 256 viewports per seed).

## `language` (type: `string`):

Google UI language (hl=). Affects place names, opening-hours strings, tag labels, etc.

## `additionalLanguages` (type: `array`):

Extra language codes to re-search the same areas. Useful for non-English regions where Google returns different translations & categories per language. Each language adds ~Nx searches.

## `zoom` (type: `integer`):

Override auto-derived zoom of the seed viewport. Higher zoom = denser results before subdivision.

## `multiZoomDelta` (type: `integer`):

Search each seed at zoom-N..zoom+N to maximize coverage. delta=1 → 3 zoom levels per seed (3x cost, +30-70% unique places). delta=0 disables.

## `categoryFilterWords` (type: `array`):

Keep only places whose categories contain ANY of these words (case-insensitive substring). Example: \['pizza', 'italian']. ⚠️ Categories can lead to false negatives — many places aren't tagged with the synonym you'd expect. Prefer multiple search terms when possible.

## `searchMatching` (type: `string`):

Restrict places by how their title matches the search term. 'all' keeps everything. 'only\_includes' requires every word of the term in the title. 'only\_exact' requires the title to equal the term.

## `placeMinimumStars` (type: `string`):

Drop places with rating below this threshold. Places with no reviews are also dropped.

## `websiteFilter` (type: `string`):

Keep only places that have / don't have a website. Useful for lead generation (target unclaimed/no-website businesses).

## `skipClosedPlaces` (type: `boolean`):

Drop places that Google has marked as permanently or temporarily closed.

## `geoStrictMatch` (type: `boolean`):

When ON (default), drops places whose coordinates fall outside the queried region's bounding box. Critical for state/country-level queries: without this, Google's residential-IP fallback can return places from completely different countries (e.g. searching 'Karnataka, India' returning Texas schools). Turn OFF only if you intentionally want to scrape a wider area than the queried location's polygon.

## `extractContactsFromWebsite` (type: `boolean`):

When enabled, the actor visits each place's website and scrapes its homepage (and optionally a /contact page) for: email addresses, additional phone numbers, and social-media URLs (Facebook, Instagram, LinkedIn, Twitter/X, YouTube, TikTok, Pinterest, WhatsApp). Adds 1–2 HTTP requests per place that has a website (~84% of places). Domain-level cache means chain stores share one fetch.

## `contactsFetchContactPage` (type: `boolean`):

When the homepage doesn't yield any email address, fetch one common contact-style page (/contact, /contact-us, /impressum, etc.). Doubles HTTP cost in the worst case.

## `contactsTimeoutSecs` (type: `integer`):

Hard timeout per website fetch. Kept short to avoid stalling on slow sites.

## `contactsSkipChains` (type: `boolean`):

Skip enrichment for big chains (McDonald's, Starbucks, Hilton, etc.) — their corporate websites don't represent the local franchise and flood your dataset with the same generic info.

## `countryCode` (type: `string`):

Discrete location field — used only when 📍 Location is empty. Combine with state/county/city/postalCode for precise geocoding.

## `state` (type: `string`):

Discrete location field. Used only when 📍 Location is empty.

## `county` (type: `string`):

Discrete location field (US county / Canadian regional district / French département). Used only when 📍 Location is empty.

## `city` (type: `string`):

Discrete location field. ⚠️ Do not include state/country here. Used only when 📍 Location is empty.

## `postalCode` (type: `string`):

Discrete location field. Combine with countryCode (never with city). Used only when 📍 Location is empty.

## `customGeolocation` (type: `object`):

GeoJSON Polygon, MultiPolygon, or Point. Overrides locationQuery and discrete geo fields. Point requires 'radiusKm'. MultiPolygon yields one seed per polygon.

## `concurrency` (type: `integer`):

How many viewports run in parallel. Each on its own sticky proxy session. Tune up for speed, down if rate-limited.

## `requestTimeoutSecs` (type: `integer`):

Per-HTTP-request timeout in seconds. Increase if your residential proxies are slow.

## `minRequestIntervalMs` (type: `integer`):

Global throttle: minimum gap between any two outbound requests. 0 = disabled.

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

Apify proxy settings. Residential proxies are strongly recommended.

## Actor input object example

```json
{
  "searchStringsArray": [
    "restaurant"
  ],
  "locationQuery": "Times Square, Manhattan, New York",
  "maxCrawledPlacesPerSearch": 500,
  "maxPlacesPerViewport": 80,
  "enableSubdivision": true,
  "maxSubdivisionDepth": 4,
  "language": "en",
  "additionalLanguages": [],
  "multiZoomDelta": 0,
  "searchMatching": "all",
  "placeMinimumStars": "",
  "websiteFilter": "allPlaces",
  "skipClosedPlaces": false,
  "geoStrictMatch": true,
  "extractContactsFromWebsite": false,
  "contactsFetchContactPage": true,
  "contactsTimeoutSecs": 10,
  "contactsSkipChains": true,
  "concurrency": 8,
  "requestTimeoutSecs": 15,
  "minRequestIntervalMs": 0,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `places` (type: `string`):

Default dataset — one row per unique Google Maps place. Deduped by placeId across all viewports, languages, and search terms.

## `runSummary` (type: `string`):

JSON object with totals, unique placeIds, languages used, viewports searched, completed tasks, duration, and the filters that were applied.

# 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 = {
    "searchStringsArray": [
        "restaurant"
    ],
    "locationQuery": "Times Square, Manhattan, New York",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("vortex_data/google-maps").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 = {
    "searchStringsArray": ["restaurant"],
    "locationQuery": "Times Square, Manhattan, New York",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("vortex_data/google-maps").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 '{
  "searchStringsArray": [
    "restaurant"
  ],
  "locationQuery": "Times Square, Manhattan, New York",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call vortex_data/google-maps --silent --output-dataset

```

## MCP server setup

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

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Google Maps Scraper",
        "description": "Stop wasting your budget on slow, resource-heavy browser-based scrapers. This is the fastest, most cost-effective, and data-rich Google Maps scraper on Apify, designed for high-scale lead generation and market research.",
        "version": "1.3",
        "x-build-id": "GAIHs0VEj05A14hyJ"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/vortex_data~google-maps/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-vortex_data-google-maps",
                "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/vortex_data~google-maps/runs": {
            "post": {
                "operationId": "runs-sync-vortex_data-google-maps",
                "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/vortex_data~google-maps/run-sync": {
            "post": {
                "operationId": "run-sync-vortex_data-google-maps",
                "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",
                "properties": {
                    "searchStringsArray": {
                        "title": "🔍 Search terms",
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Search queries to scrape, e.g. ['restaurant', 'cafe', 'pub']. Each term searches independently across all seed viewports and languages. You can also use direct place IDs in the format `place_id:ChIJ…`.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "locationQuery": {
                        "title": "📍 Location",
                        "type": "string",
                        "description": "Free-text location, e.g. 'New York, USA' or 'Manhattan'. Resolved via OpenStreetMap Nominatim. Ignored if customGeolocation is set. If empty, falls back to the discrete geo fields below (countryCode/state/city/postalCode)."
                    },
                    "startUrls": {
                        "title": "🔗 Direct Google Maps URLs",
                        "type": "array",
                        "description": "Direct google.com/maps/place/... URLs. Scraped without going through search.",
                        "items": {
                            "type": "object",
                            "required": [
                                "url"
                            ],
                            "properties": {
                                "url": {
                                    "type": "string",
                                    "title": "URL of a web page",
                                    "format": "uri"
                                }
                            }
                        }
                    },
                    "placeIds": {
                        "title": "🗃 Place IDs",
                        "uniqueItems": true,
                        "type": "array",
                        "description": "List of Google Place IDs (format `ChIJ…`). Each one is fetched directly without going through search.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "maxCrawledPlacesPerSearch": {
                        "title": "💯 Max places per search term",
                        "minimum": 1,
                        "maximum": 100000,
                        "type": "integer",
                        "description": "Hard cap per search term across all viewports & languages. Quad-tree subdivision stops when this is hit. Default 500 — increase for dense city-scale scraping.",
                        "default": 500
                    },
                    "maxPlacesPerViewport": {
                        "title": "Max places per viewport",
                        "minimum": 1,
                        "maximum": 120,
                        "type": "integer",
                        "description": "Per-viewport cap. Google itself returns ≤120 per viewport scroll; values above 120 have no effect for one viewport.",
                        "default": 80
                    },
                    "enableSubdivision": {
                        "title": "Quad-tree subdivision",
                        "type": "boolean",
                        "description": "When a viewport saturates (≥18 new of 20 first-page results), split into 4 quadrants and recurse. This is what unlocks scraping >120 results per area. Leave on.",
                        "default": true
                    },
                    "maxSubdivisionDepth": {
                        "title": "Max subdivision depth",
                        "minimum": 0,
                        "maximum": 7,
                        "type": "integer",
                        "description": "Max recursive splits. Each level multiplies viewports by 4 (depth=4 → up to 256 viewports per seed).",
                        "default": 4
                    },
                    "language": {
                        "title": "🌍 Primary language",
                        "type": "string",
                        "description": "Google UI language (hl=). Affects place names, opening-hours strings, tag labels, etc.",
                        "default": "en"
                    },
                    "additionalLanguages": {
                        "title": "Additional languages",
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Extra language codes to re-search the same areas. Useful for non-English regions where Google returns different translations & categories per language. Each language adds ~Nx searches.",
                        "default": [],
                        "items": {
                            "type": "string"
                        }
                    },
                    "zoom": {
                        "title": "Initial zoom",
                        "minimum": 1,
                        "maximum": 21,
                        "type": "integer",
                        "description": "Override auto-derived zoom of the seed viewport. Higher zoom = denser results before subdivision."
                    },
                    "multiZoomDelta": {
                        "title": "Multi-zoom expansion (±N)",
                        "minimum": 0,
                        "maximum": 3,
                        "type": "integer",
                        "description": "Search each seed at zoom-N..zoom+N to maximize coverage. delta=1 → 3 zoom levels per seed (3x cost, +30-70% unique places). delta=0 disables.",
                        "default": 0
                    },
                    "categoryFilterWords": {
                        "title": "🎢 Category filter words",
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Keep only places whose categories contain ANY of these words (case-insensitive substring). Example: ['pizza', 'italian']. ⚠️ Categories can lead to false negatives — many places aren't tagged with the synonym you'd expect. Prefer multiple search terms when possible.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "searchMatching": {
                        "title": "Match search term against title",
                        "enum": [
                            "all",
                            "only_includes",
                            "only_exact"
                        ],
                        "type": "string",
                        "description": "Restrict places by how their title matches the search term. 'all' keeps everything. 'only_includes' requires every word of the term in the title. 'only_exact' requires the title to equal the term.",
                        "default": "all"
                    },
                    "placeMinimumStars": {
                        "title": "⭐ Minimum star rating",
                        "enum": [
                            "",
                            "two",
                            "twoAndHalf",
                            "three",
                            "threeAndHalf",
                            "four",
                            "fourAndHalf"
                        ],
                        "type": "string",
                        "description": "Drop places with rating below this threshold. Places with no reviews are also dropped.",
                        "default": ""
                    },
                    "websiteFilter": {
                        "title": "🌐 Website filter",
                        "enum": [
                            "allPlaces",
                            "withWebsite",
                            "withoutWebsite"
                        ],
                        "type": "string",
                        "description": "Keep only places that have / don't have a website. Useful for lead generation (target unclaimed/no-website businesses).",
                        "default": "allPlaces"
                    },
                    "skipClosedPlaces": {
                        "title": "⏩ Skip permanently / temporarily closed",
                        "type": "boolean",
                        "description": "Drop places that Google has marked as permanently or temporarily closed.",
                        "default": false
                    },
                    "geoStrictMatch": {
                        "title": "🌍 Strict geographic match",
                        "type": "boolean",
                        "description": "When ON (default), drops places whose coordinates fall outside the queried region's bounding box. Critical for state/country-level queries: without this, Google's residential-IP fallback can return places from completely different countries (e.g. searching 'Karnataka, India' returning Texas schools). Turn OFF only if you intentionally want to scrape a wider area than the queried location's polygon.",
                        "default": true
                    },
                    "extractContactsFromWebsite": {
                        "title": "📧 Extract emails & socials from each business website",
                        "type": "boolean",
                        "description": "When enabled, the actor visits each place's website and scrapes its homepage (and optionally a /contact page) for: email addresses, additional phone numbers, and social-media URLs (Facebook, Instagram, LinkedIn, Twitter/X, YouTube, TikTok, Pinterest, WhatsApp). Adds 1–2 HTTP requests per place that has a website (~84% of places). Domain-level cache means chain stores share one fetch.",
                        "default": false
                    },
                    "contactsFetchContactPage": {
                        "title": "Fall back to /contact page if homepage has no email",
                        "type": "boolean",
                        "description": "When the homepage doesn't yield any email address, fetch one common contact-style page (/contact, /contact-us, /impressum, etc.). Doubles HTTP cost in the worst case.",
                        "default": true
                    },
                    "contactsTimeoutSecs": {
                        "title": "Per-website timeout (s)",
                        "minimum": 3,
                        "maximum": 60,
                        "type": "integer",
                        "description": "Hard timeout per website fetch. Kept short to avoid stalling on slow sites.",
                        "default": 10
                    },
                    "contactsSkipChains": {
                        "title": "Skip global chains for contacts enrichment",
                        "type": "boolean",
                        "description": "Skip enrichment for big chains (McDonald's, Starbucks, Hilton, etc.) — their corporate websites don't represent the local franchise and flood your dataset with the same generic info.",
                        "default": true
                    },
                    "countryCode": {
                        "title": "🗺 Country",
                        "type": "string",
                        "description": "Discrete location field — used only when 📍 Location is empty. Combine with state/county/city/postalCode for precise geocoding."
                    },
                    "state": {
                        "title": "State / region",
                        "type": "string",
                        "description": "Discrete location field. Used only when 📍 Location is empty."
                    },
                    "county": {
                        "title": "County",
                        "type": "string",
                        "description": "Discrete location field (US county / Canadian regional district / French département). Used only when 📍 Location is empty."
                    },
                    "city": {
                        "title": "🌇 City",
                        "type": "string",
                        "description": "Discrete location field. ⚠️ Do not include state/country here. Used only when 📍 Location is empty."
                    },
                    "postalCode": {
                        "title": "Postal code",
                        "type": "string",
                        "description": "Discrete location field. Combine with countryCode (never with city). Used only when 📍 Location is empty."
                    },
                    "customGeolocation": {
                        "title": "🛰 Custom search area (GeoJSON)",
                        "type": "object",
                        "description": "GeoJSON Polygon, MultiPolygon, or Point. Overrides locationQuery and discrete geo fields. Point requires 'radiusKm'. MultiPolygon yields one seed per polygon."
                    },
                    "concurrency": {
                        "title": "Concurrent tasks",
                        "minimum": 1,
                        "maximum": 30,
                        "type": "integer",
                        "description": "How many viewports run in parallel. Each on its own sticky proxy session. Tune up for speed, down if rate-limited.",
                        "default": 8
                    },
                    "requestTimeoutSecs": {
                        "title": "Per-request HTTP timeout (s)",
                        "minimum": 5,
                        "maximum": 60,
                        "type": "integer",
                        "description": "Per-HTTP-request timeout in seconds. Increase if your residential proxies are slow.",
                        "default": 15
                    },
                    "minRequestIntervalMs": {
                        "title": "Min interval between requests (ms)",
                        "minimum": 0,
                        "maximum": 5000,
                        "type": "integer",
                        "description": "Global throttle: minimum gap between any two outbound requests. 0 = disabled.",
                        "default": 0
                    },
                    "proxyConfiguration": {
                        "title": "Proxy",
                        "type": "object",
                        "description": "Apify proxy settings. Residential proxies are strongly recommended.",
                        "default": {
                            "useApifyProxy": true,
                            "apifyProxyGroups": [
                                "RESIDENTIAL"
                            ]
                        }
                    }
                }
            },
            "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
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
