# Google Maps Places Scraper (`sfoln/google-maps-search`)

Search Google Maps by keyword and location and get one clean JSON row per place: name, category, address, phone, website, rating, review count, opening hours, coordinates and place ID. $0.80 per 1,000 places + $0.002 per search. No browser needed.

- Family: Local & maps
- Categories: Lead generation, Travel, SEO tools
- Availability: on request (private on Apify). Email contact@sfoln.com with subject "Access request: Google Maps Places Scraper (sfoln/google-maps-search)" before calling; calls fail until it is public.
- JSON: https://www.sfoln.com/actors/google-maps-search.json
- MCP: https://mcp.apify.com?tools=sfoln/google-maps-search
- REST (sync): POST https://api.apify.com/v2/actors/sfoln~google-maps-search/run-sync-get-dataset-items
- REST (async): POST https://api.apify.com/v2/actors/sfoln~google-maps-search/runs

Google Maps Places Scraper searches Google Maps the way you would in the app — "roofing contractors Philadelphia", "dentist near Austin" — and returns one clean, normalized record per place: name, category, full address with parts, phone, website and domain, rating and review count, opening hours and open-now flag, coordinates, Google place ID and CID. Built for lead generation, local-SEO audits, market mapping and AI agents that need structured local business data in seconds, without running a browser.

## Price per delivered record

| Event | Price | Per 1,000 | Charged for |
| --- | --- | --- | --- |
| Google Maps place (`google-maps-place`) | $0.0008 | $0.80 / 1,000 | Charged once per Google Maps place (business) delivered as a dataset record. |
| Google Maps query (`google-maps-query`) | $0.002 | $2 / 1,000 | Charged once per search query that returned at least one place, or that returned none but was billed upstream (covers the per-search upstream fee). |

Pay-per-event, USD, billed by Apify; no platform usage charge. Only records with status `ok` are charged. Cap a run with `maxTotalChargeUsd`. Apify free plan: up to 10 per run.

## Run

```bash
curl -X POST "https://api.apify.com/v2/actors/sfoln~google-maps-search/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"queries":["pizza restaurants in Brooklyn"],"location":"New York,New York,United States","maxPlacesPerQuery":5}'
```

MCP client configuration:

```json
{
  "mcpServers": {
    "apify": {
      "type": "http",
      "url": "https://mcp.apify.com?tools=sfoln/google-maps-search"
    }
  }
}
```

Example prompt: Find 20 pizza restaurants in Brooklyn on Google Maps and list their ratings, review counts, phone numbers and websites.

## Input

Enter one or more Google Maps searches. Every place found becomes one dataset record; each query also gets one summary record.

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `queries` (required) | array | — | Search queries: What you would type into Google Maps, one per line (e.g. "roofing contractors Philadelphia"). To override the location for a single query write "query \| location". Also accepts objects { "query": "...", "location": "..." }. Accounts on the Apify free plan can process up to 10 queries per run. |
| `location` | string | — | Location: Where to search: a DataForSEO location name ("Philadelphia,Pennsylvania,United States"), a numeric location code (2840 = United States), or coordinates "latitude,longitude[,zoom]" (e.g. "40.6782,-73.9442,13"). Leave empty for the whole United States. |
| `languageCode` | string | `"en"` | Language code: Language of the results (ISO code such as en, de, es, fr, pt-br). |
| `maxPlacesPerQuery` | integer | `20` | Max places per query: How many places to return (and pay for) per query. Google Maps returns at most about 100 places per search. |
| `depth` | integer | — | Search depth (advanced): Number of results requested from Google per query (defaults to Max places per query, capped at 100 so each query stays one upstream search; values below Max places per query are raised to it). Places beyond Max places per query are discarded and never charged; raise it only if sponsored results crowd out regular places. |
| `device` | string | `"desktop"` | Device: Emulated device for the search. One of `"desktop"`, `"mobile"`. |
| `maxQueries` | integer | `100` | Max queries: Safety cap on the number of queries processed in this run. |
| `concurrency` | integer | `3` | Concurrency: How many queries run in parallel. Kept low because the upstream concurrency limit is shared by all runs of this Actor. |
| `includeRaw` | boolean | `false` | Include raw upstream response: Attach the unmodified provider item to each place record (larger output). |

