# Google Maps Scraper – Places, Phone, Website, Email & Hours (`moyadata/google-maps-places-scraper`) Actor

Extract business data from Google Maps by search query or coordinates: name, address, phone, website, rating, hours and more.

- **URL**: https://apify.com/moyadata/google-maps-places-scraper.md
- **Developed by:** [SERP King](https://apify.com/moyadata) (community)
- **Categories:** Lead generation, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## 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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Google Maps Scraper – Places, Phone, Website, Email & Hours

Extract structured business data from Google Maps by search query or by coordinates: name, address, phone, website, rating, review count, opening hours, price level, and more. This actor calls a licensed SERP data provider directly — **no proxies, no headless browser, no IP bans, no CAPTCHAs.** Results typically come back in a few seconds per query, and the same low, transparent price applies whether you scrape 10 places or 10,000.

### What data you get

Each result item includes:

- `placeId`, `cid` — Google's stable identifiers for the place
- `name`, `category`, `additionalCategories` — business name and category tags
- `address`, `addressParts` (street, city, zip, region, country code)
- `phone`, `website`, `domain`
- `rating`, `reviewsCount`, `ratingDistribution` (1–5 star breakdown)
- `priceLevel`
- `latitude`, `longitude`
- `openingHours` — per weekday, list of `{open, close}` time ranges
- `currentStatus` (e.g. open now / closed / temporarily closed)
- `isClaimed` — whether the business owner has claimed the listing
- `mainImageUrl`, `totalPhotos`
- `googleMapsUrl` — direct link to the place on Google Maps
- `bookOnlineUrl`
- `rank` — position in the search results for its query
- `query`, `scrapedAt` (ISO 8601 UTC)

### Why this scraper

- **Fast** — results come from a real-time SERP data pipeline, typically seconds per query, not minutes of browser automation.
- **Cheap** — pay only for places actually scraped, a flat **$0.001 per place** regardless of your Apify plan (see Pricing).
- **Up to 700 results per query** — go well past Google's default page of ~20 results.
- **Coordinate-based search** — target a specific map viewport (`lat,lng,zoom`) instead of relying on a text query alone, useful for dense areas or `search_this_area`-style workflows.
- **Optional reviews, filters, and bulk category x city expansion** — see the Reviews, Filters, and Bulk categories x cities sections below.

### How it compares

Public Apify Store place prices on the Free plan tier, checked against the Store API on 8 Sep 2026:

| Actor | Place price (Free plan) | Notes |
|---|---|---|
| compass/crawler-google-places | $0.004/place | 594,600 total users, 1,814 reviews (4.70★). |
| vortex\_data/google-maps | $0.001/place | 712 users in the last 30 days, 18 reviews (4.84★). |
| This actor (moyadata/google-maps-places-scraper) | $0.001/place | No proxies, no browser, Standby API for synchronous HTTP calls, filters applied before billing (see Filters). |

compass/crawler-google-places also offers GeoJSON polygon-area search and full photo-gallery extraction that this actor does not.

### Pricing

This actor uses Apify's **pay-per-event** pricing, flat across all plans. You are charged for:

| Event | What it means | Price |
|---|---|---|
| `apify-actor-start` | Apify's platform-billed "run started" event, charged once per run automatically -- no code call needed, so a run with zero results costs almost nothing | $0.00005 |
| `apify-default-dataset-item` ("Place scraped" in Console) | Charged once per place written to the output dataset | $0.001 |
| `review-scraped` | Charged once per review fetched, only when `includeReviews` is on (see Reviews) | $0.0005 |
| `contact-enriched` | Charged once per place, only when `enrichContacts` found at least one email or social link (see Contact enrichment) | $0.0015 |

**Worked example:** scraping 1,000 places costs `1,000 × $0.001 + $0.00005 ≈ $1.00`. The same 1,000 places with `includeReviews` on and 10 reviews fetched per place cost `1,000 × $0.001 + 10,000 × $0.0005 + $0.00005 ≈ $1.00 + $5.00 = $6.00`.

Apify's free plan includes monthly platform credit that, on its own, covers roughly the first **5,000 places** scraped with this actor before you'd need to add a payment method.

### Input

> Set `country` to the country your queries refer to. Tested example: `dentista en Providencia, Santiago` with `country: "US"` returns places from the wrong country; with `country: "CL"` it returns 100 dentists in Providencia.

Minimal example:

```json
{
  "queries": ["coffee shops in Austin, TX"],
  "country": "US",
  "language": "en",
  "maxResultsPerQuery": 20
}
```

| Field | Type | Default | Description |
|---|---|---|---|
| `queries` | array of strings | `["coffee shops in Austin, TX"]` | One or more search queries, e.g. `"dentist in Austin, TX"`. Max 700 characters each. Optional if both `categories` and `cities` are set (see Bulk categories x cities). At least one query must result, from `queries` and/or expansion. |
| `country` | string (ISO-2) | `"US"` | Country to search in. Ignored for query matching when `coordinates` is set, but always used to resolve the location for review requests (see Reviews). |
| `language` | string | `"en"` | Language code for the results (e.g. `"en"`, `"es"`, `"de"`). Also selects the categories x cities query template. |
| `coordinates` | string | — | Optional `"lat,lng,zoom"` (e.g. `"30.2672,-97.7431,14"`). When set, search is anchored to this map viewport instead of `country`. |
| `maxResultsPerQuery` | integer | `20` (max 700) | Maximum number of places returned per query. |
| `deduplicate` | boolean | `true` | Remove duplicate places (by `placeId`) seen across multiple queries in the same run. |
| `categories` | array of strings | — | Bulk mode: business categories to cross with `cities`. See Bulk categories x cities. |
| `cities` | array of strings | — | Bulk mode: cities/areas to cross with `categories`. See Bulk categories x cities. |
| `includeReviews` | boolean | `false` | Fetch reviews for each place. See Reviews. |
| `maxReviewsPerPlace` | integer | `10` (10-100) | Reviews to fetch per place when `includeReviews` is on. See Reviews. |
| `reviewsSort` | string | `"newest"` | One of `relevant`, `newest`, `highest_rating`, `lowest_rating`. See Reviews. |
| `minRating` | number | — | Keep only places with a rating >= this value (0-5). See Filters. |
| `minReviews` | integer | — | Keep only places with at least this many reviews. See Filters. |
| `hasWebsite` | string | `any` (default), `yes`, `no` | Keep only places with a website (`yes`), only those without (`no`), or both (`any`)|
| `hasPhone` | string | `any` (default), `yes`, `no` | Keep only places with a phone number (`yes`), only those without (`no`), or both (`any`)|
| `openNow` | string | `any` (default), `yes`, `no` | Keep only places open at scrape time (`yes`), only closed ones (`no`), or both (`any`)|
| `categoriesInclude` | array of strings | — | Keep only places matching one of these categories (case-insensitive, exact match). See Filters. |
| `enrichContacts` | boolean | `false` | Fetch emails and social links from each place's website. See Contact enrichment. |

### Output

One real dataset item, from a live run against `plumbers in Austin, TX` on 8 Sep 2026 (see the measured-yield numbers in Contact enrichment below for the full run this item came from):

```json
{
  "placeId": "ChIJpxUGhC_LRIYR2g941oH50ow",
  "cid": "10147447246463438810",
  "name": "Beyond Wow Plumbing & Drains",
  "category": "Plumber",
  "additionalCategories": [
    "Drainage service",
    "Gas installation service",
    "Hot water system supplier",
    "Water filter supplier"
  ],
  "address": "3432 Greystone Dr, Austin, TX 78731",
  "addressParts": {
    "street": "3432 Greystone Dr",
    "city": "Austin",
    "zip": "78731",
    "region": "Texas",
    "countryCode": "US"
  },
  "phone": "+1512-601-6173",
  "website": "https://beyondwow.com/?utm_campaign=gmb&utm_medium=gbp-austin&utm_source=google",
  "domain": "beyondwow.com",
  "rating": 4.9,
  "reviewsCount": 2579,
  "ratingDistribution": { "1": 58, "2": 17, "3": 25, "4": 50, "5": 2429 },
  "priceLevel": null,
  "latitude": 30.3578911,
  "longitude": -97.7482485,
  "openingHours": {
    "monday": [{ "open": "07:00", "close": "18:00" }],
    "tuesday": [{ "open": "07:00", "close": "18:00" }],
    "wednesday": [{ "open": "07:00", "close": "18:00" }],
    "thursday": [{ "open": "07:00", "close": "18:00" }],
    "friday": [{ "open": "07:00", "close": "18:00" }]
  },
  "currentStatus": "open",
  "isClaimed": true,
  "mainImageUrl": "https://lh3.googleusercontent.com/gps-cs-s/AHRPTWmiDLztnnpPJfoaMPrurchultIC5ltxZTId2hco9G7k_JQt-bniJ6Ujl8eMC4IUEmE9JkRzjy28-LKlqRjzNYJ5-lfpiDCCuhaa7oNEBwjLc6YrBqSgiMoPu83s4KuzBnYkmd8=w408-h408-k-no",
  "totalPhotos": 247,
  "googleMapsUrl": "https://www.google.com/maps?cid=10147447246463438810",
  "bookOnlineUrl": "https://form.recreateai.com/?rai_pak=8b0f7a2c-e37c-4714-bdff-999dc19da556&branch_id=a5321498-4798-41c9-b352-0814630c5cfe&rwg_token=AE37R_h4RLz_FFoarFAgEf7DCB9ApToHo2LMDEU4WbzMvDUtjVTiP69iCQVdIZjuf5BRWfYElxHuzQJIspr7CGNboOSgSOia0Q%3D%3D",
  "rank": 1,
  "query": "plumbers in Austin, TX",
  "scrapedAt": "2026-09-08T17:08:09Z",
  "reviews": null,
  "contacts": {
    "emails": ["info@beyondwow.com"],
    "socials": {
      "facebook": "https://www.facebook.com/beyondwowplumbing",
      "instagram": "https://www.instagram.com/beyondwowplumbing",
      "linkedin": "https://www.linkedin.com/company/beyondwowplumbing",
      "twitter": null,
      "tiktok": "https://www.tiktok.com/@beyondwowplumbing",
      "youtube": "https://www.youtube.com/@beyondwowplumbing",
      "whatsapp": null
    },
  },
  "contactsStatus": "enriched"
}
```

| Field | Type | Description |
|---|---|---|
| `placeId` | string | Google's place identifier |
| `cid` | string | Google's numeric customer/place ID, used to build `googleMapsUrl` |
| `name` | string | Business name |
| `category` | string | Primary category |
| `additionalCategories` | array of strings | Secondary category tags |
| `address` | string | Full formatted address |
| `addressParts` | object | `street`, `city`, `zip`, `region`, `countryCode` |
| `phone` | string | null | Phone number as shown on the listing |
| `website` | string | null | Business website URL |
| `domain` | string | null | Website domain, extracted from `website` |
| `rating` | float | null | Average rating (0–5), null if the place has no ratings |
| `reviewsCount` | integer | null | Total number of reviews |
| `ratingDistribution` | object | null | Count of reviews per star, keys `"1"`–`"5"` |
| `priceLevel` | string | null | One of `inexpensive`, `moderate`, `expensive`, `very_expensive`, or null when Google shows no price level |
| `latitude` | float | Latitude |
| `longitude` | float | Longitude |
| `openingHours` | object | null | Per weekday list of `{open, close}` ranges (`"HH:MM"`) |
| `currentStatus` | string | null | e.g. `open`, `closed`, `temporarily_closed` |
| `isClaimed` | boolean | Whether the owner has claimed the listing |
| `mainImageUrl` | string | null | URL of the listing's main photo |
| `totalPhotos` | integer | null | Total photo count on the listing |
| `googleMapsUrl` | string | Direct Google Maps link for the place |
| `bookOnlineUrl` | string | null | Online booking link, if available |
| `rank` | integer | Position of this place in the results for its query |
| `query` | string | The input query that returned this place |
| `scrapedAt` | string | ISO 8601 UTC timestamp of when the place was scraped |
| `reviews` | array of objects | null | `null` unless `includeReviews` is `true`, in which case it's always an array (possibly empty). See Reviews. |

### Reviews

Set `includeReviews: true` to fetch reviews for each place, in addition to the place data above.

| Field | Type | Default | Description |
|---|---|---|---|
| `includeReviews` | boolean | `false` | Turns reviews on. |
| `maxReviewsPerPlace` | integer | `10` | Reviews to fetch per place (10-100; multiples of 10 recommended — Google's reviews API pages in tens). |
| `reviewsSort` | string | `"newest"` | One of `relevant`, `newest`, `highest_rating`, `lowest_rating`. |

Each entry in the `reviews` array looks like:

```json
{
  "reviewId": "ChdDSUhNMG9nS0VJQ0FnSURvd2VYSkR3EAE",
  "author": "Jamie Rivera",
  "authorUrl": "https://www.google.com/maps/contrib/000000000000000000001",
  "isLocalGuide": true,
  "rating": 5,
  "text": "Great coffee and friendly staff, will come back.",
  "originalText": "Great coffee and friendly staff, will come back.",
  "publishedAt": "2026-08-24T09:15:00Z",
  "ownerAnswer": "Thanks so much for the kind words, Jamie!",
  "ownerAnswerAt": "2026-08-31T10:00:00Z",
  "imagesCount": 1,
  "likes": null,
  "reviewUrl": "https://www.google.com/maps/reviews/data=example1"
}
```

Notes:

- `likes` is always `null` — Google's reviews data doesn't expose a like/thumbs-up count.
- A place with no `cid`, or whose reviews couldn't be fetched (upstream error or timeout), gets `reviews: []`, not `null` — the place itself is still delivered and charged normally.
- Reviews are billed with the `review-scraped` event, once per review actually fetched, charged right after the place that contains them is pushed (see Pricing). If your max charge limit is reached while fetching reviews, this actor stops requesting reviews but keeps delivering places (charged only `apify-default-dataset-item`) until its own limit is reached.
- Because each batch of review requests can take up to a minute upstream, a run with `includeReviews` on is capped at 2,000 places total (`maxResultsPerQuery x queries`) — the run fails validation up front if your input would exceed that.

### Filters

Six optional fields filter results after scraping and before truncating to `maxResultsPerQuery` or charging `apify-default-dataset-item` — you only pay for places that pass every filter you set.

| Field | Type | Behavior |
|---|---|---|
| `minRating` | number (0-5) | Keep places with `rating >= minRating`. Places with no rating are excluded. |
| `minReviews` | integer | Keep places with `reviewsCount >= minReviews`. Places with no review count are excluded. |
| `hasWebsite` | string | `any` (default), `yes`, `no` | Keep only places with a website (`yes`), only those without (`no`), or both (`any`)
| `hasPhone` | string | `any` (default), `yes`, `no` | Keep only places with a phone number (`yes`), only those without (`no`), or both (`any`)
| `openNow` | string | `any` (default), `yes`, `no` | Keep only places open at scrape time (`yes`), only closed ones (`no`), or both (`any`)
| `categoriesInclude` | array of strings | Keep places whose `category` or any `additionalCategories` entry case-insensitively matches one of these (exact match, not substring). |

STATS (`placesFiltered` in the run's key-value store) reports how many scraped places were dropped by filters.

### Bulk categories x cities

Instead of (or in addition to) writing out `queries` by hand, fill both `categories` and `cities` to auto-generate every combination:

```json
{
  "categories": ["dentist", "coffee shop"],
  "cities": ["Austin, TX", "Denver, CO"],
  "language": "en"
}
```

generates, category-major: `"dentist in Austin, TX"`, `"dentist in Denver, CO"`, `"coffee shop in Austin, TX"`, `"coffee shop in Denver, CO"`. The template depends on `language`: `"{category} in {city}"` for `en`, `"{category} en {city}"` for `es`, and `"{category} {city}"` for anything else. Generated queries are concatenated with any explicit `queries`, then deduplicated together with them. A run is capped at 500 total queries — the input fails validation up front if `categories x cities` (plus any explicit `queries`) would exceed that.

### Limits

- Google Maps listings themselves don't expose email addresses (review authors are identified by display name only). With `enrichContacts` on, the actor reads the business's own website to find emails and social profiles — see Contact enrichment.
- Paid/ad results (`maps_paid_item`) are skipped; only organic listings are returned.
- Maximum 700 results per query — this is the data provider's ceiling; most queries have far fewer organic listings anyway.
- Results reflect Google Maps at the moment the query runs; Google's own index and ranking can change between runs.
- `includeReviews` caps a run at 2,000 places total (see Reviews).

### FAQ

**Is this legal?**
The actor retrieves publicly available business listing data through a licensed SERP data provider, the same category of data source used by SEO and local-search tools. You are responsible for how you use the extracted data under your local regulations (e.g. marketing/consent rules for phone numbers).

**How is this different from other Google Maps scrapers on Apify?**
Most Google Maps actors drive a real or headless browser against Google Maps, which means proxy costs, occasional IP blocks/CAPTCHAs, and slower runs. This actor calls a licensed SERP data API directly: no proxies, no browser, no IP bans, and — because there's no proxy or browser compute overhead — a lower price per place.

**How do I search by coordinates instead of a text query?**
Set the `coordinates` input field to `"lat,lng,zoom"` (e.g. `"30.2672,-97.7431,14"`). When present, it overrides `country` and anchors the search to that map viewport, similar to using "Search this area" on Google Maps itself.

**Why did I get fewer results than `maxResultsPerQuery`?**
Google Maps itself caps how many organic results exist for a given query and area — if a query only has 12 relevant places, that's what you'll get regardless of the requested maximum. Narrowing the query (e.g. adding a neighborhood) or using `coordinates` with "search this area"-style bounding often surfaces more results than a broad city-wide text query.

**Can I run this via API, on a schedule, or trigger it from a webhook?**
Yes. Any Apify actor can be started, monitored and read back through the [Apify API](https://docs.apify.com/api/v2), run on a recurring [schedule](https://docs.apify.com/platform/schedules) from the Apify Console, and wired to webhooks on run events (started, succeeded, failed) from the same Console.

### Contact enrichment

> Website phone numbers are deliberately not extracted. Google Maps already returns a verified `phone` for the place, and phone-shaped strings scraped from a page's HTML and JavaScript are unreliable enough that they were removed rather than shipped with caveats.

Set `enrichContacts: true` (default `false`) to have the actor visit each
result's `website` and pull out public contact details: email addresses,
social media links, and phone numbers found on the page.

For every place with a `website`, the actor fetches the home page and up to
two candidate pages likely to list contact details (`/contact`, `/contacto`,
`/about`, or a link on the home page whose text contains "contact"). Each
result item gets two new fields:

- `contacts` — `{ "emails": [...], "socials": { "facebook", "instagram", "linkedin", "twitter", "tiktok", "youtube", "whatsapp" } }`, or `null` when nothing was fetched.
- `contactsStatus` — one of:

| Status | Meaning |
|---|---|
| `enriched` | The site was fetched and `contacts` was extracted (may still be empty if the site has none of the above). |
| `no_website` | The place had no `website` field, so no request was made. |
| `blocked` | The site returned an anti-bot response (HTTP 403/503) or its `robots.txt` disallows the pages this actor requests. |
| `timeout` | The site did not respond within 10 seconds (one retry is attempted, then it's reported as a timeout). |
| `error` | Any other network or HTTP error (e.g. a 500 response, too many redirects). |
| `unsafe_url` | The `website` value failed a safety check (not `http(s)`, a bare IP address, or an address that resolves to a private/internal network) and was never requested. |

**Measured yield.** In one test run (`plumbers in Austin, TX`, 100 places,
8 Sep 2026): 98 places had a website and 2 had none. Of those 98, enrichment
found at least one email for 57 places and at least one social profile for
68; 4 sites blocked the fetch and 5 errored. 78 places were charged the
`contact-enriched` event (at least one email or social found). The whole
run — 100 places plus enrichment — took 81 seconds. Results vary by
industry and country; this is one data point, not a guarantee.

**Pricing.** The `contact-enriched` event is charged once per place, **only
when at least one email address or social media link was found** — never
merely for having attempted a fetch. Proposed price: **$0.0015 per
enriched place**, same across all plans.

**Limits, by design:**

- No JavaScript is executed — sites that render their contact details
  client-side (JS-only) won't be enriched even if a human visitor could see
  them.
- Sites behind anti-bot protection return `contactsStatus: "blocked"`
  rather than being retried aggressively or routed through proxies. This
  actor never uses proxies and never retries more than once.
- `robots.txt` is respected: if a site disallows the pages this actor would
  request, they are skipped.
- Each page fetch is capped at 1 MB and 10 seconds; requests use an
  identified User-Agent (`GoogleMapsPlacesScraper/0.2`).

### API mode (Standby)

Standby is enabled on this actor as of September 2026; its base URL is
`https://moyadata--google-maps-places-scraper.apify.actor`.

This actor can also run as a synchronous HTTP API instead of a queued run,
using Apify's [Standby mode](https://docs.apify.com/platform/actors/running/standby).
Standby must be turned on for this actor in Apify Console (Settings ->
Standby) by the developer publishing it — it is off by default and cannot
be enabled from the input.

Once Standby is enabled, the actor's Standby URL (shown on the actor's
Console page) accepts:

- `GET /search?query=...&country=...&language=...&coordinates=...&maxResults=...`
- `POST /search` with a JSON body using the same fields as the actor input
  (only places are returned in Standby mode — no reviews, no contact
  enrichment).

Both return `200` with `{ "places": [...], "stats": {...} }` on success, a
`400` with `{ "error": "..." }` on invalid input, and a `503` with
`{ "error": "..." }` if the upstream data provider is unavailable (balance
exhausted, circuit breaker open).

Example:

```bash
curl "https://moyadata--google-maps-places-scraper.apify.actor/search?query=coffee+shops+in+Austin,+TX&country=US&maxResults=20" \
  -H "Authorization: Bearer $APIFY_TOKEN"
```

```bash
curl -X POST "https://moyadata--google-maps-places-scraper.apify.actor/search" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"queries": ["coffee shops in Austin, TX"], "country": "US", "maxResultsPerQuery": 20}'
```

**Pricing.** Standby requests are charged the same `apify-default-dataset-item`
event ("Place scraped" in Console) as a normal run, per place returned in the
response — there is no separate "per request" charge and no dataset is
written (there's nothing to push, so the event is charged directly).
`apify-actor-start` is billed once by the platform when the Standby run
starts, not per individual request.

# Actor input Schema

## `queries` (type: `array`):

One or more Google Maps search queries, e.g. "dentist in Austin, TX". Max 700 characters each. Optional if both `categories` and `cities` below are set.

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

ISO-2 country code the queries refer to (e.g. US, GB, CL, MX). It must match the country in your queries: a query like 'dentista en Providencia, Santiago' with country US returns places from the wrong country, with country CL it returns 100 dentists in Providencia. Ignored for location matching when coordinates is set, but still used to resolve the location for review requests.

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

Language code for the results, e.g. "en", "es". Also picks the categories x cities query template (see below).

## `coordinates` (type: `string`):

Anchor the search to a map viewport instead of a country: "lat,lng,zoom", e.g. "30.2672,-97.7431,14".

## `maxResultsPerQuery` (type: `integer`):

Maximum number of places to return per query (1-700).

## `deduplicate` (type: `boolean`):

Remove duplicate places (by placeId) seen across multiple queries in this run.

## `categories` (type: `array`):

Business categories, e.g. "dentist", "coffee shop".

## `cities` (type: `array`):

Cities/areas to combine with `categories`, e.g. "Austin, TX".

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

Fetch reviews for each place returned.

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

How many reviews to fetch per place when Include reviews is on (10-100; multiples of 10 recommended).

## `reviewsSort` (type: `string`):

Sort order requested from the reviews API.

## `minRating` (type: `number`):

Keep only places with a rating >= this value (0-5). Places with no rating are excluded when this is set.

## `minReviews` (type: `integer`):

Keep only places with at least this many reviews. Places with no review count are excluded when this is set.

## `hasWebsite` (type: `string`):

Keep only places that have a website, only places without one, or both.

## `hasPhone` (type: `string`):

Keep only places that have a phone number, only places without one, or both.

## `openNow` (type: `string`):

Keep only places open at scrape time, only closed ones, or both.

## `categoriesInclude` (type: `array`):

Keep only places whose category or additional categories match one of these (case-insensitive, exact match).

## `enrichContacts` (type: `boolean`):

Fetch emails and social links from each place's website.

## Actor input object example

```json
{
  "queries": [
    "coffee shops in Austin, TX"
  ],
  "country": "US",
  "language": "en",
  "maxResultsPerQuery": 20,
  "deduplicate": true,
  "includeReviews": false,
  "maxReviewsPerPlace": 10,
  "reviewsSort": "newest",
  "hasWebsite": "any",
  "hasPhone": "any",
  "openNow": "any",
  "enrichContacts": false
}
```

# Actor output Schema

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

No description

## `stats` (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 = {
    "queries": [
        "coffee shops in Austin, TX"
    ],
    "country": "US",
    "maxResultsPerQuery": 20,
    "maxReviewsPerPlace": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("moyadata/google-maps-places-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 = {
    "queries": ["coffee shops in Austin, TX"],
    "country": "US",
    "maxResultsPerQuery": 20,
    "maxReviewsPerPlace": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("moyadata/google-maps-places-scraper").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "queries": [
    "coffee shops in Austin, TX"
  ],
  "country": "US",
  "maxResultsPerQuery": 20,
  "maxReviewsPerPlace": 10
}' |
apify call moyadata/google-maps-places-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,moyadata/google-maps-places-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/qxMW9UMXFom7a8Gjg/builds/OHFVkJOlwIOPrlf0O/openapi.json
