# Google Play Reviews Scraper & App Search API (`omargnagy/google-play-reviews-scraper`) Actor

Google Play reviews and app search with a live Standby HTTP API, so an AI agent can pull reviews mid-task with no cold start. Clean, LLM-ready JSON: rating, text, app version, thumbs up, date, developer reply. Any country and language. No start fee, pay only for results.

- **URL**: https://apify.com/omargnagy/google-play-reviews-scraper.md
- **Developed by:** [Omar Nagy](https://apify.com/omargnagy) (community)
- **Categories:** E-commerce, Developer tools, AI
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.10 / 1,000 reviews

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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 Play Reviews Scraper & App Search API

Pull **Google Play app reviews** and **search the Play Store** at speed, with clean, LLM-ready JSON. Built for AI agents, review-monitoring pipelines, and product teams who need real user feedback in a structured shape.

Two things make this one different: it runs in **Standby mode**, so an agent can call a live HTTP endpoint mid-task with no cold start, and every field is flat and predictable, so you can feed the output straight into an LLM, a sentiment model, or a database without a cleanup step.

### What you get

- **Reviews by app**: rating, review text, app version, thumbs-up count, timestamp, the developer's reply, and a permalink.
- **App search**: find the Android package name for any app, plus developer, rating, price, currency, and icon.
- **Full pagination**: ask for 5,000 reviews and it pages through Google for you.
- **Any storefront and language**: `country` and `lang` control which reviews you see.
- **Standby HTTP API**: a persistent endpoint your agent or app can hit directly.
- **MCP ready**: exposed as a typed tool through Apify's MCP server via the input schema.
- **Maintained upstream**: built on the community `google-play-scraper` library, so Google's internal RPC changes are handled by a shared, actively maintained project instead of one private scraper.
- **No dead columns**: every field in the output is a field Google actually serves. Nothing is advertised that always comes back empty.

### Use cases

- Monitor what users say after each release, and catch a crash wave in the reviews before support tickets arrive.
- Feed competitor reviews into an LLM for feature-gap and pain-point analysis.
- Read how a developer answers complaints, at scale, from the `replyText` field.
- Build a sentiment dashboard for your app and three rivals.
- Give an AI agent a live tool that answers "what are people complaining about in this app right now".
- Collect training or RAG data from real, public product feedback.

### Modes

| Mode | You give it | You get back |
|------|-------------|--------------|
| `search` | `query` (app name or keyword) | matching apps with their Android package names |
| `reviews` | `appIds` (package names) | reviews, paginated up to `maxItems` |

Start with `search` to get a package name, then run `reviews` on it. `reviews` and `search` are the only two accepted modes, lowercase and exact.

### Input

Find an app:

```json
{
  "mode": "search",
  "query": "notion",
  "country": "us",
  "maxItems": 5
}
```

Pull its reviews:

```json
{
  "mode": "reviews",
  "appIds": ["com.adobe.scan.android"],
  "country": "us",
  "lang": "en",
  "maxItems": 100,
  "sort": "newest"
}
```

Compare three apps in one run. `maxItems` is the budget for the whole run and is
split evenly, so this returns 30 reviews of each, not 90 of the first one:

```json
{
  "mode": "reviews",
  "appIds": ["net.booksy.business", "com.fresha.Business", "com.squareup.apos"],
  "country": "us",
  "maxItems": 90
}
```

| Field | Type | Default | Notes |
|-------|------|---------|-------|
| `mode` | `reviews` or `search` | `reviews` | required. Any other value is rejected before anything is fetched. |
| `appIds` | array of strings | | Android package names, for example `com.whatsapp`. **Required when `mode` is `reviews`.** Ignored in `search`. |
| `query` | string | | app name or keyword. **Required when `mode` is `search`.** Ignored in `reviews`. |
| `country` | 2-letter code | `us` | Play Store storefront |
| `lang` | 2-letter code | `en` | review language |
| `maxItems` | integer 1 to 5000 | `100` | budget for the whole run, **split evenly across `appIds`**. 100 across 3 apps is 34 / 33 / 33. |
| `maxItemsPerApp` | integer 1 to 5000 | | optional. When set, every app gets exactly this many and the even split is ignored, so 3 apps at 50 return up to 150 rows. |
| `sort` | `newest`, `rating`, `helpfulness` | `newest` | review order, `reviews` mode only |

A run that asks for `reviews` without `appIds`, or `search` without `query`, fails immediately with a clear input error and returns nothing, so a misconfigured run costs you nothing.

### Output

Real output from runs on 5 September 2026. Larger samples of both are in the `examples/` folder of the source.

App search:

```json
{
  "type": "app",
  "appId": "notion.id",
  "name": "Notion: Notes, Tasks, AI",
  "developer": "Notion Labs, Inc.",
  "rating": 4.5757923,
  "price": 0,
  "currency": "USD",
  "free": true,
  "url": "https://play.google.com/store/apps/details?id=notion.id",
  "icon": "https://play-lh.googleusercontent.com/-GY1Xv9hhhK_RFJCCb-rvDX8OMR8Fj68AgcRIFcD8zIXlOA8pG-L-cuOJbgSs_U6GGGyGs8qiiQsjIfEBazE8g"
}
```

A review with a developer reply:

```json
{
  "type": "review",
  "appId": "com.adobe.scan.android",
  "country": "us",
  "lang": "en",
  "reviewId": "0d06d6ed-fb52-40aa-bcca-a06628e648ab",
  "author": "Rachel Ganiyu",
  "rating": 5,
  "text": "love it",
  "version": "26.04.30",
  "thumbsUp": 0,
  "date": "2026-09-03T20:28:17.542Z",
  "replyText": "Hi there! Thank you for your kind words! We truly appreciate your support...",
  "replyDate": "2026-09-04T03:54:18.598Z",
  "url": "https://play.google.com/store/apps/details?id=com.adobe.scan.android&reviewId=0d06d6ed-fb52-40aa-bcca-a06628e648ab"
}
```

A review with no reply looks the same with `replyText` and `replyDate` set to `null`. (Reply text above is shortened for the example; the API returns it in full.)

### Use it from an agent (Standby)

Standby keeps the Actor warm and answers over plain HTTP, so there is no per-call container start.

```
GET {standbyUrl}/search?query=notion&country=us&maxItems=5
GET {standbyUrl}/reviews?appId=com.adobe.scan.android&country=us&lang=en&maxItems=50&sort=newest
```

Every successful response is:

```json
{
  "ok": true,
  "count": 30,
  "perApp": [
    { "appId": "net.booksy.business", "requested": 10, "fetched": 10, "charged": 10, "error": null },
    { "appId": "com.fresha.Business", "requested": 10, "fetched": 10, "charged": 10, "error": null },
    { "appId": "com.squareup.apos", "requested": 10, "fetched": 10, "charged": 10, "error": null }
  ],
  "items": [ ... ],
  "tookMs": 589
}
```

That is a real response to
`/reviews?appIds=net.booksy.business,com.fresha.Business,com.squareup.apos&maxItems=30`,
recorded 6 September 2026.

`perApp` is the run's own accounting: what each app was allotted, what it returned,
and what it was billed. An app id that cannot be read gets an `error` string and
`charged: 0` while the rest of the call keeps its full budget, so one bad id does
not fail the request.

`GET {standbyUrl}/` returns a small service document with the modes and the running build number.

`appId` (singular) is accepted as a convenience alongside a comma-separated `appIds`.

#### Errors are typed, so an agent knows whether to retry

| Status | When | Body |
|--------|------|------|
| `404` | the path is not `/reviews` or `/search`, for example `/foobar`, `/REVIEWS`, `/v1/reviews` | `{ "ok": false, "error": "...", "statusCode": 404, "modes": ["reviews","search"] }` |
| `400` | the path is valid but the parameters are not, for example `/reviews` with no `appId` | same shape, `statusCode: 400` |
| `500` | an upstream failure | same shape |

`404` and `400` mean "fix the call", not "try again". Neither fetches any data, so neither returns anything billable.

### Good to know

- **There is no review `title` field.** Google Play stopped serving review titles. Measured across 1,200 reviews from 8 apps on 5 September 2026: zero had one. Rather than ship a column that is always empty, the field is not in the output at all. Use `text`.
- **`replyText` and `replyDate` are real, and common on apps whose developers answer.** Same measurement: 111 of 150 on Adobe Scan, 30 of 150 on Grammarly Keyboard, 11 of 150 on Canva, 7 of 150 on Trello, and 0 on Duolingo, Uber, and Shazam, which simply do not reply. They are `null` when that particular review has no reply.
- `version` is present on most reviews but not all, because reviewers on older clients do not always report one.
- **`maxItems` is split evenly across `appIds`.** Three apps at `maxItems: 90` return 30 each, and an app that runs out early leaves its remainder unspent rather than handing it to the next app, so no app can spend another app's budget. Set `maxItemsPerApp` when you want a fixed number each regardless of how many apps you list. (Until 6 September 2026 the first app in the list took the whole budget and the rest returned nothing. That is fixed.)
- **Per-app accounting is in the run.** Every batch run writes a summary to the key-value store as `OUTPUT`, including a `mode: search` run where the per-app list is empty, and logs one `[app] <id> requested=N fetched=N charged=N` line per app. Standby answers over HTTP and does not write to the key-value store: it returns the same breakdown as `perApp` in the response body instead.
- Reviews are public data published by users on the Google Play Store. Respect Google's terms and any applicable privacy law in how you store and use it.

### Parity with the App Store Actor

There is a sibling Actor, **App Store Reviews Scraper & App Search API**, with the same
two modes, the same Standby routes, the same error shapes and the same pay-per-event
model. Input is the same except for two fields, and the review record maps field for
field with five differences. Every difference below is a difference in what the two
stores publish, not a difference in style.

Input:

| Field | Google Play | App Store |
|---|---|---|
| `mode`, `appIds`, `appId`, `query`, `country`, `maxItems`, `maxItemsPerApp` | same | same |
| review order | `sort`: `newest`, `rating`, `helpfulness` | `sortBy`: `mostRecent`, `mostHelpful` |
| review language | `lang` | not accepted, Apple serves per storefront only |

Review output:

| What it means | Google Play | App Store |
|---|---|---|
| record kind | `type` | `type` |
| app identifier | `appId` (package name, `com.whatsapp`) | `appId` (numeric, `310633997`) |
| storefront | `country` | `country` |
| review id | `reviewId` | `reviewId` |
| author display name | `author` | `author` |
| stars, 1 to 5 | `rating` | `rating` |
| review body | `text` | `text` |
| app version | `version` | `version` |
| permalink | `url` | `url` |
| when it was posted | `date` | `updatedAt` |
| review language | `lang` | not served |
| helpfulness | `thumbsUp` | `votesHelpful` and `votesTotal` |
| headline | not served | `title` |
| developer reply | `replyText`, `replyDate` | not served |

The five differences, each measured rather than assumed:

1. **Timestamp name.** Apple's customer-reviews feed names its only time field `updated`, so the App Store Actor exposes `updatedAt`. Google Play carries a review date, exposed as `date`.
2. **Language.** A live Apple feed entry carries exactly these keys: `author`, `content`, `id`, `im:contentType`, `im:rating`, `im:version`, `im:voteCount`, `im:voteSum`, `link`, `title`, `updated` (read 6 September 2026, storefront `us`, app `324684580`, 50 entries). There is no language key, so there is nothing to expose.
3. **Helpfulness.** Google Play gives one number, `thumbsUp`. Apple gives two, a sum and a count, exposed as `votesHelpful` and `votesTotal`.
4. **Headline.** Apple serves a review `title`. Google Play no longer does: zero of 1,200 reviews across 8 apps carried one on 5 September 2026, so the field is not in the Google Play output at all.
5. **Developer reply.** Google Play serves it and it is common (111 of 150 reviews on Adobe Scan, same 5 September sample). Apple's feed has no reply key at all (see the key list in point 2), so the App Store Actor has no `replyText` or `replyDate`.

One behavioural difference worth knowing when you compare depth: Google Play pages with a
continuation token until it runs out, while Apple serves up to 10 fixed pages and an
individual page can come back empty while later pages are full. Those empty pages belong
to the exact page URL and they refill within minutes, so the App Store Actor reads every
one of the 10 pages until your budget is full instead of treating a run of empty pages as
the end. A deep App Store pull can still come back shorter than the app's real review
count, when Apple is serving those pages empty for the whole run.

### Pricing

You pay for results, not for time. There is **no start fee**, and nothing is charged for a run that returns nothing.

- **review**: one review row returned, in batch mode or through Standby.
- **app**: one app record returned by a search.

A 1,000-review pull is charged as 1,000 review events. Finding an app costs a handful of app events. Current rates are on the Pricing tab of this page.

### FAQ

**How do I find an app's package name?**
Run `mode: "search"` with the app name. The `appId` in the output is the package name, for example `com.whatsapp`.

**Can I get every review an app ever received?**
Google serves a deep but not unlimited window per storefront and sort order. `sort: "newest"` with a high `maxItems` gets you the most recent history; switching `country` surfaces a different set of reviews.

**Does it need a proxy?**
No. It talks to Google's public endpoints through a maintained library.

**Can I run it on a schedule?**
Yes. Use Apify's scheduler, or call the Standby endpoint from your own job.

**Do you support the Apple App Store too?**
Not in this Actor. There is a separate App Store Reviews Actor in the same series.

***

Built by Omar Nagy. Part of an agent-native data-tool series on Apify.

# Actor input Schema

## `mode` (type: `string`):

reviews = fetch reviews for app package IDs (requires App package names below). search = find apps by keyword (requires Search query below). These are the only two accepted values, lowercase and exact.

## `appIds` (type: `array`):

Android package names, e.g. com.whatsapp. REQUIRED when mode = reviews, and a run with mode = reviews and no package name fails immediately with a 400-style input error. Ignored when mode = search. Get package names from mode = search.

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

Order for review results. Used only when mode = reviews.

## `query` (type: `string`):

App name or keyword. REQUIRED when mode = search, and a run with mode = search and no query fails immediately with a 400-style input error. Ignored when mode = reviews.

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

Two-letter Play Store country code. Applies to both modes.

## `lang` (type: `string`):

Two-letter language code for review text. Applies to both modes.

## `maxItems` (type: `integer`):

Total number of results for the whole run. With several app package names it is SPLIT EVENLY between them (100 across 3 apps is 34 / 33 / 33), so no single app can spend another app's budget. Reviews are paginated to reach each app's share.

## `maxItemsPerApp` (type: `integer`):

Optional. When set, EVERY app package name gets exactly this many reviews and the even split of Max items is ignored, so 3 apps at 50 return up to 150 rows. Leave it empty to let Max items be the total budget. Used only when mode = reviews.

## Actor input object example

```json
{
  "mode": "reviews",
  "appIds": [
    "com.whatsapp"
  ],
  "sort": "newest",
  "query": "whatsapp",
  "country": "us",
  "lang": "en",
  "maxItems": 100
}
```

# Actor output Schema

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

No description

## `summary` (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 = {
    "mode": "reviews",
    "appIds": [
        "com.whatsapp",
        "com.spotify.music"
    ],
    "sort": "newest",
    "query": "notion",
    "country": "us",
    "lang": "en",
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("omargnagy/google-play-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 = {
    "mode": "reviews",
    "appIds": [
        "com.whatsapp",
        "com.spotify.music",
    ],
    "sort": "newest",
    "query": "notion",
    "country": "us",
    "lang": "en",
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("omargnagy/google-play-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 '{
  "mode": "reviews",
  "appIds": [
    "com.whatsapp",
    "com.spotify.music"
  ],
  "sort": "newest",
  "query": "notion",
  "country": "us",
  "lang": "en",
  "maxItems": 100
}' |
apify call omargnagy/google-play-reviews-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,omargnagy/google-play-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/SLdKtupRjPelV3cHO/builds/td3EFPSkSm1D0omVo/openapi.json