Example input:

```json
{
  "queries": [
    "pizza restaurants in Brooklyn"
  ],
  "location": "New York,New York,United States",
  "maxPlacesPerQuery": 5
}
```

## Output

One record per input item with `status` (`ok` is charged; `not_found`, `error`, `skipped_*` are free) and `charged`. The run stores an `OUTPUT` summary record.

| Field | Type | Meaning |
| --- | --- | --- |
| `recordType` | string | Record type: place = one Google Maps place (charged google-maps-place); query = per-query summary (charged google-maps-query when ok). |
| `query` | string | Query: Search query this record belongs to. |
| `location` | string | Location: Location used for the search. |
| `input` | string | object | null | Input: The query as entered ({ query, location? }). |
| `rank` | integer | null | Rank: Position in the Google Maps results (1 = first). |
| `title` | string | null | Name: Business / place name. |
| `originalTitle` | string | null | Original name: Name in the original language, when Google shows a translated one. |
| `description` | string | null | Description: Owner description, when shown. |
| `category` | string | null | Category: Primary Google category. |
| `additionalCategories` | array | Additional categories: Secondary Google categories. |
| `categoryIds` | array | Category ids: Google category ids, e.g. pizza_restaurant. |
| `address` | string | null | Address: Full formatted address. |
| `addressInfo` | object | null | Address parts: street, borough, city, zip, region, countryCode. |
| `phone` | string | null | Phone: Phone number as shown on Google Maps. |
| `url` | string | null | Website: Website URL. |
| `domain` | string | null | Domain: Website domain without www. |
| `placeId` | string | null | Place ID: Google place_id (ChIJ…). |
| `cid` | string | null | CID: Google customer id (maps.google.com/?cid=…). |
| `featureId` | string | null | Feature ID: Google feature id. |
| `latitude` | number | null | Latitude: WGS84 latitude. |
| `longitude` | number | null | Longitude: WGS84 longitude. |
| `rating` | object | null | Rating: { value, votesCount } |
| `ratingDistribution` | object | null | Rating distribution: Number of reviews per star (1–5). |
| `priceLevel` | string | null | Price level: inexpensive \| moderate \| expensive \| very_expensive |
| `hotelRating` | number | null | Hotel stars: Hotel class (stars), for hotels. |
| `isClaimed` | boolean | null | Claimed: Whether the owner has verified the listing. |
| `currentStatus` | string | null | Current status: open \| closed \| temporarily_closed \| closed_forever |
| `openNow` | boolean | null | Open now: True when open at crawl time. |
| `workHoursSummary` | string | null | Opening hours: Compact weekly hours, e.g. "Mon 09:00-17:00; …; Sun closed". |
| `workHours` | object | null | Opening hours (structured): Day → list of "HH:MM-HH:MM" ranges; null = closed. |
| `mainImage` | string | null | Main image: URL of the main photo. |
| `totalPhotos` | integer | null | Photos: Number of photos on the listing. |
| `logo` | string | null | Logo: URL of the business logo. |
| `contactUrl` | string | null | Contact URL: Contact page link from the listing. |
| `contributorUrl` | string | null | Contributor URL: Owner / contributor profile link. |
| `bookOnlineUrl` | string | null | Booking URL: Online booking link. |
| `snippet` | string | null | Snippet: Short text shown in the results. |
| `googleMapsUrl` | string | null | Google Maps URL: Link to the place on Google Maps. |
| `placesReturned` | integer | Places returned: Query records only: number of place records delivered for the query. |
| `googleMapsSearchUrl` | string | null | Google Maps search URL: Query records only: the Google Maps search the places come from. |
| `status` | string | Status: ok \| not_found \| error \| skipped_budget \| skipped_free_plan \| skipped_trial |
| `charged` | boolean | Charged: Whether this record incurred a pay-per-event charge. |
| `chargedEvent` | string | Charged event: google-maps-place or google-maps-query. |
| `sponsored` | boolean | Sponsored: true when a promotional trial paid for this record instead of you: delivered with charged false and never billed (present only then). |
| `promotionId` | string | Promotion: Id of the promotional trial that sponsored this record (present only with sponsored). |
| `trialUnitsApplied` | integer | Trial events: Events of this record the trial paid for (present only with sponsored). |
| `retailValueWaivedUsd` | number | Value waived (USD): What those events cost at the listed prices, in USD: what you did not pay (present only with sponsored). |
| `warnings` | array | Warnings: Query records only: non-fatal notes (places reduced, budget reached...). |
| `error` | object | Error: {code, message, provider?} when a query record is not ok; with status skipped_trial or skipped_free_plan the code says why the query did not run (allowance_exhausted, run_cap, not_eligible, ledger_unavailable, free_plan_subsidy_cap, …). |
| `source` | string | Source: Upstream data source. |
| `fetchedAt` | string | Fetched at: ISO timestamp. |

