# Google Maps Place Details Scraper – Bulk Place ID Lookup (`locomint/google-maps-place-details-scraper`) Actor

Google Maps place details scraper: paste place IDs or place links and get the full record for each, with address, phone, website, rating, weekly hours, role emails, social profiles and WhatsApp. For lead generation and CRM enrichment.

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

## Pricing

from $1.20 / 1,000 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?

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 Place Details Scraper – Bulk Place ID Lookup

This Google Maps place details scraper takes the place IDs or place-page links you already have and returns the full record for each one: address, phone, website, rating, all seven days of opening hours, and the role mailboxes, social profiles and WhatsApp number published on the business's own website.

```json
{
  "placeIds": ["ChIJPy04-ZkzGQ0RvdLuGmOMygg"]
}
```

### What it does

There is no search step. Each reference is read directly, so every row in the dataset is a place you asked for and nothing is spent on places you did not.

Three forms of reference are accepted, and each is converted to the same place ID:

| You paste | Example |
|---|---|
| A place ID | `ChIJPy04-ZkzGQ0RvdLuGmOMygg` |
| A feature ID | `0xd193399f9382d3f:0x8ca8c631aeed2bd` |
| A place-page link | `https://www.google.com/maps/place/CDL+-+Dental+Clinic+Lisbon/data=!4m2!3m1!1s0xd193399f9382d3f:0x8ca8c631aeed2bd` |

All three rows above point at the same clinic, and the actor would read it once: references are deduplicated after conversion. A link is read for its `!1s0x…:0x…` part, or for a `ChIJ…` ID in a `place_id` or `q` parameter. Short share links (`maps.app.goo.gl/…`) are not expanded, so open the link in a browser and paste the full address from the address bar.

Each place then gets:

- **The full listing**: name, categories, the address split into street, city, region, postal code and country, coordinates, the phone as displayed and in E.164 form, website, rating, review count, open or closed status, description, opening hours for all seven days, timezone, plus code, photo count with up to five photo URLs, and attributes such as delivery or dine-in.
- **The business website** (`includeEnrichment`, on by default): the homepage, plus one hop to the contact page when the homepage is missing an email address or a WhatsApp number. It returns role mailboxes, social profile links (Facebook, Instagram, LinkedIn, X, YouTube, TikTok), a WhatsApp number from click-to-chat links, the contact page URL, the technologies the site runs on (about 35 signatures) and a `website_status`.

A reference that is not recognised, or a place that no longer exists, produces no row and no charge. If a listing cannot be read at that moment, the place is skipped rather than delivered half-empty. The last log line says how many of your IDs were delivered, so you can re-run the rest.

#### What each row contains

| Group | Columns |
|---|---|
| Identity | `place_id`, `cid`, `name`, `primary_category`, `categories`, `description` |
| Location | `address`, `street`, `city`, `region`, `postal_code`, `country_code`, `lat`, `lng`, `plus_code`, `timezone`, `service_area_business` |
| Listing contact | `phone` (as displayed), `phone_e164`, `website` |
| Reputation | `rating`, `review_count`, `price_level` |
| Status and hours | `status`, `open_now`, `hours`, `hours_status` |
| Media and attributes | `photos_count`, `photo_urls`, `attributes` |
| From the website | `emails`, `socials`, `whatsapp`, `contact_form_url`, `tech_stack`, `website_status`, `enriched_at` |
| Provenance | `source`, `fetched_at` |

`hours` is keyed by weekday. Each day is a list of `{"open": "09:00", "close": "18:00"}` periods in local 24-hour time; an empty list means closed that day, `null` means unknown, and `00:00` to `24:00` means open all day. `hours_status` is `ok` when the full week was read, `not_published` when the listing gives no hours, and `unavailable` when the listing has hours but the read did not return the whole week.

`website_status` is one of `ok`, `unreachable` (the site was down or refused the request), `parked` (a domain-for-sale page), `redirect_social` (the "website" is a Facebook or Instagram page, kept in `socials`) or `no_website`.

### Who it is for

- **CRM enrichment.** Refresh phone, website, hours and status for the accounts you already hold, keyed on `place_id`, and add the role mailbox and WhatsApp line where the record has none.
- **Lead generation.** Complete a list from another tool that has place IDs but no contacts. Each row comes back with the business's own role mailboxes, social profiles and WhatsApp.
- **Sales prospecting.** Re-read a call list before a campaign, so reps dial the number on today's listing rather than the one captured months ago.
- **Agencies.** Check client listings every week for changed hours, phone numbers, websites or categories.
- **Local SEO.** Track `rating` and `review_count` for a fixed panel of competitors over time.
- **Market research.** Re-read the same sample of places at intervals; `place_id` does not change, so rows join cleanly across runs.

### How to use it

1. Open the actor in Apify Console and click **Try for free**.
2. Paste place IDs or feature IDs into **Place IDs**, one per line, or full place-page links into **Place page links**. Up to 1,000 per run across both fields.
3. Leave **Enrich from the business website** on for contacts, or turn it off for listing data only. The price is the same.
4. Click **Start** and export the dataset to CSV, Excel or JSON, or read it through the API.

