# Gripeline — App Store & Google Play Reviews Scraper (`cybermax/app-reviews`) Actor

App Store and Google Play reviews scraper: both stores in one clean schema, plus a per-app summary agents can read (star mix, % negative, top complaints, worst app versions). Monitor mode returns only new reviews for daily alerts. $0.0001 per review, $0.003 per app summary.

- **URL**: https://apify.com/cybermax/app-reviews.md
- **Developed by:** [CyberMax](https://apify.com/cybermax) (community)
- **Categories:** AI, Marketing, Developer tools
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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

## Gripeline — App Store & Google Play reviews scraper

**Know what users hate about any app in one call.** Give Gripeline an app name, link or id. It reads the newest reviews from the **App Store and Google Play**, puts them into one schema, and adds a **summary record per app** that an AI agent or an analyst can use straight away:

> *Threads (App Store): 500 newest reviews (2026-08-30 to 2026-09-22), average 3.43★, 38.2% negative (1–2★). Top complaints: "people" (28), "account" (24), …*

### Why this one

- **Both stores, one schema.** `"Duolingo"` is looked up in the App Store *and* Google Play, and every review has the same fields: stars, title, text, date, app version, country, helpful votes and developer reply. You don't need two scrapers and a merge step.
- **A summary that saves reading 500 reviews.** Each app gets star mix, % negative and % positive, **top complaint phrases** (from 1–2★ reviews), top praise phrases, **worst app versions** (≥5 reviews, lowest average first) and the Google Play developer reply rate.
- **Agent-friendly.** Names, links or ids all work. Compact JSON, a one-sentence `summary`, and `includeReviews: false` returns only the summaries (about 2 KB per app).
- **Reliable on the App Store.** Apple's review feed often answers a valid page with nothing. Gripeline retries each page with a fresh cache key and skips gaps, so you get the full 500 newest reviews per country instead of 50. Tested 2026-09-23: 500 of 500 for Threads and Duolingo in about 7 seconds.
- **Filters are free.** Stars (`maxRating: 2` = complaints only), date (`since`) and `keyword` ("crash", "subscription"). You pay only for reviews that pass.
- **Daily new-review alerts.** `monitor: true` returns only reviews you haven't received yet, so a daily schedule to Slack or Google Sheets costs cents.

**First try:** the prefilled input (`Duolingo`, 5 reviews per store) finishes in about 10 seconds and costs about **$0.007** (2 summaries + 10 reviews).

### Input

```json
{
  "apps": ["Duolingo", "https://apps.apple.com/us/app/threads/id6446901002", "com.spotify.music"],
  "stores": "both",
  "maxReviewsPerApp": 200,
  "countries": ["us"],
  "maxRating": 2
}
```

| Field | Default | What it does |
|---|---|---|
| `apps` | – | App names, App Store links/ids (`id570060128`), Google Play links/package names (`com.duolingo`). |
| `stores` | `both` | `both`, `apple` or `google`. Names are searched in the stores selected; links and ids go to their own store. |
| `maxReviewsPerApp` | `200` | Newest first, per store and country. The App Store feed stops at 500 per country; Google Play goes up to 5,000. `0` = store metadata only. |
| `includeReviews` | `true` | `false` = summary records only. The summary always covers the reviews that pass your filters. |
| `countries` | `["us"]` | Up to 10 two-letter App Store countries; each is read separately. Google Play shows the same reviews in every country (they vary by language), so Play is read once per app and its records carry `language` instead of `country`. |
| `language` | `en` | Google Play review language. |
| `minRating` / `maxRating` | 1 / 5 | Keep only reviews in this star range. |
| `since` | – | Keep only reviews on or after this date. |
| `keyword` | – | Keep only reviews that mention this word. |
| `monitor` | `false` | Only reviews not returned by an earlier monitor run (see below). |
| `monitorKey` | – | Optional name for the monitor's saved state, to keep several watchlists apart. |

### Monitor mode: only new reviews since the last run

Turn on `monitor` for scheduled runs. Gripeline remembers, per app, store and country, which reviews it already returned (by review id) and outputs only the new ones, each marked `"change": "new"`. An app's summary is returned only when it has new reviews, and then it summarizes just those.

Shape of a monitor run's records (values shortened):

```json
{ "type": "app-summary", "store": "apple", "title": "Duolingo: Language Lessons", "change": "changed", "changedFields": ["reviews"], "newReviews": 3, "summary": "Duolingo: Language Lessons (App Store): 3 new reviews since the last run (…), average …★, …" }
{ "type": "review", "store": "apple", "appTitle": "Duolingo: Language Lessons", "reviewId": "…", "rating": 1, "text": "…", "change": "new" }
```

- The first monitor run returns everything (`"change": "new"`), like a normal run. A run with no new reviews returns an empty dataset and costs nothing.
- Edited reviews keep their id and aren't sent again. Filters (`maxRating`, `keyword`, `since`) apply to the new reviews, so `maxRating: 2` + `monitor: true` = "new complaints only".
- Set `maxReviewsPerApp` above the number of reviews an app gets between two runs (e.g. 100 for a daily run on a popular app); only new ones are charged.
- State lives in your named key-value store `app-reviews-monitor`, one record per watchlist (`monitorKey`, or derived from the apps, stores, countries and language). Delete the record to start over.

### Ready-made inputs (first try in one click)

**No account yet?** See real results first on the free demo page: [24 popular apps ranked by share of angry reviews](https://huggingface.co/spaces/shaw276/app-complaints).

Paste one into the **JSON** tab of the input form (or send it to the API) and press **Start**. Each is sized so the first run is quick and cheap; you pay only for results (Apify's compute is included in the price).

| Preset | What you get | Cost |
|---|---|---|
| Summary + 5 newest reviews (both stores) | 1 summary per store (star mix, top complaints, worst version) + 5 reviews each | $0.007 |
| Only angry reviews (1–2★, App Store) | up to 10 of the newest 1–2★ reviews + a summary | $0.004 or less |
| Complaint summary only (no review rows) | 1 summary per app from its 100 newest reviews | $0.006 |

**Summary + 5 newest reviews (both stores)**

```json
{"apps": ["Duolingo"], "maxReviewsPerApp": 5}
```

**Only angry reviews (1–2★, App Store)**

```json
{"apps": ["Spotify"], "stores": "apple", "maxRating": 2, "maxReviewsPerApp": 10}
```

**Complaint summary only (no review rows)**

```json
{"apps": ["Airbnb", "Uber"], "stores": "google", "includeReviews": false, "maxReviewsPerApp": 100}
```

Same thing from a script (returns the results directly):

```bash
curl -s -X POST "https://api.apify.com/v2/acts/cybermax~app-reviews/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'content-type: application/json' -d '{"apps": ["Duolingo"], "maxReviewsPerApp": 5}'
```

### Output

One `app-summary` record per app and store (and per country on the App Store):

```json
{
  "type": "app-summary",
  "store": "apple",
  "appId": "6446901002",
  "title": "Threads",
  "developer": "Instagram, Inc.",
  "rating": 4.64,
  "ratingCount": 2252093,
  "currentVersion": "447.0",
  "country": "US",
  "summary": "Threads (App Store): 500 newest reviews …, average 3.43★, 38.2% negative (1–2★). Top complaints: …",
  "sample": { "reviews": 500, "avgRating": 3.43, "stars": { "1": 176, "2": 15, "3": 18, "4": 22, "5": 269 }, "negativeShare": 38.2, "positiveShare": 58.2, "developerReplyRate": null },
  "topComplaints": [{ "phrase": "account", "reviews": 24 }],
  "topPraise": [{ "phrase": "twitter", "reviews": 12 }],
  "worstVersions": [{ "version": "447.0", "reviews": 156, "avgRating": 3.12, "negativeShare": 46.2 }]
}
```

…followed by its `review` records:

```json
{ "type": "review", "store": "google", "appId": "com.spotify.music", "appTitle": "Spotify: Music and Podcasts", "reviewId": "…", "rating": 1, "title": null, "text": "…", "date": "2026-09-22T10:11:42.000Z", "appVersion": "9.1.84.2231", "helpfulVotes": 0, "country": null, "language": "en", "developerReply": { "text": "…", "date": "…" }, "author": "…" }
```

Apps that aren't found come back as `type: "not-found"` and are free. The Store output tab has two views: **App summaries** and **Reviews**.

### Pricing: how much does it cost to scrape App Store and Google Play reviews?

- **$0.003 per app summary** (one app in one store; App Store: per country)
- **$0.0001 per review** ($0.10 per 1,000, the same per-review price as the most-used single-store review scrapers, and you get both stores plus the summary)
- Apps that aren't found and reviews dropped by your filters: free. No start fee.

Example: 3 apps in both stores, 200 reviews each = 6 × $0.003 + 1,200 × $0.0001 = **$0.138**. Summaries only (`includeReviews: false`) for the same apps = **$0.018**.

### Good to know

- Data comes from Apple's public iTunes lookup/search APIs and review feed, and Google Play's public web pages. No login needed, and no personal data beyond the public reviewer display name.
- App Store reviews don't include developer replies in Apple's feed, so `developerReply` is always `null` there.
- Complaint phrases are word and two-word counts from 1–2★ reviews, with the app's own name and filler words removed. They show themes; they aren't sentiment analysis.

### When NOT to use it

- **You need every review an app ever got.** Apple's public feed stops at the 500 newest reviews per country; Gripeline reads all of them, but older App Store reviews aren't reachable without the developer's own App Store Connect access. Google Play goes deeper.
- **You want reviews for your own app with replies you can post.** Use App Store Connect or the Google Play Developer API (free for your own apps, and they let you reply).
- **You need sentiment scores or topic labels from a model.** Complaint phrases are counts, not a language model. Send the `review` rows to your own LLM step (the schema is compact on purpose).
- **You need ratings history over months.** Each run is a snapshot. Schedule a daily run with `monitor: true` and keep the dataset, or use a paid app-intelligence suite.

### FAQ

**Is there an App Store reviews API?** Apple has no public review API for other people's apps, only an RSS feed of the newest reviews. Gripeline wraps that feed (and Google Play's public pages) behind one API call: `POST https://api.apify.com/v2/acts/cybermax~app-reviews/run-sync-get-dataset-items`.

**Can I scrape Google Play reviews without an app id?** Yes. Give the app name (`"Duolingo"`), a store link or the package name (`com.spotify.music`).

**How many reviews can I get?** Up to 500 newest per App Store country, and up to 5,000 per app on Google Play (`maxReviewsPerApp`).

**Which countries and languages?** Up to 10 store countries per run in `countries` (`us`, `gb`, `de`, `in`…), and `language` picks the language of Google Play reviews (`en`, `de`, `pt-BR`…).

**Can an AI agent use it?** Yes: it's on Apify's MCP server (`https://mcp.apify.com/?tools=cybermax/app-reviews`) and in the official MCP Registry. `includeReviews: false` keeps the answer to about 2 KB per app.

**Is it legal?** Gripeline reads public store pages and feeds, with no login. Reviews contain the reviewer's public display name; handle it under your own privacy rules (GDPR/CCPA) if you store it.

### Run it on a schedule

Daily "new reviews" digest in two minutes: Apify Console → **Schedules** → **Create new** → cron `0 8 * * *` (8:00 every day) → **Add Actor** → `cybermax/app-reviews` with your input (or a saved task) including `"monitor": true`. Or with the API:

```http
POST https://api.apify.com/v2/schedules?token=YOUR_TOKEN
{
  "name": "daily-app-reviews",
  "cronExpression": "0 8 * * *",
  "timezone": "America/New_York",
  "isEnabled": true,
  "actions": [{
    "type": "RUN_ACTOR",
    "actorId": "cybermax/app-reviews",
    "runInput": { "body": "{\"apps\": [\"Duolingo\", \"com.spotify.music\"], \"maxReviewsPerApp\": 100, \"monitor\": true}", "contentType": "application/json" }
  }]
}
```

With monitor mode a daily run only pays for that day's new reviews. To get notified, add a webhook, Slack or email notification on **Run succeeded** in the Actor's **Integrations** tab.

### Integrations

**Make:** Apify app → "Run an Actor" (`cybermax/app-reviews`, input JSON as above, "Run synchronously" on) → "Get dataset items" → Slack / Google Sheets module.

**Zapier:** Apify app → "Finished Actor Run" trigger (for your schedule) or "Run Actor" action → "Find dataset items" → Slack "Send channel message" with `{{title}}: {{rating}}★ {{text}}`.

**n8n:** Apify node ("Run actor and get dataset"), or an HTTP Request node:

```
POST https://api.apify.com/v2/acts/cybermax~app-reviews/run-sync-get-dataset-items?token=YOUR_TOKEN
{"apps": ["Duolingo"], "maxRating": 2, "monitor": true}
```

**Google Sheets:** Apify's Google Sheets integration (Actor → Integrations), or paste into a cell to pull the last run as CSV:

```
=IMPORTDATA("https://api.apify.com/v2/acts/cybermax~app-reviews/runs/last/dataset/items?format=csv&view=reviews&token=YOUR_TOKEN")
```

**Python** (`pip install apify-client`):

```python
from apify_client import ApifyClient
client = ApifyClient("YOUR_TOKEN")
run = client.actor("cybermax/app-reviews").call(run_input={"apps": ["Duolingo"], "maxReviewsPerApp": 50, "maxRating": 2})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["type"], item.get("rating"), item.get("summary") or item.get("text"))
```

**JavaScript** (`npm i apify-client`):

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('cybermax/app-reviews').call({ apps: ['Duolingo'], includeReviews: false });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((i) => i.summary));
```

**AI agents (MCP):** Gripeline is a tool in any MCP client via Apify's hosted MCP server, `https://mcp.apify.com/?tools=cybermax/app-reviews` (header `Authorization: Bearer <your Apify token>`). Claude Desktop / Cursor config:

```json
{ "mcpServers": { "gripeline": { "url": "https://mcp.apify.com/?tools=cybermax/app-reviews", "headers": { "Authorization": "Bearer YOUR_TOKEN" } } } }
```

A good agent prompt: *"Use app-reviews with includeReviews=false and maxRating=2 to tell me the top 3 complaints about <app> this month."*

### Also from CyberMax

- [Keyfern — Keyword ideas](https://apify.com/cybermax/keyword-ideas): real search phrasing from Google, YouTube and Amazon autocomplete for content and ASO around the complaints you find.
- [Swellmeter — Google Trends](https://apify.com/cybermax/google-trends): is interest in your app or a competitor rising or falling?
- [DomainDNA — Tech stack & domain intel](https://apify.com/cybermax/domain-intel): what the developer's website is built with.
- [SnapScout — Website screenshots](https://apify.com/cybermax/website-screenshot): snapshot competitors' landing and pricing pages.
- [Logolark — Logos by domain](https://huggingface.co/spaces/shaw276/saas-logos-brand-colors?soon=logo-finder): app developer logos for your reports and dashboards.

All tools: [Boardroom Buys](https://apify.com/cybermax/sec-insider-tracker) · [HireHeat](https://apify.com/cybermax/hiring-signals) · [HireHeat Jobs](https://apify.com/cybermax/career-site-jobs) · [Hollerdex](https://apify.com/cybermax/website-contacts) · [Linkheft](https://apify.com/cybermax/domain-authority) · [Printwren](https://huggingface.co/spaces/shaw276/docs-pages-as-pdfs?soon=html-to-pdf)

### Changelog

- 2026-09-23: Monitor mode (`monitor`, `monitorKey`: only new reviews since the last run, deduped by review id), schedule & integration recipes.

Made by CyberMax.

# Actor input Schema

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

App names ("Duolingo" is looked up in both stores), App Store links (apps.apple.com/.../id570060128), Google Play links (play.google.com/store/apps/details?id=com.duolingo), Apple numeric ids or Play package names.

## `stores` (type: `string`):

Which stores to read. Links and ids always point to their own store; app names are searched in the stores selected here.

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

Newest reviews first. The App Store feed gives at most 500 per country; Google Play goes deeper. 0 = summary of store metadata only.

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

Off = only the per-app summary record (the summary is still computed from the reviews read).

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

Two-letter store countries (us, gb, de, in…). Each country is read separately; up to 10.

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

Language of Google Play reviews (en, de, es, pt-BR…).

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

Only keep reviews with at least this many stars.

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

Only keep reviews with at most this many stars (e.g. 2 = complaints only).

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

Only keep reviews on or after this date (YYYY-MM-DD).

## `keyword` (type: `string`):

Only keep reviews whose title or text contains this word (case-insensitive), e.g. "crash" or "subscription".

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

How many apps to read at the same time.

## `monitor` (type: `boolean`):

For scheduled runs (e.g. daily new reviews to Slack or Google Sheets). Remembers which reviews were already returned for each app, store and country, and outputs (and charges) only reviews not seen before, marked change: "new". An app's summary record is returned only when it has new reviews, and then summarizes just those. The first monitor run returns everything, like a normal run. Set Max reviews per app to more than you expect per day between runs (e.g. 100).

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

Name for this watchlist's saved state, so several watchlists can be monitored separately (letters, digits, - \_ . only). Empty = derived from the apps, stores, countries and language. State is kept in your named key-value store "app-reviews-monitor".

## Actor input object example

```json
{
  "apps": [
    "Duolingo"
  ],
  "stores": "both",
  "maxReviewsPerApp": 5,
  "includeReviews": true,
  "countries": [
    "us"
  ],
  "language": "en",
  "minRating": 1,
  "maxRating": 5,
  "maxConcurrency": 4,
  "monitor": false
}
```

# Actor output Schema

## `summaries` (type: `string`):

No description

## `reviews` (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": [
        "Duolingo"
    ],
    "maxReviewsPerApp": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("cybermax/app-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": ["Duolingo"],
    "maxReviewsPerApp": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("cybermax/app-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": [
    "Duolingo"
  ],
  "maxReviewsPerApp": 5
}' |
apify call cybermax/app-reviews --silent --output-dataset

```

## MCP server setup

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