# 🗺️ Google Maps Scraper — Emails, Reviews & Photos (`pro100chok/google-maps-scraper`) Actor

Extract Google Maps places with phones, websites, hours, ratings — and verified business emails automatically pulled from each place's website. Full review history, photo URLs, 7 social networks. Pay only per result, from $0.003/place. No browser, no API key.

- **URL**: https://apify.com/pro100chok/google-maps-scraper.md
- **Developed by:** [Raven](https://apify.com/pro100chok) (community)
- **Categories:** Lead generation, E-commerce, Travel
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, NaN bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $1.90 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 — Emails, Reviews, Photos & Lead Data

**Build a lead list of any local business in any city — in minutes.** Pull names, phones, websites, addresses, opening hours, ratings, **business emails from each place's website**, social profiles, and full review history from Google Maps. Pay only for what you pull. No browser, no API key, no daily quotas.

🚀 **What you get:** A clean Excel/CSV/JSON-ready dataset of every barbershop in Berlin, every dentist in Texas, every coffee shop in Tokyo — with the **owner's email** already extracted from their website.

### 🎯 Built for

- 🧲 **B2B sales teams** — generate verified lead lists with email + phone for cold outreach
- 📍 **Local SEO agencies** — competitive analysis, GMB audits, NAP citation building
- 🍽️ **Restaurant / hotel analysts** — pricing, reviews, popularity, hours intelligence
- 🛒 **Franchise / market research** — map every competitor in a region with full contact details
- 🤖 **AI / LLM pipelines** — clean structured business data for downstream models
- 📊 **Real-estate & retail scouts** — find the right neighborhood by category density

### ⚡ Why this Actor

- ✅ **Emails included for free in the base price** — every place that has a website is enriched with verified business emails. Most competing actors charge this as a separate add-on
- ✅ **Bypass Google's 120-place limit** — enable `deepSearch` and the actor recursively splits any city into sub-neighborhoods, returning thousands of places per search term
- ✅ **Real reviews, not just samples** — paginate through the same internal review API the Google Maps website uses. Get all 1,500+ reviews of a popular place in ~12 seconds
- ✅ **Browser-free** — direct HTTPS calls with Chrome TLS fingerprinting. 10× faster than headless-browser actors, no memory hogs
- ✅ **7 social networks auto-extracted** — Facebook, Instagram, X (Twitter), LinkedIn, YouTube, TikTok, Telegram links pulled from each place's website
- ✅ **International-ready** — correct address parsing for US, EU, UK, CIS, Asia. ISO-2 country code, city, state, postal code, neighborhood — all separated cleanly
- ✅ **Pay only for results** — $0.003 per place + optional add-ons for reviews ($0.0005) and image URLs ($0.0002). No subscription, no minimum spend
- ✅ **Streamed output** — the dataset fills in real-time, you can pipe results out via the API while the run is still going

### 📦 What you get per place

| Group | Fields |
|---|---|
| **Identification** | `name`, `place_id`, `google_maps_url`, `category`, `categories[]`, `description` |
| **Address** | `address` (full), `city`, `state`, `zip_code`, `country_code` (ISO-2), `area` (neighborhood), `plus_code` |
| **Coordinates** | `latitude`, `longitude` |
| **Contact** | `phone`, `website`, `email` (primary), `emails[]` (all found) |
| **Social profiles** | `facebook`, `instagram`, `twitter`, `linkedin`, `youtube`, `tiktok`, `telegram` |
| **Reputation** | `rating` (0–5), `reviews_count`, `price_level` (`$`–`$$$$`) |
| **Status** | `permanently_closed`, `temporarily_closed` |
| **Hours** | `opening_hours` (full 7-day schedule, e.g. `Monday: 9 AM–7 PM \| Tuesday: ...`) |
| **Reviews** (add-on) | `reviews[]` — `text`, `rating`, `author`, `publishedAt`, `authorAvatar`, `authorId`, `photos[]` |
| **Images** (add-on) | `images[]` — direct CDN URLs (no binaries, just links) |
| **Provenance** | `search_query`, `search_location`, `scraped_at` (ISO-8601 UTC) |

### 🛠️ Input

| Field | Type | Default | What it does |
|---|---|---|---|
| 🔍 `searchStringsArray` | array | **required** | What to search — `barber shop`, `dental clinic`, etc. One per line |
| 📍 `locationQuery` | string | `""` | Free-text location: `New York, NY`, `Berlin, Germany`, `Minsk` |
| 🏙️ `city` / 🗺️ `state` / 🌍 `country` | string | `""` | Structured location (joined into `"City, State, Country"` if `locationQuery` is empty) |
| 🎯 `maxCrawledPlacesPerSearch` | integer | `100` | Hard cap per `(term, location)` pair |
| 🌐 `deepSearch` | boolean | `false` | Recursive geo-drilling — splits the area when ≥20 results are returned. Slower, but bypasses Google's ~120-place cap |
| 🏷️ `categoryFilter` | string | `""` | Keep only places whose category contains this substring (case-insensitive) |
| 🗣️ `language` | enum | `en` | Google interface language (`hl`) — 14 options |
| 🚩 `countryCode` | string | `us` | Google geolocation hint (`gl`) — 2-letter ISO code |
| 📧 `scrapeContactsFromWebsite` | boolean | `true` | Extract emails + socials from each place's website |
| 📭 `skipPlacesWithoutEmail` | boolean | `false` | Lead-list mode — only output places where an email was found |
| 💬 `includeReviews` | boolean | `false` | **Paid add-on**. Attach reviews per place (~20 per request, full pagination) |
| 🔢 `maxReviewsPerPlace` | integer | `20` | Cap per place (1–5000). For 1500 reviews ≈ 12 seconds |
| 🖼️ `includeImages` | boolean | `false` | **Paid add-on**. Attach photo URLs (extracted from review attachments) |
| 🔢 `maxImagesPerPlace` | integer | `20` | Cap per place (1–200) |
| 🔁 `maxRetries` | integer | `5` | Retries on transient network failures |
| ⚡ `concurrency` | integer | `3` | Parallel workers during `deepSearch` |

> 🔒 **Proxy is locked to Apify residential** in the country you choose — included in the price, you don't pick a proxy group.

#### 🟢 Quick example — every barbershop in Berlin with emails

```json
{
  "searchStringsArray": ["barber shop"],
  "locationQuery": "Berlin, Germany",
  "maxCrawledPlacesPerSearch": 500,
  "deepSearch": true,
  "scrapeContactsFromWebsite": true,
  "skipPlacesWithoutEmail": true,
  "language": "de",
  "countryCode": "de"
}
````

#### 🟡 Example — restaurants in NYC with full review history

```json
{
  "searchStringsArray": ["italian restaurant", "ramen", "steakhouse"],
  "locationQuery": "New York, NY",
  "maxCrawledPlacesPerSearch": 100,
  "includeReviews": true,
  "maxReviewsPerPlace": 200,
  "includeImages": true,
  "maxImagesPerPlace": 30
}
```

### 💵 Pricing — pay only per result

| Event | When you pay | Price (USD) |
|---|---|---|
| **`place`** | One place pushed to dataset | **$0.003** |
| **`contact`** | A unique email enriched from a website | **$0.002** |
| **`review`** | One review attached to a place (when `includeReviews: true`) | **$0.0005** |
| **`image`** | One image URL attached (when `includeImages: true`) | **$0.0002** |

**Real-world cost examples:**

- 🏪 **1,000 barbershops with emails** → 1,000 × $0.003 + ~400 unique emails × $0.002 ≈ **$3.80**
- 🍽️ **100 restaurants + 50 reviews each + 20 photos each** → $0.30 + $2.50 + $0.40 ≈ **$3.20**
- 🌆 **Full coverage of a mid-size city (3,000 places)** → ~$10

You can cap any run via **Default run options → Max total charge** in run settings — never blow your budget on accident.

Smart billing notes:

- **Duplicate emails are charged once.** If the same email shows up on multiple places (template leak / franchise HQ), you pay for it only once per run.
- **Junk emails are filtered out before billing.** `noreply@`, `example.com`, `info@hair.com`-style placeholder emails are excluded automatically.

### 📋 Output sample

```jsonc
{
  "place_id": "ChIJZW3FhdK3t4kRLXEtK4yueuE",
  "name": "Scissors & Scotch",
  "category": "Barber shop",
  "categories": ["Barber shop"],
  "address": "331 N St NE, Washington, DC 20002, USA",
  "city": "Washington",
  "state": "DC",
  "zip_code": "20002",
  "country_code": "US",
  "area": "Near Northeast",
  "plus_code": "WX4X+PH",
  "latitude": 38.9068,
  "longitude": -77.001,
  "phone": "(202) 481-2850",
  "website": "https://scissorsscotch.com/",
  "email": "info@scissorsscotch.com",
  "emails": ["info@scissorsscotch.com", "dc@scissorsscotch.com"],
  "facebook": "https://www.facebook.com/scissorsscotch",
  "instagram": "https://www.instagram.com/scissorsscotch/",
  "twitter": "https://twitter.com/scissorsscotch",
  "rating": 4.7,
  "reviews_count": 1422,
  "price_level": "$$",
  "permanently_closed": false,
  "temporarily_closed": false,
  "opening_hours": "Monday: 9 AM–8 PM | Tuesday: 9 AM–8 PM | Wednesday: 9 AM–8 PM | Thursday: 9 AM–8 PM | Friday: 9 AM–7 PM | Saturday: 9 AM–7 PM | Sunday: 10 AM–5 PM",
  "google_maps_url": "https://www.google.com/maps/search/?api=1&query=Scissors+%26+Scotch&query_place_id=ChIJZW3FhdK3t4kRLXEtK4yueuE",
  "reviews": [
    {
      "rating": 5,
      "text": "Best haircut I've had in DC. Stylist took time to understand what I wanted...",
      "publishedAt": "2 months ago",
      "author": "Mike Visalli",
      "authorAvatar": "https://lh3.googleusercontent.com/a/...",
      "authorProfileUrl": "https://www.google.com/maps/contrib/...",
      "authorId": "111279794912534073117",
      "reviewId": "ChdDSUhNMG9...",
      "photos": ["https://lh3.googleusercontent.com/grass-cs/..."]
    }
  ],
  "images": [
    "https://lh3.googleusercontent.com/grass-cs/...",
    "https://lh3.googleusercontent.com/grass-cs/..."
  ],
  "search_query": "barber shop",
  "search_location": "Washington, DC",
  "scraped_at": "2026-05-12T10:15:30+00:00"
}
```

### 🌍 Languages supported (for `language`)

English, Spanish, French, German, Italian, Portuguese, Russian, Ukrainian, Polish, Turkish, Dutch, Japanese, Korean, Chinese (Simplified).

Address parsing handles 60+ countries (ISO-2 country codes auto-extracted from address tail). Tested on US, EU (DE/FR/IT/ES/NL/PL), UK, CIS (RU/UA/BY/KZ), Turkey, Japan, Brazil, Mexico.

### 🔧 How it works under the hood

1. **Search** — direct GET to `google.com/search?tbm=map` with the same internal `pb` protobuf parameter the Maps frontend uses. Pagination 20 results per page until empty
2. **Deep search** — when a page returns ≥20 results, the actor reads the addresses, extracts neighborhood names, and re-queries each one in a worker pool. This bypasses Google's ~120-place soft cap per search term
3. **Address parser** — country detected from the last segment (60+ countries → ISO-2). Then state/zip/city from the remaining parts, with locale-specific heuristics (US "ST 12345" format, EU "12047 Berlin", CIS "Minsk Region 220040")
4. **Contact enrichment** — for each place with a website: GET the homepage, regex-extract emails + social links. If no email there, try `/contact`, `/contact-us`, `/about`, `/kontakt`, `/контакты`. Junk filter removes `noreply@`, `example.com`, generic placeholder domains
5. **Review extraction** — reverse-engineered `/maps/rpc/listugcposts` endpoint with pagination tokens. Yields all reviews per place (capped at your `maxReviewsPerPlace`). User-submitted photos from reviews are also harvested for the place's image gallery
6. **TLS fingerprinting** via `curl_cffi` with Chrome impersonation — Google sees real-Chrome handshakes, no bot blocks

### 📞 Support & feedback

Found a bug or need a feature? Email **africanec@gmail.com** — usually fix-and-redeploy within 24 hours.

To make debugging faster you can let the actor share run data with the developer: [Apify Security Settings](https://console.apify.com/settings/security) → **"Share run data with developers"** → check this actor (or all).

### 🏷️ Tags

Google Maps Scraper, Google Maps API alternative, Google Places Scraper, Google Maps Email Extractor, Google Maps Phone Number Extractor, Local Business Scraper, Lead Generation Tool, B2B Lead Scraper, Sales Prospecting Tool, GMB Scraper, Google My Business Scraper, Business Email Finder, Google Reviews Scraper, Google Maps Photos Scraper, Google Maps Reviews Extractor, Restaurant Scraper, Hotel Scraper, Dentist Scraper, Barbershop Lead Finder, Place ID Extractor, Plus Code Extractor, Postal Code Extractor, Geographic Business Database, City Business Scraper, Country-wide Business Scraper, Local SEO Tool, NAP Citation Scraper, Cold Email Lead List, Email Lead List Builder, Web Scraping without Browser, TLS Fingerprinting Scraper, curl\_cffi Scraper, Google Maps Data Mining, Local Market Research Tool, Competitor Analysis Tool, Real Estate Business Scraper, Franchise Scout Tool, Restaurant Database, Hotel Database

# Actor input Schema

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

What to search on Google Maps — one term per line. Examples: `barber shop`, `coffee`, `dental clinic`.

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

Where to search. Free-text like `Washington, DC` or `Berlin, Germany`. Leave empty if you fill the structured fields below.

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

Optional — combined with state/country into a single location string.

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

Optional — US state or other top-level region (e.g. `CA`).

## `country` (type: `string`):

Optional — full country name, e.g. `USA`, `Germany`.

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

Upper bound on results per `(search term, location)` pair. Google's UI caps at ~120; with `deepSearch` we go past that via geo-drilling.

## `deepSearch` (type: `boolean`):

When a result page saturates (≥20 items), recursively split the area into sub-areas to bypass the 120-place cap. Slower but much wider coverage.

## `categoryFilter` (type: `string`):

Optional. Keep only places whose category contains this string (case-insensitive), e.g. `Barber shop`.

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

Google interface language for the search.

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

Google geolocation hint — 2-letter ISO code. Affects ranking, results, and currency.

## `scrapeContactsFromWebsite` (type: `boolean`):

For every place that has a website, fetch homepage + contact pages and extract emails plus Facebook / Instagram / X / LinkedIn / YouTube / TikTok / Telegram links.

## `skipPlacesWithoutEmail` (type: `boolean`):

Only push places where an email was found (useful for lead lists).

## `includeReviews` (type: `boolean`):

Attach up to N user reviews per place (text, rating, author, date). Billed separately per review extracted — see pricing.

## `maxReviewsPerPlace` (type: `integer`):

Hard cap on reviews attached to one place. The actor paginates through Google's internal review API in batches of 20 — for a place with 1500 reviews, fetching all takes ~12 seconds.

## `includeImages` (type: `boolean`):

Attach photo URLs to each place. Sources: 3-5 thumbnails from the search response + every user-submitted photo from reviews (works only together with 💬 includeReviews). Higher review limit → more photos. Billed per image.

## `maxImagesPerPlace` (type: `integer`):

Hard cap on photo URLs attached to one place.

## `maxRetries` (type: `integer`):

Retries on transient network/proxy failures before giving up on a request.

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

Worker sessions running in parallel during deep search. Higher = faster but more proxy-intensive.

## Actor input object example

```json
{
  "searchStringsArray": [
    "barber shop"
  ],
  "locationQuery": "New York, NY",
  "maxCrawledPlacesPerSearch": 100,
  "deepSearch": false,
  "language": "en",
  "countryCode": "us",
  "scrapeContactsFromWebsite": true,
  "skipPlacesWithoutEmail": false,
  "includeReviews": false,
  "maxReviewsPerPlace": 20,
  "includeImages": false,
  "maxImagesPerPlace": 20,
  "maxRetries": 5,
  "concurrency": 3
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `contacts` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchStringsArray": [
        "barber shop"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("pro100chok/google-maps-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 = { "searchStringsArray": ["barber shop"] }

# Run the Actor and wait for it to finish
run = client.actor("pro100chok/google-maps-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 '{
  "searchStringsArray": [
    "barber shop"
  ]
}' |
apify call pro100chok/google-maps-scraper --silent --output-dataset

```

## MCP server setup

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

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "🗺️ Google Maps Scraper — Emails, Reviews & Photos",
        "description": "Extract Google Maps places with phones, websites, hours, ratings — and verified business emails automatically pulled from each place's website. Full review history, photo URLs, 7 social networks. Pay only per result, from $0.003/place. No browser, no API key.",
        "version": "1.0",
        "x-build-id": "McWgldX58sF5Opmno"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/pro100chok~google-maps-scraper/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-pro100chok-google-maps-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/pro100chok~google-maps-scraper/runs": {
            "post": {
                "operationId": "runs-sync-pro100chok-google-maps-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/pro100chok~google-maps-scraper/run-sync": {
            "post": {
                "operationId": "run-sync-pro100chok-google-maps-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": [
                    "searchStringsArray"
                ],
                "properties": {
                    "searchStringsArray": {
                        "title": "🔍 Search terms",
                        "minItems": 1,
                        "type": "array",
                        "description": "What to search on Google Maps — one term per line. Examples: `barber shop`, `coffee`, `dental clinic`.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "locationQuery": {
                        "title": "📍 Location (free text)",
                        "type": "string",
                        "description": "Where to search. Free-text like `Washington, DC` or `Berlin, Germany`. Leave empty if you fill the structured fields below."
                    },
                    "city": {
                        "title": "🏙️ City",
                        "type": "string",
                        "description": "Optional — combined with state/country into a single location string."
                    },
                    "state": {
                        "title": "🗺️ State / region",
                        "type": "string",
                        "description": "Optional — US state or other top-level region (e.g. `CA`)."
                    },
                    "country": {
                        "title": "🌍 Country",
                        "type": "string",
                        "description": "Optional — full country name, e.g. `USA`, `Germany`."
                    },
                    "maxCrawledPlacesPerSearch": {
                        "title": "🎯 Max places per search",
                        "minimum": 1,
                        "maximum": 5000,
                        "type": "integer",
                        "description": "Upper bound on results per `(search term, location)` pair. Google's UI caps at ~120; with `deepSearch` we go past that via geo-drilling.",
                        "default": 100
                    },
                    "deepSearch": {
                        "title": "🌐 Deep search (geo-drilling)",
                        "type": "boolean",
                        "description": "When a result page saturates (≥20 items), recursively split the area into sub-areas to bypass the 120-place cap. Slower but much wider coverage.",
                        "default": false
                    },
                    "categoryFilter": {
                        "title": "🏷️ Category filter",
                        "type": "string",
                        "description": "Optional. Keep only places whose category contains this string (case-insensitive), e.g. `Barber shop`."
                    },
                    "language": {
                        "title": "🗣️ Language (hl)",
                        "enum": [
                            "en",
                            "es",
                            "fr",
                            "de",
                            "it",
                            "pt",
                            "ru",
                            "uk",
                            "pl",
                            "tr",
                            "nl",
                            "ja",
                            "ko",
                            "zh-CN"
                        ],
                        "type": "string",
                        "description": "Google interface language for the search.",
                        "default": "en"
                    },
                    "countryCode": {
                        "title": "🚩 Country code (gl)",
                        "pattern": "^[a-zA-Z]{2}$",
                        "type": "string",
                        "description": "Google geolocation hint — 2-letter ISO code. Affects ranking, results, and currency.",
                        "default": "us"
                    },
                    "scrapeContactsFromWebsite": {
                        "title": "📧 Scrape emails + socials from websites",
                        "type": "boolean",
                        "description": "For every place that has a website, fetch homepage + contact pages and extract emails plus Facebook / Instagram / X / LinkedIn / YouTube / TikTok / Telegram links.",
                        "default": true
                    },
                    "skipPlacesWithoutEmail": {
                        "title": "📭 Skip places with no email",
                        "type": "boolean",
                        "description": "Only push places where an email was found (useful for lead lists).",
                        "default": false
                    },
                    "includeReviews": {
                        "title": "💬 Include reviews (add-on)",
                        "type": "boolean",
                        "description": "Attach up to N user reviews per place (text, rating, author, date). Billed separately per review extracted — see pricing.",
                        "default": false
                    },
                    "maxReviewsPerPlace": {
                        "title": "🔢 Max reviews per place",
                        "minimum": 1,
                        "maximum": 5000,
                        "type": "integer",
                        "description": "Hard cap on reviews attached to one place. The actor paginates through Google's internal review API in batches of 20 — for a place with 1500 reviews, fetching all takes ~12 seconds.",
                        "default": 20
                    },
                    "includeImages": {
                        "title": "🖼️ Include image URLs (add-on)",
                        "type": "boolean",
                        "description": "Attach photo URLs to each place. Sources: 3-5 thumbnails from the search response + every user-submitted photo from reviews (works only together with 💬 includeReviews). Higher review limit → more photos. Billed per image.",
                        "default": false
                    },
                    "maxImagesPerPlace": {
                        "title": "🔢 Max images per place",
                        "minimum": 1,
                        "maximum": 200,
                        "type": "integer",
                        "description": "Hard cap on photo URLs attached to one place.",
                        "default": 20
                    },
                    "maxRetries": {
                        "title": "🔁 Max retries per request",
                        "minimum": 1,
                        "maximum": 15,
                        "type": "integer",
                        "description": "Retries on transient network/proxy failures before giving up on a request.",
                        "default": 5
                    },
                    "concurrency": {
                        "title": "⚡ Concurrent sessions",
                        "minimum": 1,
                        "maximum": 20,
                        "type": "integer",
                        "description": "Worker sessions running in parallel during deep search. Higher = faster but more proxy-intensive.",
                        "default": 3
                    }
                }
            },
            "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
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
