# App Store & Google Play Reviews Scraper (`cylindrical_lighthouse/app-reviews-monitor`) Actor

Scrape and monitor App Store and Google Play reviews, ratings and 1-5 star histograms for any apps and countries. Only-new-reviews mode.

- **URL**: https://apify.com/cylindrical\_lighthouse/app-reviews-monitor.md
- **Developed by:** [Lighthouse Data](https://apify.com/cylindrical_lighthouse) (community)
- **Categories:** SEO tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 app checks

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 Scraper

Get **App Store and Google Play reviews, ratings and 1–5★ rating histograms** for any list of apps and countries, in **one run and one clean table**. Turn on **monitor mode** and schedule it to get **only the reviews that are new since the last run**, plus how each app's rating changed. No login, no API keys, no browser.

### What does App Store & Google Play Reviews Scraper do?

- **Both stores, one schema.** Mix App Store IDs or URLs with Google Play package names or URLs in the same input. Every review has the same fields whichever store it came from: `rating`, `title`, `text`, `appVersion`, `reviewedAt`, `developerReplyText`, and more.
- **Deep App Store history.** Goes well past the 500-reviews-per-country limit of Apple's RSS feed. Tested at 600+ per country in our smoke tests, with thousands available for popular apps.
- **Rating snapshot for every app and country.** You get the average rating, rating count, the full **1★–5★ histogram**, version, price, installs (Google Play), top-chart position (App Store) and **changes since the last run**.
- **Monitor mode.** The Actor remembers which reviews it already returned and saves **only new ones** on each run. You pay only for new reviews.
- **Multi-country and multi-language.** For example, `us`, `gb`, `de` and `jp` on the App Store, and English, German and Japanese reviews on Google Play.
- **Filters.** Sort by newest or most relevant, only reviews since a date (e.g. `7 days`), only certain star ratings (e.g. 1–2★ for alerts), and ratings-only mode.
- **Privacy controls.** Keep reviewer nicknames as the store shows them, hash them, or omit them. Account IDs and profile photos are never collected.

### Why use it?

- **Both stores, one schema.** App Store and Google Play reviews land in one table with the same fields, ready to chart, filter or feed to an LLM.
- **Built for scheduled monitoring.** Re-downloading the same 500 reviews every day wastes money and forces you to deduplicate. Monitor mode returns only what's new, with rating deltas, so a daily run of a quiet app costs about half a cent.
- **Reliable.** It uses the stores' own public JSON endpoints, with automatic retries and rate-limit backoff. If Apple's web endpoint is unavailable, it falls back to Apple's RSS feed. Apps that can't be found are reported, not silently skipped.
- **Predictable price.** You pay a small fee per app checked and per review saved, and you can cap the total cost of any run.

### Use cases

- **ASO agencies and consultants:** track clients' and competitors' ratings, rating mix and review volume across countries every week.
- **Product teams:** after each release, get every new 1–2★ review with the app version it mentions, to spot regressions fast.
- **Customer-experience and support teams:** send a daily feed of new reviews to Slack, a helpdesk or a spreadsheet via [Apify integrations](https://docs.apify.com/platform/integrations), and see which ones already have a developer reply.
- **Market research and AI pipelines:** build normalized review datasets across apps and stores for sentiment analysis, topic modelling or LLM summarization.

### How to use it

1. Click **Try for free**.
2. In **Apps**, paste App Store and/or Google Play links, IDs or package names, for example `https://apps.apple.com/us/app/slack/id618783545` and `com.Slack`.
3. Choose **Countries** (and **Languages** for Google Play), and how many reviews per app you want.
4. Click **Start**. Download the results as JSON, CSV or Excel, or read them through the API. The **Reviews** and **App snapshots** tabs show the two record types.

Example input:

```json
{
  "apps": ["https://apps.apple.com/us/app/slack/id618783545", "com.Slack"],
  "countries": ["us", "gb"],
  "languages": ["en"],
  "maxReviewsPerApp": 200,
  "sinceDate": "30 days",
  "mode": "scrape"
}
```

#### Schedule it: new reviews every day (monitor mode)

1. Set **Mode** to **Monitor: only new reviews since the last run**. Give the **Monitor state store name** a name that is unique to this monitor, for example `acme-apps`.
2. Save the input as a **task** and add a **schedule**, for example daily at 07:00 (`0 7 * * *`).
3. Optional: connect an integration (Slack, email, Google Sheets, Zapier, Make or a webhook) to receive each run's new reviews.

```json
{
  "apps": ["618783545", "com.Slack", "com.microsoft.teams"],
  "countries": ["us", "gb", "de"],
  "languages": ["en", "de"],
  "mode": "monitor",
  "maxReviewsPerApp": 500,
  "ratings": ["1", "2"],
  "monitorStateStore": "acme-apps"
}
```

- The first monitor run saves the latest reviews as a baseline.
- Every later run saves only reviews it has not returned before, plus one snapshot per app and country with `newReviews`, `ratingChange`, `ratingCountChange` and `previousSnapshotAt`.
- **Rating history:** collect the **App snapshots** view across runs, or set **Include reviews** to off for a ratings-only tracker.

#### Use it from AI agents, MCP and the API

- The Actor works well as a tool for AI agents through the [Apify MCP server](https://mcp.apify.com): every input and output field is described in the schema.
- Call it synchronously from code with `POST https://api.apify.com/v2/acts/<username>~app-reviews-monitor/run-sync-get-dataset-items`, using the input above as the JSON body.
- To get one record type, filter on `recordType` (`review` or `appSnapshot`).

### Input

| Field | Type | Description | Example |
|---|---|---|---|
| `apps` | array of strings | **Required.** App Store IDs/URLs and Google Play package names/URLs, mixed freely. The store is detected automatically | `["618783545", "com.Slack"]` |
| `countries` | array of strings | Two-letter store countries. Every app is checked in each one. Default `["us"]` | `["us", "gb", "de"]` |
| `languages` | array of strings | Google Play review languages (each Google Play app is checked per country × language). Ignored by the App Store. Default `["en"]` | `["en", "es"]` |
| `maxReviewsPerApp` | integer | Max reviews per app × country (× language). `0` = no limit. Default `100` | `500` |
| `sinceDate` | string | Only reviews posted on or after this date. Absolute or relative | `"2026-09-01"`, `"7 days"` |
| `mode` | `scrape` | `monitor` | `monitor` saves only reviews that are new since the last run. Default `scrape` | `"monitor"` |
| `includeReviews` | boolean | Off = rating snapshots only. Default `true` | `false` |
| `sortBy` | `newest` | `mostRelevant` | Review order. Monitor mode needs `newest`. Default `newest` | `"mostRelevant"` |
| `ratings` | array of `"1"`–`"5"` | Only these star ratings. Empty = all | `["1", "2"]` |
| `reviewerNames` | `keep` | `hash` | `omit` | How to output reviewer nicknames. Default `keep` | `"hash"` |
| `monitorStateStore` | string | Named key-value store that holds monitor state. Use one per monitor. Default `app-reviews-monitor-state` | `"acme-apps"` |
| `proxyConfiguration` | object | Optional Apify proxy. Usually not needed | `{ "useApifyProxy": true }` |

### Output

The dataset holds two record types, told apart by `recordType`. Each has its own view in the Console.

**Review** (`recordType: "review"`):

```json
{
  "recordType": "review",
  "store": "appStore",
  "appId": "618783545",
  "appName": "Slack",
  "country": "us",
  "language": null,
  "reviewId": "14601776949",
  "rating": 3,
  "title": "Slack tries to do everything and in the end does nothing well",
  "text": "Too many options, too many ways to organize things, it gets confusing and easy to lose new messages once they are read.",
  "reviewerName": "Reviewer nickname",
  "appVersion": "26.09.40",
  "reviewedAt": "2026-09-28T02:09:48.000Z",
  "isEdited": false,
  "helpfulCount": 0,
  "voteCount": 0,
  "developerReplyText": null,
  "developerRepliedAt": null,
  "url": "https://apps.apple.com/us/app/id618783545?see-all=reviews",
  "scrapedAt": "2026-09-29T21:11:31.941Z"
}
```

**App snapshot** (`recordType: "appSnapshot"`), one per app × country (× language). This one is from a scheduled monitor-mode run:

```json
{
  "recordType": "appSnapshot",
  "status": "ok",
  "errorMessage": null,
  "store": "googlePlay",
  "appId": "com.Slack",
  "appName": "Slack",
  "country": "us",
  "language": "en",
  "developer": "SLACK TECHNOLOGIES L.L.C.",
  "genre": "Business",
  "price": 0,
  "currency": "USD",
  "version": "26.09.41.0",
  "appUpdatedAt": "2026-09-28T22:01:20.000Z",
  "rating": 4.64539,
  "ratingCount": 218972,
  "reviewCount": 7803,
  "stars1Count": 10162,
  "stars2Count": 2913,
  "stars3Count": 5111,
  "stars4Count": 17741,
  "stars5Count": 183018,
  "installs": "50,000,000+",
  "minInstalls": 50000000,
  "chartPosition": null,
  "chartName": null,
  "iconUrl": "https://play-lh.googleusercontent.com/C9w-zv2PzpS-Rr9L8PUveRfv36lfYiB2kPKiEy45ucveIYMCoUW1nKQls5VxXSJyUsNxC_s7nFqpIAbqlLbSKA",
  "reviewsCollected": 0,
  "newReviews": 0,
  "previousRating": 4.6465545,
  "ratingChange": -0.00116,
  "previousRatingCount": 218972,
  "ratingCountChange": 0,
  "previousSnapshotAt": "2026-09-29T21:36:49.486Z",
  "url": "https://play.google.com/store/apps/details?id=com.Slack&hl=en&gl=us",
  "scrapedAt": "2026-09-29T21:37:40.112Z"
}
```

Field notes:

- All dates are ISO-8601 UTC. A missing value is `null`, never an empty string.
- `status` is `ok`, `notFound` (the app doesn't exist, isn't sold in that country, or was removed) or `error` (with `errorMessage`).
- Some fields exist only on one store:
  - `title`, `isEdited` and `voteCount`: App Store only.
  - `reviewCount`, `installs` and `minInstalls`: Google Play only.
  - `chartPosition`: App Store only, when the app is charted.
- On the App Store, `appVersion` is available for roughly the 500 most recent reviews per country.
- A run summary (apps checked, not found, failed, reviews saved) is stored in the key-value store under `SUMMARY`.

### Pricing

This Actor uses **pay-per-event** pricing. You pay only for what you get:

| Event | Price | When |
|---|---|---|
| Actor start | $0.003 | Once per run |
| App check | $0.002 | Per app × country (× language) checked: details, rating, 1–5★ histogram and changes since last run. **Not charged** if the app is not found |
| Review | $0.0001 | Per review saved ($0.10 per 1,000). In monitor mode only **new** reviews are saved and charged |

Higher Apify subscription plans get lower per-event prices, shown on the **Pricing** tab.

**Cost examples:**

- **Default input** (Slack on both stores, `us`, 20 reviews each): $0.003 + 2 × $0.002 + 40 × $0.0001 = **$0.011**.
- **Bulk pull:** 5 apps × 1 country × 1,000 reviews = $0.003 + $0.01 + $0.50 = **$0.51**.
- **Daily monitor** of 10 app listings with about 10 new reviews each per day:
  - Per run: $0.003 + $0.02 + $0.01 = $0.033.
  - Per month: about **$1.00**.

To cap the cost of a run, set **Maximum cost per run** in the run options. The Actor stops cleanly at that limit and tells you what it skipped.

New to Apify? The free plan's monthly credit covers thousands of reviews.

### FAQ

**Is it legal to scrape app reviews?** This Actor extracts only publicly available data: reviews and app details that anyone can see in the stores without logging in. It does not extract private user data. Your results may still contain personal data, which is protected by GDPR and other regulations. Do not scrape personal data unless you have a legitimate reason. If you are unsure, consult your lawyers. You can read the basics of ethical web scraping in [Is web scraping legal?](https://blog.apify.com/is-web-scraping-legal/)

**What personal data does it collect?** Only the reviewer's public nickname as the store displays it, and you can set **Reviewer names** to `hash` or `omit`. It never collects reviewer account IDs, profile photos, profile links or emails. Monitor state stores only review IDs and dates.

**How many reviews can I get?**

- **Google Play:** as many as the store lists for that language (tens of thousands for popular apps).
- **App Store:** each country's reviews separately, well beyond the 500 per country of Apple's RSS feed.
- **Big pulls:** add more countries to get more App Store reviews.

**Why do Google Play results depend on language, and App Store results on country?** Google Play groups written reviews by language. Apple keeps a separate review list for each country's store. The input follows how each store works.

**What happens if an app ID is wrong, or the app isn't available in a country?**

- You get an `appSnapshot` with `status: "notFound"` and an explanation, and it is not charged.
- The run still succeeds for the other apps.
- If **no** app can be found, the run fails with a clear message so you notice.

**Does monitor mode ever miss or repeat reviews?**

- Each run re-checks the last 24 hours and remembers up to 10,000 review IDs per app and country, so reviews that the store publishes late are still caught and nothing is repeated.
- If more than `maxReviewsPerApp` new reviews arrive between two runs, only the newest ones are returned. Raise the limit or run more often.

**Can I run several monitors?** Yes. Give each one its own **Monitor state store name**.

### Limitations

- **App Store:**
  - `appVersion`, `helpfulCount` and `voteCount` come from Apple's RSS feed, which covers only about the 500 most recent reviews per country. Older reviews have these as `null`.
  - The **Most relevant** sort has no app version.
  - Apple doesn't expose review languages, so `language` is `null` for App Store records.
- **Google Play:**
  - Reviews have no title.
  - The review date is the date of the latest edit.
  - Country has little effect on which reviews are returned; language decides that.
- **Large pulls:** App Store reviews come 20 per request, and the Actor backs off automatically when Apple rate-limits. A pull of 1,000 App Store reviews plus 1,000 Google Play reviews took 12 s in our tests; very large pulls (tens of thousands per country) take proportionally longer.
- **Not supported:** search by keyword, top charts, and in-app purchase details.
- **Undocumented endpoints:** the stores can change their internal endpoints at any time. We run automated daily checks and fix breakages quickly.

### Changelog

See [CHANGELOG.md](./CHANGELOG.md) for all changes.

### Support

Found a bug or need a feature? Open an issue on the **Issues** tab. We respond within 48 hours. For custom solutions, such as more stores, alerts or dashboards, contact us through the same tab.

# Changelog

This Actor's version history is a separate document: https://apify.com/cylindrical\_lighthouse/app-reviews-monitor/changelog.md

# Actor input Schema

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

Apps to check, from either store, mixed freely. Accepts App Store IDs or URLs (e.g. <code>618783545</code> or <code>https://apps.apple.com/us/app/slack/id618783545</code>) and Google Play package names or URLs (e.g. <code>com.Slack</code> or <code>https://play.google.com/store/apps/details?id=com.Slack</code>). The store is detected automatically.

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

Two-letter store country codes, e.g. <code>us</code>, <code>gb</code>, <code>de</code>, <code>jp</code>. Every app is checked in every country. App Store reviews are separate per country; on Google Play the country sets prices and availability.

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

Google Play groups reviews by language, so each Google Play app is checked once per country × language. Use codes like <code>en</code>, <code>de</code>, <code>ja</code>, <code>pt-BR</code>. The App Store ignores this field because its reviews are grouped by country.

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

The maximum number of reviews to save for each app × country (× language on Google Play). Keeps cost predictable. Use 0 for no limit, which means every review the store returns. Example: 500.

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

Only save reviews posted on or after this date. Accepts an absolute date (<code>2026-09-01</code>) or a relative one (<code>7 days</code>, <code>2 weeks</code>, <code>1 month</code>). With newest-first sorting, the Actor stops paging once it reaches older reviews.

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

<b>Scrape</b> saves reviews (newest first, up to the limits above) on every run. <b>Monitor</b> remembers which reviews it has already returned for each app and country, and saves <b>only new reviews</b> since the last run, plus rating changes. Schedule it daily for a new-review feed. The first monitor run saves the latest reviews as a baseline.

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

Turn this off to track ratings only. You then get one app snapshot per app and country (average rating, rating count, 1–5★ histogram and changes since the last run) and no reviews.

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

The order in which reviews are collected. <b>Newest</b> is required for monitor mode and for stopping early at <i>Only reviews since</i>. <b>Most relevant</b> uses the store's default ordering (the most helpful reviews first).

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

Save only reviews with these star ratings. Leave empty to save all. Example: <code>\["1", "2"]</code> for a negative-review alert feed. Filtered-out reviews are never saved or charged.

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

Reviewer nicknames are personal data. <b>Keep</b> returns the nickname exactly as the store shows it publicly. <b>Hash</b> replaces it with a stable pseudonymous code, so you can still count repeat reviewers. <b>Omit</b> returns null. Account IDs and profile photos are never collected.

## `monitorStateStore` (type: `string`):

The name of the key-value store where monitor mode remembers which reviews it has already returned. Use a different name for each separate monitor (e.g. one per client) so they don't share state. Letters, digits and hyphens only. Example: <code>acme-app-monitor</code>.

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

Proxies are usually not needed. Enable the Apify datacenter proxy if you check many apps at once and see rate-limit warnings in the log.

## Actor input object example

```json
{
  "apps": [
    "618783545",
    "com.Slack",
    "https://play.google.com/store/apps/details?id=com.duolingo"
  ],
  "countries": [
    "us",
    "gb",
    "de"
  ],
  "languages": [
    "en",
    "es"
  ],
  "maxReviewsPerApp": 500,
  "sinceDate": "7 days",
  "mode": "monitor",
  "includeReviews": true,
  "sortBy": "newest",
  "ratings": [
    "1",
    "2"
  ],
  "reviewerNames": "hash",
  "monitorStateStore": "acme-app-monitor",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

One record per review (App Store and Google Play, normalized). In monitor mode, only reviews that are new since the last run.

## `appSnapshots` (type: `string`):

One record per app, country and language checked: app details, average rating, rating count, 1-5 star histogram and changes since the last run.

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

Counts of apps checked, apps not found, apps failed and reviews saved.

# 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/slack/id618783545",
        "com.Slack"
    ],
    "countries": [
        "us"
    ],
    "languages": [
        "en"
    ],
    "maxReviewsPerApp": 20,
    "mode": "scrape",
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("cylindrical_lighthouse/app-reviews-monitor").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/slack/id618783545",
        "com.Slack",
    ],
    "countries": ["us"],
    "languages": ["en"],
    "maxReviewsPerApp": 20,
    "mode": "scrape",
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("cylindrical_lighthouse/app-reviews-monitor").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/slack/id618783545",
    "com.Slack"
  ],
  "countries": [
    "us"
  ],
  "languages": [
    "en"
  ],
  "maxReviewsPerApp": 20,
  "mode": "scrape",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call cylindrical_lighthouse/app-reviews-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cylindrical_lighthouse/app-reviews-monitor"
        }
    }
}
```

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/KTVS7IGFF83ViZ5Kn/builds/zZ9cTYKssb85gOzBh/openapi.json
