# Google Map Actor (`bluetail/google-map-actor`) Actor

- **URL**: https://apify.com/bluetail/google-map-actor.md
- **Developed by:** [Noushad Ali](https://apify.com/bluetail) (community)
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $1.50 / 1,000 scraped results

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

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

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

📍 What is Google Maps Business Scraper?
Google Maps Business Scraper lets you extract business data from Google Maps by keyword and location, helping you generate leads, confirm business listings, build local databases, and enrich address or review data.

- **Generate qualified leads**: extract business names, full address breakdowns, phone numbers, and websites to build prospect lists for your sales team
- **Confirm and enrich listings**: pull rating, review count, price level, business status, opening hours, and amenities for places you already know about
- **Analyze reputation**: pull up to 5 of the most relevant reviews per place (author, rating, text, and timestamp) without opening Google Maps by hand
- **Cover a whole area, not just a partial view**: the scraper automatically splits your search area into smaller tiles and de-duplicates results, so you get full coverage instead of a capped, partial result set
- **Control cost precisely**: choose how much detail to fetch per place — from a basic address confirmation up to full contact, reviews, and amenity info — so you only pay for what you need

### ⚙️ How it works

1. **Geocoding** — turns `locationQuery` into a bounding box (skipped if you supply `customBounds` directly).
2. **Grid tiling** — splits that box into overlapping circular tiles (`gridSizeKm` wide) so no single search has to cover more than 60-result cap.
3. **Text Search** (`place/textsearch/json`) — runs `searchTerm` against every tile, following `next_page_token` pagination, and de-duplicates everything by `place_id`.
4. **Place Details** (`place/details/json`) — for each unique place, optionally fetches richer data at the tier you choose (see `detailsTier` below).
5. Every result is flattened into one row and pushed to the dataset.

### 📦 What data does Google Maps Business Scraper extract?

Every row is fully flat (no nested objects/arrays) so it looks the same whether you export as **JSON or CSV**. Fields marked "Pro" or "Enterprise" only populate at that `detailsTier` or higher — see **Input** below.

| Group | Fields |
|---|---|
| **Identity** | `placeId`, `title`, `businessStatus` |
| **Address** | `address`, `adrAddress`, `vicinity`, `streetNumber`, `route`, `premise`, `sublocality`, `neighborhood`, `city`, `county`, `state`, `stateShort`, `country`, `countryShort`, `zipCode` |
| **Location** | `lat`, `lng`, `viewportNortheastLat/Lng`, `viewportSouthwestLat/Lng`, `plusCodeCompound`, `plusCodeGlobal`, `utcOffsetMinutes` |
| **Category** | `categoryPrimary`, `categories` |
| **Google signals** | `rating`*, `reviewsCount`*, `priceLevel`*, `googleMapsUrl`, `icon`, `iconBackgroundColor`, `wheelchairAccessibleEntrance`, `editorialSummary`* |
| **Contact** (Pro+) | `phone`, `internationalPhone`, `website` |
| **Hours** (Pro+) | `opening_openNow`, `opening_weekdayText`, `currentOpening_openNow`, `currentOpening_weekdayText` |
| **Amenities** (Enterprise) | `curbsidePickup`, `delivery`, `dineIn`, `reservable`, `takeout`, `servesBeer`, `servesBreakfast`, `servesBrunch`, `servesDinner`, `servesLunch`, `servesVegetarianFood`, `servesWine` |
| **Reviews** (Enterprise) | `review1_author` … `review5_author`, `review1_rating` … `review5_rating`, `review1_text` … `review5_text`, `review1_relativeTime` … `review5_relativeTime`, `review1_timestamp` … `review5_timestamp`, `reviewsReturnedCount` |
| **Search context** | `geoChunk` — center of the search-grid tile the place was found in |
| **Full fidelity fallback** | `rawPlaceDetailsJson` — the complete, unflattened Place Details response as a JSON string, in case you need a field that isn't broken out above |

\* `rating`/`reviewsCount`/`editorialSummary` need at least the `enterprise` tier — `priceLevel`, amenities, and reviews are Enterprise-only.

### ⬇️ Input

The input is a search term plus a location (or a custom bounding box).

| Field | Required | Default | Description |
|---|---|---|---|
| `searchTerm` | yes | — | e.g. `"restaurant"`, `"plumber"`, `"coffee shop"` |
| `locationQuery` | yes\* | — | Free text location, e.g. `"Austin, TX, USA"` |
| `customBounds` | no | — | `{"north":..,"south":..,"east":..,"west":..}` — overrides `locationQuery` |
| `gridSizeKm` | no | `3` | The search area is split into tiles of roughly this size for full coverage. Smaller = more complete coverage in dense areas, but a longer run |
| `maxPlaces` | no | `200` | Stop once this many unique places are found (`0` = no limit) |
| `fetchPlaceDetails` | no | `true` | Fetches extra detail per place — takes longer, adds far more data |

\* `locationQuery` is required unless `customBounds` is supplied.

#### Search term guidance

A focused, single-purpose term (`"coffee shop"`) generally performs better than a long list of near-duplicate terms (`"coffee shop"`, `"cafe"`, `"coffee"`) — the latter just slows the run down without adding real coverage.

#### Grid size guidance

`gridSizeKm` is a coverage/speed knob: smaller tiles catch more places in dense urban areas but increase run time. Start around 3–5 km and shrink it only if you're missing results in a dense area.

### ⬆️ Output

Results are pushed to the Actor's default dataset as flat rows, one per unique place. Every field is a scalar or a delimited string, so **JSON and CSV exports carry the same information** — arrays like opening hours or reviews are joined into readable strings (`|` for hours, one column set per review) instead of nested structures that CSV can't represent.

Example row (`enterprise` detail level, trimmed to the highlights — the real row also includes every field from the table above):

```json
{
    "placeId": "ChIJd2Sm_soDDTkR62jsR0f47Y0",
    "title": "Dr Arun Gupta | Interventional radiologist in Delhi",
    "businessStatus": "OPERATIONAL",
    "address": "Department of Interventional Radiology, SIR GANGA RAM HOSPITAL, G20 - A, near ultrasound dept, Old Rajinder Nagar, Rajinder Nagar, New Delhi, Delhi, 110060, India",
    "premise": "Department of Interventional Radiology",
    "city": "New Delhi",
    "state": "Delhi",
    "stateShort": "DL",
    "country": "India",
    "countryShort": "IN",
    "zipCode": "110060",
    "lat": 28.6384582,
    "lng": 77.1895559,
    "categoryPrimary": "doctor",
    "categories": "doctor, establishment, health, point_of_interest",
    "rating": 4.8,
    "reviewsCount": 117,
    "phone": "099584 74870",
    "internationalPhone": "+91 99584 74870",
    "website": "https://www.interventionalradiologyindia.com/",
    "googleMapsUrl": "https://maps.google.com/?cid=10227103313861306603",
    "wheelchairAccessibleEntrance": true,
    "opening_openNow": false,
    "opening_weekdayText": "Monday: 12:00 AM – 5:00 PM | Tuesday: 12:00 AM – 5:00 PM | ... | Sunday: Closed",
    "photoCount": 9,
    "photoReferences": "Aa-ngMbUb2ZFd...; Aa-ngMatZVZ-b7Y5...; ...",
    "review1_author": "Pradeep Sharrma",
    "review1_rating": 5,
    "review1_text": "I have done life saver surgery by DR Arun gupta in 2025...",
    "review1_relativeTime": "3 weeks ago",
    "reviewsReturnedCount": 5,
    "geoChunk": "28.6390,77.1900",
    "rawPlaceDetailsJson": "{\"place_id\":\"ChIJd2Sm_soDDTkR62jsR0f47Y0\", ...full payload...}"
}
```

### ⚠️ Known limitations

- Reviews are capped at 5 per place — that's Google's own limit on the Place Details `reviews` field, not something this Actor can raise
- No "popular times" histograms, Q\&A, or owner updates
- A single run covers one `locationQuery` or `customBounds` area; for multiple non-contiguous areas, run the Actor once per area

### ❓ FAQ

**How does this Actor cover a whole area instead of a limited result set?**
It splits your search area into a grid of overlapping tiles (sized by `gridSizeKm`), searches each tile independently, and de-duplicates the results by `placeId`. Smaller tiles in dense areas find more unique places overall.

**Can I get the full, unflattened Google response for a place?**
Yes — every row's `rawPlaceDetailsJson` field is the complete Place Details payload as a JSON string, in addition to the flattened columns.

**Will the CSV export lose data that's in the JSON export?**
No. Every field is already a scalar or a joined string, so the CSV and JSON exports contain the same information.

# Actor input Schema

## `searchTerm` (type: `string`):

e.g. 'restaurant', 'plumber', 'coffee shop'

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

Free text location to geocode, e.g. 'Austin, TX, USA'. Ignored if custom bounds are given.

## `customBounds` (type: `object`):

Overrides locationQuery. {"north":..,"south":..,"east":..,"west":..}

## `gridSizeKm` (type: `integer`):

The location is split into tiles of roughly this size to work around the 60-result-per-search cap. Smaller = more API calls but more complete coverage in dense areas.

## `maxPlaces` (type: `integer`):

Stop once this many unique places have been found (0 = no limit).

## `fetchPlaceDetails` (type: `boolean`):

Makes one extra Place Details API call per place. Costs more, adds far more data per row (address components, contact info, hours, reviews, amenities...).

## `detailsTier` (type: `string`):

How much Place Details data to fetch per place, following Google's own Basic / Contact / Atmosphere billing tiers. essentials = Basic Data only (full address breakdown, geometry, categories, business status — no extra cost tier). pro = adds Contact Data (phone, website, opening hours). enterprise = adds Atmosphere Data too (rating, price level, reviews, amenities like delivery/dine-in/wheelchair access) — the richest and most expensive tier. See Google's Places API 'Usage and Billing' docs for current per-field pricing.

## Actor input object example

```json
{
  "gridSizeKm": 3,
  "maxPlaces": 200,
  "fetchPlaceDetails": true,
  "detailsTier": "pro"
}
```

# Actor output Schema

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

Extracted places, one item per unique place.

# API

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

## JavaScript example

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

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

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("bluetail/google-map-actor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("bluetail/google-map-actor").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 '{}' |
apify call bluetail/google-map-actor --silent --output-dataset

```

## MCP server setup

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

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/sbaz8Ejq80xFAxreK/builds/3A9S2marQfBVs1bvi/openapi.json
