# App Store & Google Play Reviews and Ratings Tracker (`nat-labs/app-store-reviews-scraper`) Actor

App Store + Google Play in one run: ratings, 1-5★ histogram, app details and the reviews each store shows, per country (all 175). Monitoring mode: only new reviews and rating changes. One stable schema, dedup, GDPR pseudonyms. Never pay for empty results.

- **URL**: https://apify.com/nat-labs/app-store-reviews-scraper.md
- **Developed by:** [Nat | Data Tools](https://apify.com/nat-labs) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 app snapshots

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?

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 and Ratings Tracker

Track apps on **both stores in one run**: average rating, rating count, the **1–5★ histogram per country**, app details (version, last update, installs, price, genre, developer) and the **reviews each store shows**, with developer replies and app versions. Works for all 175 App Store countries and every Google Play country, each with its own data. Schedule it in **monitoring mode** to get only new reviews and rating changes, such as "+38 new 1★ ratings in Germany since yesterday".

**You never pay for a failed, empty or duplicate result.** No login, no browser, no proxy to set up.

### Why this Actor

| | This Actor | Typical App Store / Google Play review actors |
|---|---|---|
| Stores | **App Store and Google Play in one run, one schema** | One store per actor, different field layouts |
| Countries | **Many countries per run**, each with its own rating, histogram and reviews; `top` (40 markets) or `all` (175) | One country per run; users report getting US or Snapchat data when they asked for Nigeria |
| Google Play language | Defaults to **each country's language** (DE → de, BR → pt-BR, NG → en), or any list you give | One fixed language for every country |
| Ratings | **Rating, count and 1–5★ histogram per country**, plus a free summary across countries | Reviews only; "overall rating" is a common feature request |
| Monitoring | **Only new reviews**, and app rows only when ratings move, with **per-star deltas** | Re-download everything each run |
| Duplicates | Removed across countries and languages **before billing** | Billed again per country |
| Schema | **Flat and stable.** Every key on every row, same keys for both stores | "Output JSON schema changes between runs (nested original.\* vs flat)" |
| Privacy | Reviewer names **pseudonymised by default** (GDPR), or kept or removed | Names and avatars always included |
| Empty / blocked runs | Fail with a clear reason, **not charged**. A per-section `dataStatus` explains every empty list. | "Only ever get 10 back", with no reason given |
| Field names | Same as thewolves' scrapers: `id`, `score`, `title`, `text`, `date`, `userName`, `version`, `thumbsUp`, `replyText`, `replyDate`, `criterias`, `country`, `appId`, `language` | — |

### What it is, and what it is not

This Actor reads only what the **public store pages** show, and only through the endpoints both stores allow in their `robots.txt`. That gives you, **per app and country**:

| | App Store | Google Play |
|---|---|---|
| Rating, rating count, 1–5★ histogram | ✅ per country | ✅ per country (Google shows one global rating count with a country-specific average and distribution) |
| App details | ✅ version, release notes, size, min iOS, price, genres, developer, content rating | ✅ installs, version, last update, price, in-app purchases, genre, developer, content rating, review count |
| Reviews | up to **18 per country** (10 featured + 8 most helpful) with developer replies | **20 per country and language** (Google's "most relevant") with developer replies, app version, thumbs-up and feature ratings |

It is **not a bulk review exporter**. Apple's customer-reviews RSS feed (up to 500 per country) and Google Play's review-paging RPC are what bulk scrapers use, and both are disallowed by the stores' robots.txt files, so this Actor does not use them. To build a larger review history, schedule the Actor daily with **Only new** on. It then collects the new reviews that appear on the store pages, in every country and language you track.

### Use cases

- **App teams**: a daily rating and review digest for your own apps in every market, with alerts when 1★ ratings spike after a release.
- **Competitor tracking**: compare ratings, histograms, versions and release cadence across countries.
- **ASO and localisation**: see where an app is rated lowest, and read what users there say in their own language.
- **Sentiment and AI**: feed flat, deduplicated review rows to an LLM or a sentiment model. Each row has `score`, `text`, `language`, `country` and `version`.
- **AI agents**: call it from Claude, ChatGPT or Cursor through Apify's AI integrations. Inputs like `"Nigeria"`, `"30d"` or a pasted store link all work.
- **Your own stack**: run it from the Apify API, the Python or JavaScript client, Make, Zapier, n8n, Google Sheets, Slack, webhooks or a schedule; export JSON, CSV or Excel.

### Quick start

#### 1. Both stores, several countries

```json
{
    "apps": ["https://apps.apple.com/us/app/instagram/id389801252", "com.whatsapp"],
    "countries": ["us", "gb", "de", "ng"]
}
```

→ 8 app rows (2 apps × 4 countries), up to ~150 reviews, and 2 free summary rows.

#### 2. Ratings in every market, no reviews

```json
{ "apps": ["570060128", "com.duolingo"], "countries": ["all"], "includeReviews": false, "includeDescription": false }
```

#### 3. Daily monitoring (only new)

```json
{
    "apps": ["https://apps.apple.com/us/app/your-app/id123456789", "com.your.app"],
    "countries": ["top"],
    "onlyNew": true,
    "monitorName": "my-apps"
}
```

Schedule it daily and connect Slack, email or a webhook. The first run is the baseline. After that you get only new reviews, plus app rows whose rating, count, histogram or version changed, with a `changes` object:

```json
"changes": {
    "isFirstCheck": false,
    "previousCheckAt": "2026-10-16T06:00:04.112Z",
    "ratingAverageDelta": -0.0012,
    "ratingCountDelta": 412,
    "newRatingsByStar": { "1": 38, "2": 9, "3": 14, "4": 41, "5": 310 },
    "versionChanged": true,
    "previousVersion": "9.1.88"
}
```

#### 4. Complaints only, last 30 days, in English and Spanish

```json
{ "apps": ["com.spotify.music"], "countries": ["us", "mx"], "languages": ["en", "es"], "ratings": [1, 2], "since": "30d" }
```

#### Migrating from other actors

Paste your existing input. `appIds`, `startUrls`, `country`, `language`, `sort`, `maxItems` (thewolves), `appIdOrUrl`, `rating`, `ratingFilter`, `recentDays`, `endDate`, `keywords`, `appVersion`, `maxReviews` (neatrat) and `product_ids`, `max_reviews` (johnvc) all work. Review rows keep the familiar field names.

### Output

Every row has a `type`: `app`, `review` or `summary`. The dataset has three views: **Reviews**, **Apps by country** and **Summary**.

#### Review row

```json
{
    "type": "review",
    "store": "googlePlay",
    "appId": "com.whatsapp",
    "appTitle": "WhatsApp Messenger",
    "country": "de",
    "language": "de",
    "id": "3b7d8e89-30c5-4e13-aabd-7c638e4f70bd",
    "reviewKey": "googlePlay:com.whatsapp:3b7d8e89-30c5-4e13-aabd-7c638e4f70bd",
    "score": 4,
    "title": null,
    "text": "An sich eine gute App. Allerdings habe ich seit mindestens einem Jahr den Bug, …",
    "textLength": 512,
    "date": "2026-09-18T08:31:13.000Z",
    "userName": "user_2cfce797185c",
    "userImage": null,
    "version": "2.26.36.74",
    "thumbsUp": 9,
    "hasReply": false,
    "replyText": null,
    "replyDate": null,
    "criterias": [{ "criteria": "vaf_phase1_voice_messaging", "rating": 1 }],
    "source": "storePage",
    "url": "https://play.google.com/store/apps/details?id=com.whatsapp&reviewId=3b7d8e89-…",
    "scrapedAt": "2026-10-17T09:48:44.855Z"
}
```

App Store reviews have the same keys. `title` is filled in, `version`, `thumbsUp` and `criterias` are empty because the App Store page does not show them, and `source` is `featured` or `mostHelpful`. The App Store has no per-review links, so `url` points to the app's Ratings & Reviews page.

#### App row (one app in one country)

```json
{
    "type": "app",
    "store": "appStore",
    "appId": "389801252",
    "bundleId": "com.burbn.instagram",
    "country": "gb",
    "title": "Instagram",
    "developer": "Instagram, Inc.",
    "url": "https://apps.apple.com/gb/app/instagram/id389801252",
    "genre": "Photo & Video",
    "price": 0,
    "currency": "GBP",
    "version": "451.0.0",
    "updatedAt": "2026-10-12T18:25:41Z",
    "ratingAverage": 4.7005,
    "ratingCount": 4221175,
    "ratingCountScope": "country",
    "histogram": { "1": 132403, "2": 38861, "3": 124273, "4": 369586, "5": 3556052 },
    "histogramPercent": { "1": 3.1, "2": 0.9, "3": 2.9, "4": 8.8, "5": 84.2 },
    "reviewsDelivered": 18,
    "dataStatus": { "details": "ok", "histogram": "ok", "reviews": "ok" },
    "changes": null
}
```

The full row also carries `installs`, `minInstalls`, `reviewCount`, `releaseNotes`, `releasedAt`, `contentRating`, `sizeBytes`, `minOsVersion`, `inAppPurchases`, `description`, `screenshots` and more. Keys a store does not have are `null`. `dataStatus` is `ok`, `empty`, `partial`, `failed`, `unavailable` or `not-requested` for each section.

#### Summary row (one app across countries, free)

`ratingAverage` (App Store: weighted by each country's ratings; Google Play: the mean of the country ratings), `ratingCount`, `ratingCountScope`, the summed `histogram`, `bestCountry`, `worstCountry` and a `perCountry` table.

### Pricing

Pay per event. **Platform usage and proxies are included.** There is no start fee and no compute bill on top.

| Event | What it is | Price |
|---|---|---|
| App snapshot | One app in one country: details + rating + 1–5★ histogram | **$2.00 / 1,000** ($0.002) |
| Review | One unique review delivered (after deduplication and your filters) | **$0.10 / 1,000** |
| Monitor check | Monitoring: the app was checked in that country and nothing changed | $0.50 / 1,000 |

Paid Apify plans get lower rates automatically: snapshots go down to $1.00 / 1,000 and monitor checks to $0.30 / 1,000. **You are never charged for** apps that do not exist in a country, failed or blocked pages, duplicate reviews, reviews removed by your filters, or summary rows.

Examples: 2 apps × 4 countries with ~150 reviews ≈ **$0.03**. One app in all 40 top markets with ~500 reviews ≈ **$0.13**. Daily monitoring of 5 apps in 10 countries is about 1,500 checks a month, roughly **$3–4 a month** including new reviews, even if every check finds a change.

Before each check the Actor makes sure it fits your run's **maximum cost**, and it stops cleanly when the limit is reached.

**Free Apify plan:** up to 10 app × country checks and 200 reviews per run. This limit is set by the developer, not by Apify. Any paid plan removes it.

### Good to know

- **Review volume is capped by the stores' public pages.** You get up to 18 reviews per country on the App Store and 20 per country and language on Google Play. More appear over time, which is what monitoring mode collects.
- **Search by app name isn't supported.** Both stores disallow automated search in robots.txt. Paste the store link instead, or an Apple id or Android package name. Apple bundle ids work too, with the prefix `apple:com.example.app`.
- **Google Play rating counts are global.** Google shows the same total on every country page but a country-specific average and distribution. Per-star deltas for Google Play are therefore country-weighted estimates. App Store counts are true per-country counts.
- **The App Store doesn't show the app version or helpful votes for each review.** Those fields stay empty for App Store reviews.
- **Reviewer names** are pseudonymised by default: `user_` plus a stable code, the same for the same reviewer across your runs. Set `reviewerNames` to `include` for display names and avatars, or to `omit`.
- **Proxies**: the Actor uses Apify datacenter proxies, included in the price. It rotates IPs and backs off when a store rate-limits it. A page that still fails is listed in the run's `STATISTICS` record with the reason, and isn't charged.

### Is it legal to scrape App Store and Google Play reviews?

This Actor reads public pages that anyone can open without signing in, and only the paths the stores' robots.txt files allow. It does not log in, use accounts or bypass any protection, it paces its requests, and it pseudonymises reviewer names by default. Reviews contain personal data such as display names and free text, so make sure you have a lawful basis under GDPR or similar laws for how you use them. Check Apple's and Google's terms for your use case, and talk to a lawyer before redistributing the data commercially.

### FAQ

**Why do I get fewer reviews than with other scrapers?** Other scrapers use Apple's RSS feed and Google's internal paging RPC, which the stores' robots.txt files disallow. This Actor stays on the allowed public pages. For volume over time, run it on a schedule with `onlyNew`.

**Can I track my own app's reviews every day?** Yes. That is what monitoring mode is for: new reviews plus rating and histogram changes per country, with no duplicates across runs.

**Does it work from Claude, ChatGPT or Cursor?** Yes, through Apify's AI integrations. You can type countries as `"Nigeria"` or `"top"` and dates as `"30d"`.

**Which Google Play language do I get?** By default, each country's main language. Set `languages` to collect reviews in other languages too. Each language is one more page per country, and its reviews are deduplicated against the others.

**What happens when a store changes its page?** Each run reports a status for every section and fails loudly instead of returning silent empties, so changes are noticed and fixed quickly. Report problems in the Issues tab.

# Actor input Schema

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

Mix both stores freely: App Store links (<code>https://apps.apple.com/us/app/instagram/id389801252</code>), Apple ids (<code>389801252</code>, <code>id389801252</code>), Google Play links (<code>https://play.google.com/store/apps/details?id=com.whatsapp</code>) or Android package names (<code>com.whatsapp</code>). Apple bundle ids work with a prefix: <code>apple:com.burbn.instagram</code>. Searching by app name is not possible — both stores disallow automated search.

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

One app row per app per country, each with that country's own rating, histogram and reviews. Codes (<code>us</code>, <code>NG</code>), names (<code>Germany</code>), <code>top</code> (40 largest app markets) or <code>all</code> (all 175 App Store countries). Empty = the country in the app link, else <code>us</code>.

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

Google Play only shows reviews in the page language. Leave empty to use each country's main language automatically (Germany → de, Brazil → pt-BR, Nigeria → en). Add several (e.g. <code>en</code>, <code>es</code>) to collect each language's reviews. The App Store ignores this.

## `includeReviews` (type: `boolean`):

Deliver the reviews each store page shows: up to 18 per country on the App Store (10 featured + 8 most helpful), 20 per country and language on Google Play, with developer replies, app version and thumbs-up where the store has them. Turn off for ratings/histogram tracking only.

## `includeAppDetails` (type: `boolean`):

One row per app per country: average rating, rating count, 1–5★ histogram, review count (Google Play), installs, price, version, last update, genre, developer.

## `includeDescription` (type: `boolean`):

Add the store description and screenshot links to each app row. Turn off for slimmer CSV/Excel exports.

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

When an app is checked in 2+ countries, add one free summary row: global rating weighted by each country's ratings, summed histogram, best and worst country.

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

For scheduled runs: deliver only reviews no earlier run delivered, and app rows only when the rating, rating count, histogram or version changed — with per-star deltas (e.g. 12 new 1★ ratings in Germany). The first run is the baseline. Unchanged apps cost a cheap <i>monitor check</i>.

## `monitorName` (type: `string`):

Separate memory per schedule. Use a different name for each watch list (e.g. <code>my-apps</code>, <code>competitors</code>).

## `ratings` (type: `array`):

Keep only reviews with these stars, e.g. <code>\[1, 2]</code> for complaints. Empty = all.

## `since` (type: `string`):

Keep reviews on or after this date: <code>2026-01-01</code> or a window like <code>30d</code>, <code>2w</code>, <code>6m</code>.

## `until` (type: `string`):

Keep reviews up to and including this date (<code>2026-06-30</code>).

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

Keep reviews whose title or text contains any of these words (case-insensitive), e.g. <code>crash</code>, <code>login</code>, <code>refund</code>.

## `appVersions` (type: `array`):

Keep reviews written on these app versions (prefix match: <code>2.26</code> matches 2.26.33.74). Google Play only — the App Store page does not show the version.

## `onlyWithReply` (type: `boolean`):

Keep only reviews the developer answered.

## `sortBy` (type: `string`):

Order of reviews within each app and country.

## `maxReviews` (type: `integer`):

Stop delivering reviews after this many in total. 0 = no limit.

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

Per app, across all its countries and languages. 0 = no limit.

## `reviewerNames` (type: `string`):

Reviewer names are personal data. <b>Pseudonymize</b> (default) replaces each name with a stable code like <code>user_3f9a…</code> — the same reviewer gets the same code across runs and apps, so analysis still works — and drops avatars. <b>Include</b> keeps the public display name and avatar; <b>Omit</b> removes both.

## `appleReviewSources` (type: `array`):

<code>featured</code> = the Ratings & Reviews page (10 reviews, also gives the histogram); <code>mostHelpful</code> = the product page (8 most helpful reviews, one more request).

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

App × country checks run in parallel, each on its own proxy IP.

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

Default: Apify datacenter proxy, included in the price. You can bring your own proxy URLs. Residential proxy is not needed for these public pages and is not used.

## Actor input object example

```json
{
  "apps": [
    "https://apps.apple.com/us/app/instagram/id389801252",
    "com.whatsapp"
  ],
  "countries": [
    "us",
    "gb"
  ],
  "includeReviews": true,
  "includeAppDetails": true,
  "includeDescription": true,
  "includeSummary": true,
  "onlyNew": false,
  "monitorName": "default",
  "onlyWithReply": false,
  "sortBy": "newest",
  "maxReviews": 0,
  "maxReviewsPerApp": 0,
  "reviewerNames": "pseudonymize",
  "appleReviewSources": [
    "featured",
    "mostHelpful"
  ],
  "maxConcurrency": 4,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

App snapshots per country, reviews and per-app summaries.

## `statistics` (type: `string`):

Per-run counters, failed apps with reasons, proxy traffic.

# 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/instagram/id389801252",
        "com.whatsapp"
    ],
    "countries": [
        "us",
        "gb"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("nat-labs/app-store-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 = {
    "apps": [
        "https://apps.apple.com/us/app/instagram/id389801252",
        "com.whatsapp",
    ],
    "countries": [
        "us",
        "gb",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("nat-labs/app-store-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 '{
  "apps": [
    "https://apps.apple.com/us/app/instagram/id389801252",
    "com.whatsapp"
  ],
  "countries": [
    "us",
    "gb"
  ]
}' |
apify call nat-labs/app-store-reviews-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nat-labs/app-store-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/etWENsOGaySwPbTNP/builds/SoMaFr5rQddGHp5nJ/openapi.json