Example record:

```json
{
  "recordType": "place",
  "query": "pizza restaurants in Brooklyn",
  "location": "New York,New York,United States",
  "rank": 1,
  "title": "Joe's Pizza",
  "category": "Pizza restaurant",
  "additionalCategories": [
    "Takeout restaurant"
  ],
  "categoryIds": [
    "pizza_restaurant",
    "takeout_restaurant"
  ],
  "address": "216 Bedford Ave, Brooklyn, NY 11249",
  "addressInfo": {
    "street": "216 Bedford Ave",
    "borough": "Williamsburg",
    "city": "Brooklyn",
    "zip": "11249",
    "region": "New York",
    "countryCode": "US"
  },
  "phone": "+1 718-555-0100",
  "url": "https://www.example-pizza.com/",
  "domain": "example-pizza.com",
  "placeId": "ChIJxxxxxxxxxxxxxxxxxxxx",
  "cid": "12345678901234567890",
  "featureId": "0x89c25bxxxxxxxx:0x1234xxxxxxxx",
  "latitude": 40.7176,
  "longitude": -73.9587,
  "rating": {
    "value": 4.6,
    "votesCount": 2134
  },
  "ratingDistribution": {
    "1": 60,
    "2": 40,
    "3": 120,
    "4": 400,
    "5": 1514
  },
  "priceLevel": "inexpensive",
  "isClaimed": true,
  "currentStatus": "open",
  "openNow": true,
  "workHoursSummary": "Mon 10:00-23:00; Tue 10:00-23:00; Wed 10:00-23:00; Thu 10:00-23:00; Fri 10:00-01:00; Sat 10:00-01:00; Sun 10:00-23:00",
  "workHours": {
    "monday": [
      "10:00-23:00"
    ],
    "sunday": [
      "10:00-23:00"
    ]
  },
  "mainImage": "https://lh5.googleusercontent.com/p/...",
  "totalPhotos": 812,
  "snippet": "216 Bedford Ave, Brooklyn, NY 11249",
  "googleMapsUrl": "https://www.google.com/maps?cid=12345678901234567890",
  "status": "ok",
  "charged": true,
  "chargedEvent": "google-maps-place",
  "source": "dataforseo",
  "fetchedAt": "2026-10-14T10:00:00.000Z"
}
```

## Related

- [Google Maps Leads Scraper + Emails](https://www.sfoln.com/actors/google-maps-business-leads.md) (`sfoln/google-maps-business-leads`)
- [Google Business Profile Scraper](https://www.sfoln.com/actors/google-business-profile.md) (`sfoln/google-business-profile`)
- [Local SEO Audit – Google Business Profile](https://www.sfoln.com/actors/local-business-audit.md) (`sfoln/local-business-audit`)
- [Website Contact Details Scraper](https://www.sfoln.com/actors/company-contacts-finder.md) (`sfoln/company-contacts-finder`)

Publisher: SFOLN LLC, contact@sfoln.com. Catalogue: https://www.sfoln.com/catalog.json. Agent guide: https://www.sfoln.com/agents.md.
