# Google Maps Reviews Scraper — Date Range, New-Only, Complete (`kestrel/google-maps-reviews-scraper`) Actor

Scrape Google Maps reviews by URL, short link, place id or name. True date range, new-reviews-only monitoring, owner replies, exact dates, text-only and star filters, and a completeness check against the listed total on every place. No login. $0.40 per 1,000 reviews.

- **URL**: https://apify.com/kestrel/google-maps-reviews-scraper.md
- **Developed by:** [Tedj MEABIOU](https://apify.com/kestrel) (community)
- **Categories:** Lead generation, Automation, AI
- **Stats:** 6 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.40 / 1,000 review rows

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?

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

## Google Maps Reviews Scraper — date range, new-only monitoring, completeness check

This Google Maps reviews scraper reads the Google reviews of any place — a restaurant, a hotel, a clinic, a competitor, all 40 branches of your own brand — and gives you, as a Google reviews scraper should, one clean row per review: stars, the full text, **exact** publish and edit dates, the **owner replies**, likes, photos, the reviewer, and the sub-ratings (food, service, rooms…) when the reviewer gave them. It was built from what users of other Google reviews scrapers complain about: it checks every place against the total Google lists and tells you the result, it has a **true date range** and **new-reviews-only** review monitoring so repeat runs stay cheap, and it accepts whatever you have — a Maps URL, a short link, a place id, or just "name, city". No login, no API key, no browser. $0.40 per 1,000 reviews.

Last verified working: 2026-09-21

### What does the Google Maps Reviews Scraper do?

You give it places. For each place it:

1. **Finds the place** — from a Google Maps URL, a `maps.app.goo.gl` or `share.google` short link, a place id (`ChIJ…`), a CID, or a plain `name, city`. No place id is needed for a name or a search URL; the actor resolves it.
2. **Reads what Google lists** — the rating, the total number of reviews and the per-star histogram. This is the number your delivery is checked against.
3. **Walks the reviews** in the order you choose, 50 at a time, applying your filters *before* anything is charged, and stopping as early as your date range or new-only memory allows.
4. **Reports honestly** — one free `place` row per place with a completeness verdict such as `4,404 listed, 4,404 read — complete`, and one free `status` row per input line saying what was found, filtered, skipped and charged.

It is a reviews tool, not a general Google Maps scraper: it does not list places or collect contacts, it reads business reviews — all of them, or exactly the slice you ask for — and it works as a Google reviews API for your own code: one HTTP call in, JSON out.

Three row types share one dataset: `review` (charged), `place` (free) and `status` (free).

### Google reviews you can trust: complete Google reviews, checked on every place

The most common complaint about Google reviews scrapers is silent incompleteness: *"we have over 1,950 Google reviews, only got 1,850 back"*, *"scraping the same place twice leads to different datasets"*, *"out of 9,000 reviews there are 6,437 duplicates"*. This actor treats that as the main job:

- **Completeness check on every place.** The reviews read are compared with the total Google lists. If a full walk comes up more than 2% short, the actor walks the place again in another sort order and merges the two passes. The verdict is printed on the `place` row and is never silent: `1,950 listed, 1,948 read, 2 withheld by Google`.
- **Zero duplicates.** Reviews are de-duplicated by Google's own review id, within a walk and across passes. The `status` row tells you how many repeats Google served (`duplicates`), none of which you see or pay for.
- **No false empties.** Google sometimes answers an empty page to a busy IP. An empty page while Google still lists more reviews is never read as "no more reviews": the same page is asked again from another IP.
- **Repeatable.** The newest-first feed is strictly ordered by last-modified time (measured: 0 order inversions in a 4,404-review walk), so the same place walked twice gives the same set — on the release test, identical id sets on places of 4,222 and 1,215 reviews, and a difference of one review on a third. That last one is Google, not the actor: its feed serves about one review in several thousand intermittently, and the listed total moves by one with it.
- **Honest billing.** The number of `review` events charged always equals the number of review rows delivered. Your spending limit is honoured mid-place. A place Google does not know ends as a green run with a `not_found` status row and a $0 charge.

On the release test a bakery with 4,404 listed reviews was read in full: 4,404 delivered, 0 duplicates, and the delivered stars matched Google's histogram star for star (277 / 220 / 363 / 692 / 2,852).

### Google reviews by date range — without rescraping everything

*"Would love a 'from date' and 'to date'… now I need to rescrape everything I already have."* Set `fromDate` and/or `toDate`, as a date (`2026-07-01`) or a look-back (`30 days`, `6 months`).

With the default newest-first order the actor **stops reading at your From date**. A place with 50,000 reviews and 40 reviews from the last 30 days costs one page and 40 reviews — not a thousand pages. Reviews newer than `toDate` are read past and not charged.

```json
{
  "places": ["Katz's Delicatessen, New York"],
  "fromDate": "2026-04-01",
  "toDate": "2026-06-30",
  "maxReviewsPerPlace": 0
}
```

Dates are compared with `published_at`, the moment the review was first posted. Turn on `includeEdited` and a review that was *edited* inside the range counts as inside it too.

### Review monitoring: only new reviews, and a quiet run costs $0

For agencies, multi-location brands and anyone who wants new Google reviews in Slack — Google reviews for multiple locations is exactly what this mode is for: turn on `onlyNew` and put the run on a schedule.

- The first run delivers up to `maxReviewsPerPlace` reviews per place and remembers them.
- Every later run delivers — and bills — **only reviews it has not delivered before**, then stops at the first page of already-delivered reviews. For most places that is a single request.
- A run that finds nothing new delivers 0 rows and charges **$0**.
- With `includeEdited`, a review whose author edited it after you received it is delivered again (`edited` = true, with both dates).
- `monitorKey` names the memory. Runs with the same key share it; give each separate feed — a client, a channel, a test — its own key.
- Only what was actually delivered is remembered, so a review your spending limit refused comes back next run.

```json
{
  "places": ["ChIJD3uTd9hx5kcR1IQvGfr8dbk", "https://maps.app.goo.gl/your-branch-link", "Your Brand, Lyon"],
  "onlyNew": true,
  "monitorKey": "acme-weekly",
  "maxReviewsPerPlace": 200
}
```

**Recipe — new reviews to Slack, n8n or Make:** schedule the input above (daily or weekly) → add an Apify integration that fires on *run succeeded* → in Make or n8n use the *Get dataset items* step with `type=review` → post `place_name`, `stars`, `text` and `review_url` to your channel. Filter `stars <= 3` there, or set `maxStars: 3` here and never pay for the good ones.

### What data does each Google Maps review row contain?

A flat row, no nested junk to clean, and no empty columns that are empty for everyone:

| Field | Meaning |
|---|---|
| `review_id` | Google's id of the review — the same id Google Maps uses; stable across runs |
| `place_id`, `place_name`, `place_url` | The place the review belongs to |
| `review_url` | Direct link to this review on Google Maps |
| `report_url` | Google's "report this review" link |
| `stars` | 1–5 |
| `text` | The review text; `null` for a star-only rating |
| `text_translated` | Google's translation (with `translate` = both) |
| `original_language` | Language the review was written in (`en`, `fr`, `ja` …) |
| `published_at` | When it was first posted — **exact** ISO 8601 UTC, not guessed from "3 weeks ago" |
| `updated_at`, `edited` | When it was last edited, and whether it was |
| `published_text` | Google's own relative date, e.g. `3 weeks ago`, `Edited a month ago` |
| `likes` | Helpful votes |
| `photos` | URLs of the photos attached to the review |
| `owner_reply_text`, `owner_reply_at` | The owner's response and its exact date (`owner_reply_text_translated` with `translate` = both) |
| `reviewer_name`, `reviewer_id`, `reviewer_url`, `reviewer_photo_url` | The reviewer's public profile |
| `reviewer_review_count`, `reviewer_photo_count`, `reviewer_is_local_guide` | How experienced the reviewer is |
| `sub_ratings` | Aspect ratings when given: `{"Food": 5, "Service": 4, "Atmosphere": 4}` for restaurants, rooms / service / location for hotels |
| `visit_details` | Answers to Google's visit questions: `{"Meal type": "Dinner", "Price per person": "€20–30", "Wait time": "No wait"}` |
| `visited_in` | Month of the visit, `YYYY-MM`, when stated |
| `target`, `fetched_at` | The input line the row came from, and when it was read |

Because Google sends an exact timestamp with every review, `published_at` is the real moment of posting in every language — there is no fragile conversion of "vor 3 Wochen" or "3 週間前" into a date.

```json
{
  "type": "review",
  "review_id": "Ci9DQUlRQUNvZENodHljRjlvT2w5UVVsa3hUV0ZJWVc5eUxXVmpZbTlNV2t0NWRHYxAB",
  "place_name": "Du Pain et des Idées",
  "stars": 4,
  "text": null,
  "published_at": "2026-08-29T14:56:53+00:00",
  "updated_at": "2026-08-29T14:56:53+00:00",
  "published_text": "3 weeks ago",
  "edited": false,
  "likes": 0,
  "owner_reply_text": "Bonjour,\n\nMerci pour cette note ! Nous ferons le nécessaire pour mériter la cinquième étoile…",
  "owner_reply_at": "2026-08-30T13:30:12+00:00",
  "reviewer_name": "Yüksel Savaş",
  "reviewer_review_count": 6,
  "reviewer_is_local_guide": false,
  "sub_ratings": {"Food": 5, "Service": 4, "Atmosphere": 4},
  "visited_in": "2026-08"
}
```

### Place rows and status rows: the summary, free

One `place` row per place (switch off with `includePlaceRow`):

| Field | Meaning |
|---|---|
| `rating`, `reviews_total`, `histogram` | What Google lists: the average, the total and reviews per star |
| `reviews_read`, `reviews_delivered` | Distinct reviews this run read before filters, and how many it delivered |
| `stars_read` | The reviews read, per star — compare with `histogram` |
| `completeness` | The verdict sentence |
| `stopped_by` | What ended the walk early — `maxReviewsPerPlace`, `fromDate`, `onlyNew`, `spendingLimit` — or `null` for a full walk |
| `trend` | Average stars of the last 90 days against the 90 days before, with the review counts (only when a newest-first walk covered the 180 days) |
| `address`, `category`, `cid`, `place_url` | Context for joining with your own data |

One `status` row per input line: `status` (`ok`, `no_reviews`, `not_found`, `duplicate`, `error`), `listed`, `found`, `reviews`, `filtered`, `filtered_by` (how many each filter removed, e.g. `{"onlyWithText": 31}`), `already_seen`, `duplicates`, `pages`, `passes`, `charged` and `error`.

### Google reviews scraper filters — all free, all before billing

*"If I ask for the latest 100 reviews, many won't contain any text. For my purpose the text is the important part."* Every filter runs before billing, so you pay only for rows you keep, and `maxReviewsPerPlace` counts only rows that pass:

- `onlyWithText` — skip star-only ratings (about a third of all Google reviews).
- `minStars` / `maxStars` — e.g. `maxStars: 2` for negative Google reviews only.
- `keywords` / `excludeKeywords` — words or phrases to require or to drop, matched in the text and in its translation.
- `reviewLanguages` — only reviews written in these languages.
- `hasOwnerReply` — `yes` for Google reviews with owner responses (study a competitor's tone), `no` for the ones still waiting for a reply (a reputation manager's to-do list).
- `hasPhotos` — only reviews carrying photos.
- `sort` — `newest`, `most_relevant`, `highest`, `lowest`.
- `translate` — `original`, `translated` (Google's translation into `language`), or `both`.

```json
{
  "places": ["Du Pain et des Idées, Paris"],
  "sort": "lowest",
  "maxStars": 2,
  "onlyWithText": true,
  "translate": "both",
  "maxReviewsPerPlace": 100
}
```

### How do I run the Google Maps reviews scraper?

1. Paste your places into **Places**, one per line. Any mix works:

```text
https://www.google.com/maps/place/Louvre+Museum/@48.86,2.33,17z/data=!4m6!3m5!1s0x47e671d877937b0f:0xb975fcfa192f84d4
https://maps.app.goo.gl/AbCdEfGh12345
ChIJD3uTd9hx5kcR1IQvGfr8dbk
10222232094831998944
https://www.google.com/maps/search/?api=1&query=Katz+Delicatessen+New+York
Du Pain et des Idées, Paris
```

A column copied from Excel or Google Sheets pastes as it is. The same place given twice — as a URL and as a place id, say — is read once; the second line becomes a free `duplicate` status row.
2\. Set **Max reviews per place** (`0` = all). Optionally a date range, filters, or new-only monitoring.
3\. Run. Export Google reviews as CSV, Excel, JSON or XML — a Google reviews CSV opens straight in Excel or Google Sheets — or read them over the API.

**Coming from another actor?** Paste your Compass input as it is: `startUrls`, `placeIds`, `maxReviews`, `reviewsSort` (`newest`, `mostRelevant`, `highestRanking`, `lowestRanking`), `reviewsStartDate` and `language` are all understood.

Other inputs: `language` (interface and translation language, e.g. `de`, `pt-BR`), `country` (the two-letter country Google is asked from), `sessions` and `perIp` (parallelism and pacing), and `proxyConfiguration` — datacenter proxies are enough, which is what keeps the price low.

### Scrape Google reviews from Python, JavaScript, curl, n8n, Make and AI agents

Python:

```python
from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("kestrel/google-maps-reviews-scraper").call(run_input={"places": ["Katz's Delicatessen, New York"], "fromDate": "30 days", "onlyWithText": True, "maxReviewsPerPlace": 0})
reviews = [r for r in client.dataset(run["defaultDatasetId"]).iterate_items() if r["type"] == "review"]
```

JavaScript (Node.js):

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<APIFY_TOKEN>' });
const run = await client.actor('kestrel/google-maps-reviews-scraper').call({ places: ['ChIJD3uTd9hx5kcR1IQvGfr8dbk'], maxReviewsPerPlace: 500 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

curl — run and get the rows in one call:

```bash
curl -X POST "https://api.apify.com/v2/acts/kestrel~google-maps-reviews-scraper/run-sync-get-dataset-items?token=<APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"places": ["Du Pain et des Idées, Paris"], "maxReviewsPerPlace": 50}'
```

**n8n and Make:** use the Apify node/module → *Run an Actor and get dataset*. **AI agents and MCP:** the actor is available through the Apify MCP server, so Claude, ChatGPT or your own agent can call it as a tool — "get the negative Google reviews of this place from the last 90 days".

**Recipe — AI sentiment analysis:** run with `onlyWithText: true` and `translate: "translated"` so every row has text in one language, then send `text`, `stars` and `sub_ratings` to your model. You pay for no star-only rows, and the exact `published_at` lets the model see trends by month.

**Recipe — back up your Google reviews (Google Business Profile reviews included, replies and all):** run once with `maxReviewsPerPlace: 0` for all your locations and keep the CSV; then schedule the same places with `onlyNew: true` and append. If a review is ever removed or your Business Profile is suspended, you still have every review, reply and date.

### How much does it cost to scrape Google Maps reviews?

One event, **$0.0004 per delivered review** — $0.40 per 1,000, $4.00 for 10,000. There is no start fee.

Free: place rows, status rows, filtered reviews, duplicates, translations, the completeness second pass, reviews an earlier new-only run already delivered, places with no reviews, and places Google does not know. With `onlyWithText` on you pay only for reviews that have text. `maxReviewsPerPlace` and your run's spending limit cap the bill; when the limit is reached the run stops cleanly and says so.

### How fast is it, and what are the limits?

- About 1,000 reviews per minute per place (50 reviews a request), and places run in parallel — `sessions` sets how many.
- Google does not publish every review: spam-filtered and very fresh star-only ratings can be listed in the total before they appear in any feed. That is what `withheld by Google` in the verdict means; it is usually 0 and rarely above a fraction of a percent.
- `likes` is the helpful-vote count at the time of the run.
- Reviews come from Google's public reviews feed for signed-out visitors. The actor never logs in and cannot see anything a signed-out visitor cannot.
- A `share.google` or `maps.app.goo.gl` link is followed to the place it points at; a link that points at a list, a route or nothing at all is reported as `not_found`, free.

### Is it legal to scrape Google Maps reviews?

Google reviews are public: anyone can read them without an account. This actor reads only that public data and never logs in. Review rows contain personal data the reviewers chose to publish (display name, profile link, photo), so the GDPR and similar laws apply to what *you* do with it: have a legitimate reason, keep only what you need — drop the `reviewer_*` columns if you only analyse text — and do not use it to contact or profile individuals. If in doubt, ask your lawyer. This is not legal advice.

### FAQ

#### Do I need a Google API key or a Google account?

No — this is Google reviews without an API key. The official Places API returns at most five reviews per place; this actor reads the public reviews feed and needs no key, no account and no cookies.

#### Can I get Google Maps reviews by place id?

Yes — put the `ChIJ…` place id in `places` (or in `placeIds`). A CID, a feature id (`0x…:0x…`) and any Maps URL work the same way.

#### Why do search URLs and short links work here without a place id?

The actor resolves them itself: a Google Maps short link (`maps.app.goo.gl`, `share.google`) is followed, a name or a search URL is looked up on Google Maps, and only a real business is accepted — a name Google cannot place is reported as `not_found` instead of silently returning some other place's reviews.

#### A name resolved to the wrong branch — what now?

A plain name is looked up the way Google Maps search does it, and for a name that several places share (a chain, or a restaurant with a sister venue in another city) Google's first result can differ between runs. The `place` and `status` rows always show the `place_name` and `address` that were read, so a wrong match is visible at once. For anything you run repeatedly, use the place's Maps URL, place id or CID instead of its name — those always mean exactly one place.

#### Can I download Google reviews as CSV or Excel?

Yes. To download Google reviews, open the run's dataset: Every run's dataset exports to CSV, Excel, JSON, XML and RSS. Use the *Reviews* view for a clean table of review rows only.

#### How do I get only new reviews each week?

Turn on `onlyNew`, schedule the run weekly. Each run delivers only reviews not delivered before; a week with no new reviews costs $0.

#### Can I get reviews from the last 30 days only?

Set `fromDate` to `30 days`. With newest-first order the actor stops reading at that date, so you do not pay to walk the older reviews.

#### Is there a Google Maps reviews API I can call from my own code?

Yes — the actor *is* one. Call it with the `run-sync-get-dataset-items` endpoint shown above and you get the review rows back in the same HTTP response, as JSON or CSV. The same call works from Python, JavaScript, n8n, Make, Zapier and MCP-connected AI agents.

#### Can I get Google reviews with text only?

Turn on `onlyWithText`. Star-only ratings are read past for free, and `maxReviewsPerPlace` counts only the reviews that have text.

#### Does it return the owner's replies?

Yes: `owner_reply_text` and the exact `owner_reply_at`. Filter with `hasOwnerReply`.

#### Can I scrape reviews for many locations at once?

Yes. Paste hundreds of places; they are read in parallel, each gets its own place and status row, and one bad line never fails the run.

#### What happens if a place has no reviews, or does not exist?

A place with no reviews gives a `no_reviews` status row; an unknown place gives `not_found`. Both are free and the run still succeeds.

#### Are competitor reviews allowed?

You can read the public reviews of any place, including competitors — a common use for sector research and for benchmarking owner response times.

### Related scrapers

From the same author, with the same charged-equals-delivered billing: Tripadvisor, Booking.com, Agoda, Trip.com, Airbnb and Trustpilot review scrapers for the other half of a location's reputation, and Google Hotels prices for hotel rate monitoring.

# Changelog

This Actor's version history is a separate document: https://apify.com/kestrel/google-maps-reviews-scraper/changelog.md

# Actor input Schema

## `places` (type: `array`):

One place per line, in any mix: a Google Maps place URL, a maps.app.goo.gl or share.google short link (followed for you), a place id (ChIJ…), a CID (the long number in ?cid=), a feature id (0x…:0x…), a Maps search URL, or just "name, city" — the actor finds the place itself, no place id needed. A column copied from Excel or Google Sheets can be pasted as it is. The same place given twice is read once.

## `startUrls` (type: `array`):

An alias for Places, for integrations that always send startUrls (Make, n8n, Zapier, a Google Sheet of links) and for input copied from compass/google-maps-reviews-scraper. Every entry is read exactly like a line in Places.

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

An alias for Places that takes Google place ids (ChIJ…), so the placeIds list of a Compass input or of a Google Places API export pastes straight in.

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

0 = every review the place has. N = the first N in the chosen order that pass your filters. This is the main cost control: you pay per delivered review, so 100 reviews from 50 places is at most 5,000 reviews = $2.00.

## `sort` (type: `string`):

Newest first is what monitoring and date ranges need: the walk stops as soon as it passes your From date, so nothing older is read or paid for. Lowest rating first is the quick way to a place's complaints.

## `fromDate` (type: `string`):

Only reviews published on or after this day: YYYY-MM-DD, or a look-back such as "30 days", "2 weeks", "6 months". With Newest first the actor stops reading at this date, so a weekly run over a place with 50,000 reviews reads one page, not a thousand.

## `toDate` (type: `string`):

Only reviews published on or before this day: YYYY-MM-DD or a look-back such as "7 days". Together with From date this is a true date range — for example one calendar quarter.

## `onlyNew` (type: `boolean`):

Monitoring mode. The actor remembers which reviews it delivered for each place and, on the next run, delivers and bills only the ones it has not delivered before. Put the run on a schedule and you get a feed of new reviews; a run that finds nothing new costs $0.

## `includeEdited` (type: `boolean`):

With Only new reviews on: deliver a review again when its author edited it after you received it (the row carries edited = true and both dates). With a date range: a review edited inside the range counts as inside it.

## `monitorKey` (type: `string`):

The name of the memory that Only new reviews uses. Runs with the same key share one memory; give each separate feed (a client, a Slack channel, a test) its own key so they do not hide reviews from each other. Letters, digits and hyphens.

## `onlyWithText` (type: `boolean`):

Skip star-only ratings. Roughly a third of Google reviews carry no text; with this on you read past them for free and pay only for reviews that say something — what sentiment analysis and AI summaries need.

## `minStars` (type: `integer`):

Keep reviews with at least this many stars. 0 = no lower bound.

## `maxStars` (type: `integer`):

Keep reviews with at most this many stars, e.g. 2 for complaints only. 0 = no upper bound.

## `keywords` (type: `array`):

Keep a review only if its text, or Google's translation of it, contains at least one of these words or phrases. Case-insensitive.

## `excludeKeywords` (type: `array`):

Drop a review if its text or translation contains any of these words or phrases.

## `reviewLanguages` (type: `array`):

Keep only reviews written in these languages, as two-letter codes: en, de, fr, es, ja … Star-only ratings have no language and are dropped when this is set.

## `hasOwnerReply` (type: `string`):

"Still waiting for a reply" is the to-do list of a reputation manager; "replied" is how you study a competitor's tone.

## `hasPhotos` (type: `boolean`):

Keep only reviews that carry at least one photo.

## `translate` (type: `string`):

Google translates every review that is not in your Language. Original leaves text as written; Translation puts the translation in text; Both fills text and text\_translated. The owner's reply follows the same choice.

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

The interface language, as a code: en, de, fr, es, pt-BR, ja … It sets the language of translations, of place categories and of Google's relative dates (published\_text). Exact dates do not depend on it.

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

The two-letter country Google is asked from (gl), e.g. us, gb, de. Rarely matters; change it if a place name resolves to the wrong country.

## `includePlaceRow` (type: `boolean`):

One free row per place: rating, the total Google lists, the per-star histogram, how many reviews this run read and delivered, the completeness verdict and the 90-day rating trend.

## `sessions` (type: `integer`):

How many proxy sessions (IPs) work at once. Places are read in parallel; pages of one place are read in order.

## `perIp` (type: `string`):

Pacing for each session, e.g. 1 or 0.5. Lower is gentler on Google.

## `proxyConfiguration` (type: `object`):

Datacenter proxies are enough and keep the run cheap. The actor rotates the IP whenever Google answers an empty or cut-down page.

## Actor input object example

```json
{
  "places": [
    "https://www.google.com/maps/place/?q=place_id:ChIJD3uTd9hx5kcR1IQvGfr8dbk"
  ],
  "startUrls": [],
  "placeIds": [],
  "maxReviewsPerPlace": 20,
  "sort": "newest",
  "fromDate": "",
  "toDate": "",
  "onlyNew": false,
  "includeEdited": false,
  "monitorKey": "default",
  "onlyWithText": false,
  "minStars": 0,
  "maxStars": 0,
  "keywords": [],
  "excludeKeywords": [],
  "reviewLanguages": [],
  "hasOwnerReply": "any",
  "hasPhotos": false,
  "translate": "original",
  "language": "en",
  "country": "us",
  "includePlaceRow": true,
  "sessions": 4,
  "perIp": "1",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

One row per review, plus one place row and one status row per place. Review rows are billed as delivered; place and status rows are always free.

## `summary` (type: `string`):

One JSON record with the counts this run delivered and charged, its filtered, already-seen, duplicate and error tallies, and its HTTP stats.

# 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 = {
    "places": [
        "https://www.google.com/maps/place/?q=place_id:ChIJD3uTd9hx5kcR1IQvGfr8dbk"
    ],
    "maxReviewsPerPlace": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("kestrel/google-maps-reviews-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 = {
    "places": ["https://www.google.com/maps/place/?q=place_id:ChIJD3uTd9hx5kcR1IQvGfr8dbk"],
    "maxReviewsPerPlace": 20,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("kestrel/google-maps-reviews-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 '{
  "places": [
    "https://www.google.com/maps/place/?q=place_id:ChIJD3uTd9hx5kcR1IQvGfr8dbk"
  ],
  "maxReviewsPerPlace": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call kestrel/google-maps-reviews-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kestrel/google-maps-reviews-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/Wir7nM69hOYUbfuYu/builds/PcC751ftlUzBhikaT/openapi.json
