# App Store & Google Play Reviews Scraper, $0.10/1K (`zovo-one/app-store-google-play-reviews`) Actor

Scrape App Store and Google Play reviews in one run with one schema. Each row has rating, title, text, date, app version, thumbs-up and developer reply, for many apps and countries. Filter by date and rating, and add an optional per-app summary with a rating histogram and top keywords.

- **URL**: https://apify.com/zovo-one/app-store-google-play-reviews.md
- **Developed by:** [Michael Lip](https://apify.com/zovo-one) (community)
- **Categories:** Developer tools, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.10 / 1,000 reviews

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

## App Store & Google Play Reviews Scraper

Get App Store and Google Play reviews in one run and one table, at $0.10 per 1,000 reviews.

List your apps from either store, pick countries, and every review comes back with the same fields. That means rating, title, text, date, app version, helpful votes and the developer's reply. You don't need a login, an API key or a browser. In a run on 2026-11-05 the Actor saved 200 Spotify reviews (100 from each store) in 3.2 seconds.

People use it to watch their own app on both stores with one scheduled run, to track competitors across countries, to pull 1-star reviews with the rating filter, and to feed clean JSON into sentiment analysis, LLM pipelines and dashboards.

### What you get

One row per review. Both stores use the same schema, so you can sort and filter them together.

| Field | Type | What it holds |
|---|---|---|
| `type` | string | `review`, or `summary` for the optional per-app summary row |
| `store` | string | `appstore` or `googleplay` |
| `appId` | string | App Store numeric id or Google Play package name |
| `appName` | string or null | App name as the store shows it |
| `country` | string | 2-letter country code the review was read from |
| `lang` | string or null | Google Play language code the review was requested in. `null` on App Store rows, because Apple groups reviews by country |
| `reviewId` | string | Store review id. Duplicates are removed by store, app and this id |
| `rating` | integer | 1 to 5 stars |
| `title` | string or null | Review title (App Store only, Google Play reviews have no title) |
| `text` | string or null | Review text |
| `author` | string or null | Public display name, only when `includeAuthor` is on. Otherwise `null` |
| `date` | string | ISO 8601 date of the review |
| `version` | string or null | App version the review was written for (Google Play only) |
| `thumbsUp` | integer or null | Helpful votes (Google Play only) |
| `developerReply` | object or null | `{ "text", "date" }` of the developer's reply |
| `url` | string | Link to the review (Google Play) or to the app's reviews page (App Store) |

Turn on `includeSummary` and you also get one free summary row per app with `reviewCount`, `avgRating`, `ratingHistogram`, `developerReplyRate`, `oldestReviewDate`, `newestReviewDate` and the top 15 `topKeywords`.

The dataset has three views (Overview, Developer replies, App summaries). Export them as JSON, CSV, Excel, XML or RSS. The key-value store record `OUTPUT` holds a run report with reviews fetched and saved, duplicates removed, and timings and errors per task.

### Example output

These rows come from real runs on 2026-11-05 with Spotify on both stores. The first three are from one run (build 0.3.3). The summary row is from an earlier run (build 0.3.2), and its `topKeywords` list is cut to 5 of 15 entries here.

```json
[
  {
    "type": "review",
    "store": "appstore",
    "appId": "324684580",
    "appName": "Spotify: Music and Podcasts",
    "country": "us",
    "lang": null,
    "reviewId": "14746997096",
    "rating": 5,
    "title": "Staggering difference in my music quality",
    "text": "Spotify changed my life from being a decent music lover (vinyl, etc) to truly A level. The best of the best.",
    "author": null,
    "date": "2026-11-03T19:41:44.000Z",
    "version": null,
    "thumbsUp": null,
    "developerReply": null,
    "url": "https://apps.apple.com/us/app/id324684580?see-all=reviews"
  },
  {
    "type": "review",
    "store": "googleplay",
    "appId": "com.spotify.music",
    "appName": "Spotify: Music and Podcasts",
    "country": "us",
    "lang": "en",
    "reviewId": "11985860-f84b-481a-94e9-cf09908f12aa",
    "rating": 2,
    "title": null,
    "text": "to many ad and not letting us enjoy",
    "author": null,
    "date": "2026-11-04T13:13:25.622Z",
    "version": null,
    "thumbsUp": 0,
    "developerReply": {
      "text": "Hi. Thanks and we appreciate you flagging this with us. We hear you and we'll pass your feedback along to the Ads team.",
      "date": "2026-11-04T16:19:32.288Z"
    },
    "url": "https://play.google.com/store/apps/details?id=com.spotify.music&reviewId=11985860-f84b-481a-94e9-cf09908f12aa"
  },
  {
    "type": "review",
    "store": "googleplay",
    "appId": "com.spotify.music",
    "appName": "Spotify: Music and Podcasts",
    "country": "us",
    "lang": "en",
    "reviewId": "1196be45-7791-40cd-94dc-54b50ef84401",
    "rating": 5,
    "title": null,
    "text": "very good",
    "author": null,
    "date": "2026-11-04T18:44:02.222Z",
    "version": "9.1.94.1929",
    "thumbsUp": 0,
    "developerReply": null,
    "url": "https://play.google.com/store/apps/details?id=com.spotify.music&reviewId=1196be45-7791-40cd-94dc-54b50ef84401"
  },
  {
    "type": "summary",
    "store": "appstore",
    "appId": "324684580",
    "appName": "Spotify: Music and Podcasts",
    "countries": ["us"],
    "reviewCount": 20,
    "avgRating": 3.9,
    "ratingHistogram": { "1": 3, "2": 2, "3": 2, "4": 0, "5": 13 },
    "developerReplyRate": 0,
    "oldestReviewDate": "2026-11-03T18:33:28.000Z",
    "newestReviewDate": "2026-11-03T19:41:44.000Z",
    "topKeywords": [
      { "keyword": "music", "count": 8 },
      { "keyword": "spotify", "count": 5 },
      { "keyword": "ads", "count": 3 },
      { "keyword": "better", "count": 3 },
      { "keyword": "love", "count": 3 }
    ]
  }
]
```

### Pricing

You pay per review saved. There is no start fee and no monthly fee.

| Event | Price | When it's charged |
|---|---|---|
| Review | $0.0001 per review ($0.10 per 1,000) | Once per review row saved to the dataset, after the rating and date filters and duplicate removal |

What runs cost.

| Run | Reviews saved | Cost |
|---|---|---|
| The prefilled first run (2 apps, US, 50 each) | 100 | $0.01 |
| 1,000 reviews of one app | 1,000 | $0.10 |
| 5 apps x 4 countries x 500 reviews | up to 10,000 | up to $1.00 |
| Daily monitor, 1 app, `sinceDate: "3 days"`, 30 runs that save about 90 reviews each (about 30 new reviews a day, each one saved in 3 runs) | about 2,700 | about $0.27 a month |

Summary rows are free. Apps that aren't found and reviews dropped by your filters or as duplicates save no review rows, so they cost nothing. A task that fails before its first page also costs nothing. If it fails later, the reviews it saved before the error are kept and charged. You can set a maximum cost per run in the run options (`maxTotalChargeUsd` in the API). When the budget runs out, the Actor stops saving, keeps what it already saved, and says so in the status message.

### Quick start

1. Open the Input tab. Two Spotify apps (one per store) and the US are already filled in.
2. Click Start. The run takes a few seconds and costs about $0.01.
3. Open the Output tab, or export the dataset as CSV or Excel.

Then replace the apps with your own. Paste store URLs, App Store ids or Google Play package names, one per line.

### Use it from the API

Run the Actor and get the reviews back in one HTTP call.

```bash
curl -X POST "https://api.apify.com/v2/acts/zovo-one~app-store-google-play-reviews/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "apps": ["324684580", "com.spotify.music"],
    "countries": ["us"],
    "maxReviewsPerApp": 100,
    "sort": "newest",
    "maxRating": 2
  }'
```

The sync endpoint waits up to 300 seconds. For bigger runs, start the run with `/runs` and read the dataset when it finishes.

### Use it from Python

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("zovo-one/app-store-google-play-reviews").call(run_input={
    "apps": [
        "https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580",
        "com.spotify.music",
    ],
    "countries": ["us", "gb"],
    "maxReviewsPerApp": 200,
    "sinceDate": "30 days",
    "includeSummary": True,
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item["type"] == "review":
        print(item["store"], item["rating"], item["text"])
```

Install the client with `pip install apify-client`.

### Use it from Node.js

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('zovo-one/app-store-google-play-reviews').call({
    apps: ['324684580', 'com.spotify.music'],
    countries: ['us'],
    maxReviewsPerApp: 100,
    minRating: 1,
    maxRating: 1,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(`${items.length} one-star reviews`);
```

Install the client with `npm install apify-client`.

### Use it from AI agents (MCP)

The Apify MCP server at mcp.apify.com exposes this Actor as a tool to Claude, Cursor, VS Code and other MCP clients. Each client adds servers in its own way. VS Code and Claude Desktop, for example, don't take the JSON below as written. For your client, open the [MCP configurator](https://mcp.apify.com), pick this Actor and copy the setup it gives you. A client that reads an `mcpServers` block with a `url` key, such as Cursor, can use this config.

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=zovo-one/app-store-google-play-reviews"
    }
  }
}
```

On first connect your browser opens so you can sign in to Apify. You can also skip OAuth and send your token as an `Authorization: Bearer <APIFY_TOKEN>` header. Without the `tools` parameter, an agent can still find the Actor with `search-actors`, run it with `call-actor`, and read the rows with `get-dataset-items`.

An example prompt.

```text
Get the 200 newest 1 and 2 star reviews of Spotify from the US App Store and Google Play,
then group the complaints into themes and count them.
```

### Input

The Console form explains every field. These are the ones you'll touch most.

| Field | Default | What it does |
|---|---|---|
| `apps` | required | App Store URL, numeric id or `id324684580`, Google Play URL or package name. Mix both stores |
| `countries` | URL country, else `us` | 2-letter codes, up to 50. `uk` is read as `gb` |
| `languages` | `["en"]` | Google Play languages, up to 20 (`en`, `de`, `pt-br`, `es-419`). The App Store ignores it |
| `maxReviewsPerApp` | 100 | Max reviews saved per app and country (and language on Google Play), up to 20,000 |
| `sort` | `newest` | `newest` or `helpful` |
| `sinceDate` | none | `2026-10-01`, an ISO timestamp with a time zone, or `7 days`, `2 weeks`, `3 months` |
| `minRating`, `maxRating` | 1, 5 | Star filter |
| `includeSummary` | false | One free summary row per app |
| `includeAuthor` | false | Adds the reviewer's public display name |
| `maxConcurrency` | 3 | App and country tasks in parallel, 1 to 4 |

### Limits

Per run you can ask for up to 200 apps, 50 countries and 20 languages, and at most 2,000 app x country (x language on Google Play) tasks. `maxReviewsPerApp` goes up to 20,000.

- The App Store API returns 20 reviews per request and the Actor pages up to offset 20,000. In our tests that ran at about 1,000 to 3,000 reviews a minute per task, so 20,000 App Store reviews for one app and country take about 7 to 20 minutes. For a large app (Facebook, US) we paged past 10,000 reviews, about 2.5 months back. Google Play returns up to 150 reviews per request.
- The stores can't filter by stars. With a rating filter, each task reads at most 10 × `maxReviewsPerApp` reviews (never more than 20,000) and drops the rest. If the rating you want is rare, a task can stop there with fewer reviews saved, and `OUTPUT` shows `readLimitReached: true` for it. In one test (3-star only, max 200) each task read 2,000 and saved 93 on Google Play and 140 on the App Store.
- Big runs need a longer timeout. When the run timeout is near, the Actor stops reading, writes the summaries and `OUTPUT`, and keeps everything saved so far.
- Each request has a 30 second timeout and up to 4 retries with backoff on HTTP 429 and 5xx. Google Play calls start at least 250 ms apart. The Actor has no proxy option.

If every task of one store fails for a reason other than "app not found", the run ends as failed with the first error in the status message, and the other store's reviews stay in the dataset. Neither store publishes star-only ratings (no text) as reviews, so they aren't included. The stores can also change their public endpoints without notice. If a run breaks, open an issue on the Issues tab.

### FAQ

#### A second country adds few Google Play reviews

Google Play shows mostly the same reviews in every country for one language. The Actor removes duplicates, so you don't pay twice, and `country` is the first country where the review turned up. For more distinct Google Play reviews, add languages (`["en", "de", "fr"]`) instead of countries. App Store reviews are different in each country.

#### How do I collect only new reviews every day?

Both stores show new reviews 1 to 2 days late, so `sinceDate: "1 day"` usually returns nothing. Schedule a daily run with `sort: "newest"` and `sinceDate: "3 days"`, then drop `reviewId` values you already have. Reading stops as soon as older reviews start, so the run is short. Each review can show up in up to three daily runs and is charged each time it's saved, because the Actor doesn't remember reviews between runs.

#### Empty `version` and `thumbsUp` on App Store rows

Apple's public review data has no app version and no helpful votes. Google Play has both.

#### App Store apps by name

Not yet. Use the apps.apple.com URL or the numeric id. For Google Play, use the URL or the package name (`com.example.app`).

#### Restarts and migrations

The Actor rebuilds its state from the dataset. Reviews already saved aren't saved or charged again.

#### Legal use

The Actor reads only reviews that anyone can see on apps.apple.com and play.google.com without logging in. It doesn't log in or collect private data. Reviewer names are left out unless you turn on `includeAuthor`, and profile pictures and user ids are never collected. You're responsible for using the data in line with the stores' terms, copyright and data protection law such as the GDPR.

### Changelog

- 0.3. Stricter input checks, a clear "no storefront" error for App Store countries without one, a stop shortly before the run timeout that still writes summaries and `OUTPUT`, and read-limit flags for rating filters.
- 0.2. No hangs after Google Play errors, store failures reported instead of empty results, restart-safe charging, and the app URL's country used when `countries` is empty.
- 0.1. First release with both stores in one run, one schema, developer replies, date and rating filters, duplicate removal and summary rows.

# Actor input Schema

## `apps` (type: `array`):

Apps to read, one per line. Mix App Store and Google Play freely. App Store: app URL (https://apps.apple.com/us/app/name/id324684580), numeric id (324684580) or id324684580. Google Play: app URL (https://play.google.com/store/apps/details?id=com.spotify.music) or package name (com.spotify.music). App names are not accepted. Up to 200 apps per run.

## `countries` (type: `array`):

2-letter ISO country codes such as us, gb, de, fr, jp (up to 50). Leave empty to read each app in the country of its URL (apps.apple.com/de/... or play.google.com/...\&gl=DE), or in "us" when the URL has no country. App Store reviews differ per country. Google Play mostly shows the same reviews in every country for one language; duplicates are removed and not charged, so add languages instead of countries for more Google Play reviews. "uk" is read as "gb". A country without an App Store storefront (e.g. "aq") is reported as "no storefront" for App Store apps.

## `languages` (type: `array`):

Google Play language codes such as en, de, fr, es, ja, pt-br, en-gb, es-419, zh-tw (up to 20). Each language is read separately, so more languages give more distinct Google Play reviews. Unknown codes such as "zz" are rejected, because Google Play would answer them with reviews in another language. The App Store ignores this field (its reviews are grouped by country), so App Store rows have lang = null.

## `maxReviewsPerApp` (type: `integer`):

Maximum reviews saved for each app in each country (and each language on Google Play), 1 to 20,000. You pay $0.0001 per saved review, so 1,000 reviews cost $0.10. The App Store returns 20 reviews per request (about 1,000-3,000 per minute in our tests), so 20,000 App Store reviews take about 7-20 minutes per app and country: raise "Timeout" in the run options for big runs. If the timeout gets near, the run stops reading and still writes summaries and the OUTPUT report. With a rating filter, each task reads at most 10 x this value (never more than 20,000 reviews), so it can save fewer when the rating is rare; OUTPUT then shows readLimitReached.

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

Newest first (best for monitoring and with "Only reviews since", because reading stops at that date) or most helpful first.

## `sinceDate` (type: `string`):

Only save reviews posted on or after this date. Use YYYY-MM-DD (UTC), a full ISO timestamp with time zone (2026-10-01T08:00:00Z) or a relative value such as "7 days", "2 weeks" or "3 months" (at most 5 digits). Leave empty for no date limit. With sort = newest, the run stops reading as soon as it passes this date. Both stores show new reviews 1-2 days late, so "1 day" usually returns nothing; for daily monitoring use "3 days" and drop already-seen reviewId values on your side.

## `minRating` (type: `integer`):

Only save reviews with at least this many stars (1-5). Set min = max = 1 to collect only 1-star reviews. The stores cannot filter by stars, so the run reads and drops other reviews: each task reads at most 10 x "Max reviews per app and country" (never more than 20,000). Dropped reviews are not charged.

## `maxRating` (type: `integer`):

Only save reviews with at most this many stars (1-5). Set it to 2 to get only 1- and 2-star complaints.

## `includeSummary` (type: `boolean`):

Adds one row per app with type = "summary": review count, average rating, rating histogram (1-5 stars), developer reply rate, oldest and newest review date, and the top 15 keywords of the saved reviews. Summary rows are free.

## `includeAuthor` (type: `boolean`):

Adds the reviewer's public display name in the author field. Off by default to keep personal data out of your dataset. Profile pictures and user ids are never collected.

## `maxConcurrency` (type: `integer`):

How many app/country tasks run in parallel (1-4). Kept low to respect the stores' rate limits; the default of 3 is fine for most runs.

## Actor input object example

```json
{
  "apps": [
    "https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580",
    "https://play.google.com/store/apps/details?id=com.spotify.music"
  ],
  "countries": [
    "us"
  ],
  "languages": [
    "en"
  ],
  "maxReviewsPerApp": 50,
  "sort": "newest",
  "sinceDate": "3 days",
  "minRating": 1,
  "maxRating": 5,
  "includeSummary": false,
  "includeAuthor": false,
  "maxConcurrency": 3
}
```

# Actor output Schema

## `reviews` (type: `string`):

No description

## `allFields` (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 = {
    "apps": [
        "https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580",
        "https://play.google.com/store/apps/details?id=com.spotify.music"
    ],
    "countries": [
        "us"
    ],
    "languages": [
        "en"
    ],
    "maxReviewsPerApp": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("zovo-one/app-store-google-play-reviews").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 = {
    "apps": [
        "https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580",
        "https://play.google.com/store/apps/details?id=com.spotify.music",
    ],
    "countries": ["us"],
    "languages": ["en"],
    "maxReviewsPerApp": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("zovo-one/app-store-google-play-reviews").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 '{
  "apps": [
    "https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580",
    "https://play.google.com/store/apps/details?id=com.spotify.music"
  ],
  "countries": [
    "us"
  ],
  "languages": [
    "en"
  ],
  "maxReviewsPerApp": 50
}' |
apify call zovo-one/app-store-google-play-reviews --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,zovo-one/app-store-google-play-reviews"
        }
    }
}
```

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/diE87TcYLVfbg35DV/builds/anNMZ8TXEFJU40Ght/openapi.json