The synchronous endpoint runs the actor and returns the rows in one request:

```bash
curl -X POST "https://api.apify.com/v2/acts/locomint~google-maps-place-details-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"placeIds": ["ChIJPy04-ZkzGQ0RvdLuGmOMygg"]}'
```

That endpoint waits at most 300 seconds, which is about 100 records. For longer lists use the Python client (`pip install apify-client`), which starts the run and waits for it to finish:

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
ids = [line.strip() for line in open("place_ids.txt") if line.strip()]
run = client.actor("locomint/google-maps-place-details-scraper").call(run_input={"placeIds": ids})
for record in client.dataset(run.default_dataset_id).iterate_items():
    print(record["place_id"], record["phone_e164"], record["emails"], record["hours_status"])
```

### Input example

```json
{
  "placeIds": [
    "ChIJPy04-ZkzGQ0RvdLuGmOMygg",
    "0x3e5f43fb0778138f:0xec413d37c36bb02e"
  ],
  "placeUrls": [
    { "url": "https://www.google.com/maps/place/CDL+-+Dental+Clinic+Lisbon/data=!4m2!3m1!1s0xd193399f9382d3f:0x8ca8c631aeed2bd" }
  ],
  "includeEnrichment": true,
  "language": "en"
}
```

The link points at the same clinic as the first ID, so this input produces two records, not three, and is charged for two.

| Field | Default | What it does |
|---|---|---|
| `placeIds` | – | `ChIJ…` place IDs or `0x…:0x…` feature IDs, one per line. |
| `placeUrls` | – | Full place-page links; the ID is read from the link. |
| `includeEnrichment` | true | Visit each business website for contacts and tech stack. Same price either way. |
| `language` | `en` | Language for names and categories (`en`, `de`, `ar`, `ja`, …). |

Give at least one ID or link. Both fields together are capped at 1,000 references per run after duplicates are removed.

### Output example

The Lisbon clinic from the input above, trimmed to the columns from a captured run. The full row carries every column listed under What it does, including `emails`, `hours` and `hours_status`.

```json
{
  "place_id": "ChIJPy04-ZkzGQ0RvdLuGmOMygg",
  "cid": "633473054880486077",
  "name": "CDL - Dental Clinic Lisbon",
  "primary_category": "Dental implants provider",
  "phone_e164": "+351213540059",
  "website": "https://dentistalisboa.com/",
  "rating": 4.8,
  "review_count": 272,
  "status": "open",
  "address": "R. Ferreira da Silva 9B, 1900-228 Lisboa, Portugal",
  "street": "R. Ferreira da Silva 9B",
  "city": "Lisbon",
  "country_code": "PT",
  "socials": {
    "facebook": "https://www.facebook.com/…",
    "instagram": "https://instagram.com/clinicadentarialisboa",
    "linkedin": "https://www.linkedin.com/…"
  },
  "whatsapp": "+351916423353",
  "contact_form_url": "https://dentistalisboa.com/contactos/",
  "tech_stack": ["wordpress", "cloudflare"],
  "website_status": "ok"
}
```

Missing values are `null` or an empty list, never guessed.

### Pricing

This actor is pay-per-event. You pay for two events and nothing else; Apify platform usage and proxy traffic are not billed to you separately.

| Event | Price | Charged |
|---|---|---|
| Record delivered | $0.0015 ($1.50 per 1,000) until 26 September 2026, then $0.0025 ($2.50 per 1,000) | once per row in the dataset |
| Actor start | $0.00005 per GB of run memory | once per run ($0.00005 at 1 GB) |

**Worked example.** 1,000 place IDs exported from your CRM: 1,000 x $0.0015 = $1.50, plus $0.00005 for the start. From 26 September 2026 the same run is $2.50.

You are not charged for references that are not recognised, for places that no longer exist or could not be read, or for duplicates within one run. Set a maximum cost per run in the run options and the actor stops before the next row would pass it. Apify's free plan includes $5 of usage a month with no card.

### FAQ

**Where do I get place IDs?**
Most Google Maps scrapers export a `place_id` or `placeId` column in the `ChIJ…` form, and the official Places API uses the same IDs. You can also open a business on the map and copy the link from the address bar. Numeric CIDs are not accepted.

**What happens to an ID that no longer resolves?**
It produces no row and no charge. The log counts references that were not recognised, and the last line says how many of your IDs were delivered.

**Why do some records have no email?**
The business has no website, the site did not answer, or it publishes no role mailbox on its homepage or contact page. A 403 or a challenge page counts as a refusal, and the actor does not retry it through another IP. Mailboxes that name a person are dropped on purpose.

**Does it include reviews?**
No. Every record carries `rating` and `review_count`, but not review text. The `reviews_summary` column exists for compatibility and is empty in this actor.

**Are the opening hours for the whole week?**
Yes, when the listing publishes them: `hours` carries all seven days and `hours_status` is `ok`. The source sometimes answers with today's hours only; the actor then reads the listing once more, and a record that still lacks the full week says `hours_status: "unavailable"`, so you can put that ID in your next run.

**How fresh is the data?**
Every run reads the listing and the website live, at the time of the run; nothing comes from a stored database. Each record carries `fetched_at` for the listing read and `enriched_at` for the website read.

**Is it legal to scrape Google Maps?**
The actor reads publicly available listing pages and business websites while logged out. It never signs in, never solves a CAPTCHA, and backs off when a site refuses it rather than retrying through another IP. Whether a particular use is lawful depends on your jurisdiction and on what you do with the data, and that part is your responsibility.

**Can I run the same list every week?**
Yes. Schedule the run in Apify; each run charges for the rows it delivers. Comparing `phone`, `website`, `hours`, `rating` and `review_count` between two runs shows what changed.

### Limits

- **Run size.** 1,000 references per run. There is no search and there are no filters in this form; to find places, use one of the two search actors below.
- **Speed.** Listing reads are paced at about 20 a minute, so 1,000 records take close to an hour.
- **References.** Short share links are not expanded and numeric CIDs are not accepted.
- **Reviews.** No review text. `reviews_summary`, `snapshot_id` and `first_seen_at` are always empty in actor runs.
- **Websites.** Pages are read as served, without running JavaScript, so a contact page that only appears after scripts load is missed. The actor reads the homepage and at most one contact page. A site that takes longer than 20 seconds is abandoned: that row keeps its `website` but shows `website_status: "no_website"` with `enriched_at: null`, meaning the site was not read.
- **Emails.** Role mailboxes only, such as info@, sales@ and bookings@. An address on another domain, a Gmail account for example, is kept only when its name is a business function.
- **Phones.** `phone` and `phone_e164` come from the listing, not from the website. `phone_e164` is empty when only a national number was offered and the calling code cannot be settled safely (+1 and +7 are each shared by several countries).
- **Fields not yet seen filled.** `price_level` has not been populated in any record we have captured, and closed markers have not appeared in our captures either.
- **Addresses.** Service-area businesses with no storefront have an empty `address` and `service_area_business: true`; `lat` and `lng` are still set. `street`, `region` and `postal_code` are filled only when the listing's structured address carries them.

### Compliance

Business contact points only: role mailboxes published on the business's own website. Mailboxes that name a person are dropped and owner names are never collected. You are responsible for using the data lawfully (for example GDPR, CAN-SPAM and PECR for outreach).

Questions, bug reports and feature requests go on this actor's Issues tab. Business owners can send data-removal requests to info@locomint.io; that address is for removal requests only. This actor keeps no copy of what it reads between runs, so each run returns what the public pages show at that moment.

Not affiliated with or endorsed by Google.

### Other Locomint actors

- [Google Maps Scraper & Email Extractor – Business Leads](https://apify.com/locomint/google-maps-scraper): Search terms and a city in, business records with website contacts out.
- [Google Maps Scraper – Multi-City Lead Lists with Emails](https://apify.com/locomint/google-maps-multi-city-scraper): Many categories across many cities in one deduplicated run.
- [Website Email Scraper – Contact Details, Socials & WhatsApp](https://apify.com/locomint/website-content-contact-extractor): Contact points from website URLs you supply.
- [Website Content Crawler – Markdown for AI, Emails & Contacts](https://apify.com/locomint/website-crawler-content-contacts): A whole site as Markdown, with its contact points.
- [Bulk Email Verifier & Validator](https://apify.com/locomint/bulk-email-verifier): Checks whether addresses can receive mail.
- [Company Enrichment API – Domain to Emails, Socials & Tech](https://apify.com/locomint/company-enrichment): A domain in, its contacts and technologies out.
- [AI Crawler Checker – robots.txt Rules for GPTBot & ClaudeBot](https://apify.com/locomint/ai-crawler-checker): Which AI crawlers a site's robots.txt allows.
- [Schema Markup Validator & Generator – JSON-LD Checker](https://apify.com/locomint/schema-markup-validator): Checks and generates schema.org markup.

# Actor input Schema

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

Place IDs (ChIJ...) or feature IDs (0x...:0x...), one per line. Up to 1,000 per run.

## `placeUrls` (type: `array`):

Alternatively, paste map place-page links (the URL you see when you open a business on the map). The ID is read from the link.

## `includeEnrichment` (type: `boolean`):

Visits each business's own website to extract business emails, social profiles, WhatsApp, contact page and tech stack. Included in the price. Never collects personal data.

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

BCP-47 language for names and categories (en, ar, de, ja, ...).

## Actor input object example

```json
{
  "placeIds": [
    "ChIJjxN4B_tDXz4RLrBrwzc9Qew"
  ],
  "includeEnrichment": true,
  "language": "en"
}
```

# Actor output Schema

## `records` (type: `string`):

One full record per place ID or link: identity, location, contact, reputation, status, weekly hours, description and website enrichment.

# 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 = {
    "placeIds": [
        "ChIJjxN4B_tDXz4RLrBrwzc9Qew"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("locomint/google-maps-place-details-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 = { "placeIds": ["ChIJjxN4B_tDXz4RLrBrwzc9Qew"] }

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,locomint/google-maps-place-details-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/TpeOB27nFwjc5nuXg/builds/5sHtEgW1XoL81bNoe/openapi.json
