# App Review Monitor — Google Play & App Store Alerts (`vasram/app-review-monitor`) Actor

Monitor new Google Play & App Store reviews for your apps: instant Slack/Discord alerts, daily digests, release canary & competitor pain radar. Deterministic tagging, no API keys, no AI bills.

- **URL**: https://apify.com/vasram/app-review-monitor.md
- **Developed by:** [Vasram Sonagara](https://apify.com/vasram) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.20 / 1,000 review delivereds

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

### What does App Review Monitor do?

**App Review Monitor** watches the **user reviews of your Google Play and Apple App Store apps** and delivers one clean, structured dataset — **new reviews only**, de-duplicated across runs, enriched with **star-based sentiment, theme tags (Bug, Pricing, UX…), crash-language flags and a reply-needed signal** — plus **instant alerts (Slack, Discord or any webhook)**, a **bundled daily/weekly digest** and a **one-page HTML dashboard**. Paste your app IDs, press Run, and every scheduled run brings you exactly what is new. **No API keys, no logins, no AI bills** — every enrichment is computed deterministically.

It also acts as a **release watchdog**: the built-in Release Canary compares the 1★ share and crash language of your newest reviews against your app's normal levels and pages you when a release looks broken. And as a **competitive-weapon**: add rival apps and the Competitor Pain Radar mines their negative reviews for the complaints you can win users with.

Everything lands in **your own Apify storage** — nothing is uploaded anywhere else. As an Apify Actor you also get scheduling, an API, integrations (Make, n8n, Zapier) and webhooks out of the box.

Try it in under a minute: the input comes pre-filled with a working example — press **Run**, then open **Storage → Dataset** and the **REPORT** key in **Storage → Key-value store**.

### Why use this Google Play & App Store review monitor?

Reviews are your fastest signal: a fresh 1★ wave tells you about a broken release before your dashboards do, and unanswered angry reviews quietly drag your rating down. The stores make this surprisingly hard — Play Console and App Store Connect each show only their own reviews, with clunky exports, no shared history and no alerting you control. This actor fixes that:

- **Never miss a review** — every run remembers which reviews it has already seen, so repeat runs only surface fresh ones. Reviews are pushed incrementally, so partial progress survives any interruption.
- **One dataset for both stores** — Google Play and App Store reviews share the same field contract, so your sheets, BI dashboards and automations just work.
- **Instant alerts without spam** — critical reviews (1–2★, unanswered replies, crash language) go to Slack/Discord/any webhook the moment they appear, protected by quiet hours, mute keywords, a daily cap and review-storm bundling. Everything suppressed waits in the next digest — nothing is lost.
- **Release Canary** — WATCH / ROLLBACK / IMMEDIATE-ROLLBACK verdicts from your 1★ share and crash-language level vs. a per-app baseline, confirmed over consecutive runs to avoid false alarms.
- **Bundled digest** — one daily or weekly message: reviews that need a reply, release health, competitor pains and a per-app pulse.
- **HTML dashboard** — a visual one-page report (verdicts, actions, radar, trends) stored in every run under the `REPORT` key.
- **Deterministic tagging out of the box** — sentiment from stars, theme tags (Bug, Performance, Pricing, UX, Feature Request, Account & Login, Ads, Update, Praise), a crash-language score, and a `replyNeeded` flag for unanswered 1–2★ Google Play reviews.
- **Competitor Pain Radar** — rival reviews fetched and mined in the same run: top complaint themes, most-frequent complaint keywords, worst-case quotes.
- **Built for the Apify platform** — schedule it, point Apify integrations (Slack, Google Sheets, Make, n8n, Zapier) at the dataset, or call it through the API.

### Why a review monitor beats a one-shot review scraper

Plenty of Actors **scrape reviews**: you paste an app, get a dump, and repeat the same work tomorrow. App Review Monitor is the **monitoring layer** on top — you set it up once and it keeps watch:

| | One-shot review scraper | **App Review Monitor** |
|---|---|---|
| What you get | A static dump of reviews — the same ones again tomorrow | Only **new** reviews, de-duplicated forever |
| Alerting | None — you re-check manually | Instant **Slack/Discord/webhook alerts** with anti-spam (quiet hours, caps, storm protection) |
| Daily digest | None | One bundled message: replies needed, release health, competitor pains, store pulse |
| Release watch | None | **Release Canary** verdicts (WATCH / ROLLBACK) from your 1★ share and crash language |
| Competitor radar | Scrape rivals by hand | Automatic pain-point mining of rivals' negative reviews |
| Reporting | Filter raw dumps yourself | One-page **HTML dashboard** in every run |
| Cost model | Pay for the same reviews repeatedly | Pay once per review — repeats are **free**, thanks to de-duplication |

A scraper is the tool you use once. A monitor is the system you set up once and trust forever.

### How to monitor app reviews (step by step)

1. Paste your **Google Play package names** (find them in the Play Store URL after `?id=`, e.g. `com.spotify.music`) and/or **Apple app IDs** (numbers only, from the App Store URL after `/id`, e.g. `324684580`).
2. Pick the **countries** to monitor as 2-letter codes (default: `us`). For Google Play you can also pick **languages** (default: `en`).
3. To get alerts, paste a **Slack**, **Discord** or **generic webhook URL** in the Alerts section. Leave them empty while testing — alerts are printed to the run log.
4. Optionally add **competitor apps** in the Competitor fields.
5. Press **Run** — a typical run of a few apps takes under a minute and costs a fraction of a cent.
6. Open **Storage → Dataset** (one row per review, exportable as JSON/CSV/Excel/HTML) and the **HTML report** under the `REPORT` key in **Storage → Key-value store**.
7. Set a **schedule** (next section) — that is the whole setup. From then on, each run only processes reviews that arrived since the last one.

### How to schedule app review monitoring (set and forget)

The Actor is built for **scheduled monitoring** — you do not need to run it manually again and again. Set one fixed schedule and it quietly does the job forever:

1. Open the Actor in **Apify Console** and fill in the input once (apps, countries, webhook).
2. Click **Schedule** on the Actor's page (or go to **Schedules → Create new**).
3. Pick a repeat interval — for example **every 6 hours** (cron: `0 */6 * * *`) — and save. Done.

Why a fixed schedule beats manual re-runs:

- **You never miss reviews** — every run remembers exactly which reviews it has already seen, so frequent small runs catch everything between them. Rare manual pulls can permanently miss reviews on busy apps once they fall out of the store's "newest" window.
- **No wasted compute** — a scheduled run only fetches what is new, so even an hourly run of a few apps costs just a few cents a day. Running the Actor by hand in a loop adds nothing except cost — if nothing new appeared, it finishes in seconds.
- **Alerts stay on time** — instant alerts fire within minutes of a review appearing, while the digest still lands only once per period (daily or weekly). A tighter schedule makes alerts *faster*, not noisier.
- **Canary verdicts get stronger** — the Release Canary compares results across consecutive runs, so a regular schedule makes release verdicts more reliable.

Recommended setups:

| Use case | Schedule | Suggested extras |
|---|---|---|
| Trying it out | Run manually once or twice | Leave webhooks empty — alerts print to the run log |
| Small app, personal project | Every 24 hours | Daily digest in the morning |
| Active app, regular releases | Every 4–6 hours | Quiet hours at night, digest at breakfast |
| Launch week / release watch | Every 1–2 hours | `canaryConfirmRuns: 1`, storm protection on |

Timezone note: schedules follow your Apify account timezone; quiet hours use the `quietHoursTimezone` input (default `Asia/Kolkata`).

### Input parameters

| Field | Default | Description |
|---|---|---|
| `googlePlayApps` | — | Your Play package names, e.g. `com.spotify.music`. |
| `appStoreApps` | — | Your Apple app IDs (numbers only), e.g. `324684580`. |
| `competitorGooglePlayApps` / `competitorAppStoreApps` | — | Rival apps, fetched and tagged in the same run. |
| `countries` | `us` | 2-letter country codes: `us`, `in`, `gb`, `de`, `br`… |
| `languages` | `en` | Google Play language codes (`en`, `hi`, `es`…). Ignored by the App Store feed. |
| `mode` | `monitor` | `monitor` = poll what's new (use with a schedule) · `backfill` = pull review history (up to `backfillMax`). |
| `canaryWindowHours` | `48` | How far back the Release Canary looks. |
| `canaryWatchOneStarMultiplier` / `canaryRollbackOneStarMultiplier` / `canaryImmediateRollbackMultiplier` | `2 / 4 / 6` | Verdict thresholds as multiples of the app's normal 1★ share. |
| `canaryMinVolume` / `canaryCrashShareThreshold` / `canaryConfirmRuns` | `10 / 0.08 / 2` | Minimum window volume, crash-share threshold, and how many consecutive runs must confirm a verdict. |
| `slackWebhookUrl` / `discordWebhookUrl` / `genericWebhookUrl` | — | Instant alert channels (empty = log only). |
| `quietHoursEnabled` / `quietHoursStartHour` / `quietHoursEndHour` / `quietHoursTimezone` | `false / 22 / 8 / Asia/Kolkata` | Pause instant alerts during sleeping hours (deferred to the digest). |
| `muteKeywords` | — | Reviews containing these words never alert instantly (digest only). |
| `maxInstantPerDay` | `5` | Daily cap on instant alerts (0 = digest only). |
| `stormWindowMinutes` / `stormCount` | `120 / 5` | Bundle a burst of alerts into one storm message. |
| `reportEnabled` / `reportPublicLink` / `reportMaxReviewsShown` | `true / true / 10` | HTML report controls. |
| `digestFrequency` | `daily` | `daily`, `weekly` or `off`. |
| `digestSectionActions` / `digestSectionRelease` / `digestSectionCompetitors` / `digestSectionHealth` | `true` | Which sections the digest includes. |
| `keywordMining` / `minPhraseLength` | `true / 4` | Complaint-keyword mining for the radar. |
| `filterMinRating` / `filterMaxRating` | `0` | Keep only reviews inside a star range (0 = off). |
| `filterIncludeKeywords` / `filterExcludeKeywords` | — | Keyword gates, case-insensitive. |
| `filterMinDate` / `filterLanguages` | — | Keep only reviews since `YYYY-MM-DD` / in given languages. |
| `maxReviewsPerApp` | `100` | Fetch cap per app per run (per country). |
| `maxCountries` | `5` | Uses the first N entries of `countries` per app. |
| `backfillMax` | `500` | History cap in backfill mode. |
| `concurrency` | `4` | Parallel fetch jobs (1–16). |
| `appOverrides` | `{}` | Per-app tweaks keyed by `platform:appId`: a `countries` list, `currentVersion` (pin the canary to that version), `baselineVersion`. |

### Output: dataset, alerts and the HTML report

Every review lands in the Actor's default dataset (downloadable as JSON, CSV, Excel, or HTML):

```json
{
    "recordType": "review",
    "platform": "google-play",
    "appId": "com.spotify.music",
    "appName": "Spotify: Music and Podcasts",
    "country": "us",
    "reviewId": "gp:AFDFQo08…",
    "rating": 1,
    "title": null,
    "text": "After the update it crashes every time I open my playlists.",
    "author": "Alex M.",
    "reviewedAt": "2026-09-29T18:41:02",
    "firstSeenAt": "2026-09-30T06:00:11.482+00:00",
    "language": "en",
    "version": "9.2.4",
    "thumbsUp": 12,
    "devReply": null,
    "tags": ["Bug", "Update"],
    "sentiment": "negative",
    "isReplyNeeded": true,
    "url": "https://play.google.com/store/apps/details?id=com.spotify.music&reviewId=gp:AFDFQo08…"
}
```

App Store items additionally carry `title` and omit developer replies (Apple's public data does not include them).

| Field | Description |
|---|---|
| `platform` | `google-play` or `app-store` |
| `appId` / `appName` | Store identifiers and resolved listing name |
| `country` / `language` | Country fetched / language detected from the review text (`und` = too short to detect) |
| `rating` | 1–5 stars |
| `title` / `text` | Review content (title only on the App Store) |
| `author` | Reviewer display name |
| `reviewedAt` / `firstSeenAt` | When the review was posted / when this Actor first saw it |
| `version` | App version the review was written against (when available) |
| `thumbsUp` | Helpful votes (Play) or vote count (Apple) |
| `devReply` | Developer reply on Google Play, if posted |
| `tags` | Theme tags: Bug, Performance, Pricing, UX, Feature Request, Account & Login, Ads, Update, Praise |
| `sentiment` | `negative` (1–2★), `neutral` (3★), `positive` (4–5★) |
| `isReplyNeeded` | True for unanswered 1–2★ Google Play reviews |
| `url` | Deep link to the review |

**Key-value store:** the HTML dashboard is stored under `REPORT` in every run (open it from Storage → Key-value store). When no webhook is configured, the generated digest is stored under `DIGEST` instead of being sent.

**Alert payloads:** instant alerts, storm bundles and digests are delivered as simple text to Slack/Discord and as structured JSON (`kind: review | storm | digest`) to generic webhooks — ready for Zapier, Make or n8n.

### How much does it cost to monitor app reviews?

Simple **pay-per-event pricing** — you pay only for what the Actor delivers, and Apify platform usage is included:

| Event | Price | What it covers |
|---|---|---|
| Run start | **$0.00005** per run ($0.05 per 1,000 runs) | One monitoring run (scheduled or manual) |
| Review delivered | **$0.0002** per review (**$0.20 per 1,000**) | Each new review pushed to your dataset — tagging, de-duplication, alerts, digest and report included |

Typical monthly costs:

| Setup | Runs | New reviews/month | Cost |
|---|---|---|---|
| 1 app, daily digest | 30 | ~300 | **≈ $0.06** |
| 2 apps + 1 competitor, every 6h | 120 | ~1,500 | **≈ $0.31** |
| 5 apps, hourly | 720 | ~10,000 | **≈ $2.04** |

Volume discounts apply automatically at higher tiers (10K+ reviews: $0.15/1K · 100K+: $0.10/1K). Nothing is charged for filtered-out or duplicate reviews — only for reviews actually delivered.

### Tips for scheduled review monitoring

- **Schedule it, don't max it** — a modest `maxReviewsPerApp` (50–100) with a frequent schedule beats rare huge pulls; de-duplication guarantees you never miss anything between runs.
- **Let the canary learn first** — the first canary run seeds the baseline and never alerts; verdicts start after `canaryMinVolume` window reviews and `canaryConfirmRuns` consistent evaluations.
- **Filters reduce noise, not memory** — a filtered-out review is still marked as seen, so loosening filters later never re-alerts old reviews.
- **Quiet hours + digest** — enable quiet hours at night and a daily digest in the morning; the digest carries everything that was deferred.
- **Per-app countries** — use `appOverrides` to fetch specific apps only in the countries you actually release to.
- **Route the data anywhere** — filter on `isReplyNeeded` or `sentiment` in your integration to escalate urgent reviews to Sheets, a helpdesk or an on-call channel.

### FAQ, disclaimers and support

#### How do I monitor Google Play and App Store reviews in one place?

Add your Play package names to `googlePlayApps` and your Apple IDs to `appStoreApps`. Both stores land in the same dataset with the same fields, so one export or integration covers everything.

#### Do I need an API key, a Google account or an Apple account?

No. The Actor reads the same public review data you see in the browser — no credentials, no Google Cloud project, no App Store Connect access.

#### How far back can reviews be fetched?

In `backfill` mode: Google Play supports deep paging (throttled only by `backfillMax`), while Apple's public web exposes only the latest ~10 reviews per country — backfill there is shallow, but scheduled monitoring still catches every new review that surfaces in that batch.

#### Can I track my competitors' reviews?

Yes. Add their apps to the competitor fields — their reviews are fetched, tagged and de-duplicated in the same run, and the Competitor Pain Radar summarises their complaint themes, keywords and worst quotes.

#### How does de-duplication work between scheduled runs?

Every review ID is remembered per app, country and language in the Actor's persistent named store (which survives across runs). A review can never be delivered twice, even across different filters, countries or run frequencies.

#### Where do alerts arrive, and how do I stop the spam?

Slack, Discord or any webhook you configure. Anti-spam is built in: mute keywords, quiet hours (deferred to the digest), a daily instant cap, and storm bundling when many critical reviews arrive at once. With no webhook configured, alerts are printed to the run log instead.

#### Can I use the review data via the API or in Python?

Yes — everything lands in a standard Apify dataset, so you can pull it with the Apify API, the Python client (`apify_client`), JavaScript, or the CLI, and export it as JSON, CSV, Excel or HTML:

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
items = client.dataset("DATASET_ID").list_items().items
negative = [r for r in items if r["sentiment"] == "negative"]
```

#### Is it safe and legal?

The Actor only reads publicly available review content — the same as opening the store page in your browser — and stores results in your own Apify storage. Respect the stores' terms when redistributing the data.

#### What are the known limitations?

App Store history is limited to what Apple exposes publicly (the latest ~10 reviews per country — a hard limit of Apple's public web data in 2026). App Store review fetching depends on that public page — if Apple changes its site, App Store fetches may pause until the Actor is updated; Google Play fetches are unaffected. Review `version` and `devReply` appear only when the store exposes them. Analysis is deterministic (rules + word statistics), not AI.

#### Can I run it more often than the schedule — or run it twice by mistake?

Yes, it is safe. De-duplication remembers every review it has delivered, so an extra run never sends duplicate alerts or repeats the digest — it just costs a few seconds of compute. The better habit is one fixed schedule (see the scheduling section above).

#### Bugs or feature requests?

Open an issue on the Actor's **Issues** tab.

### Related Actors

- **Google Drive Folder Downloader** — download and ZIP an entire shared Google Drive folder in the cloud, no API key needed. (Link to its Apify Store page goes here at publication.)

# Actor input Schema

## `googlePlayApps` (type: `array`):

Package names of YOUR Android apps, one per row. Find each package name in its Play Store URL right after ?id= — for Spotify that URL is play.google.com/store/apps/details?id=com.spotify.music, so the package name is com.spotify.music.

## `appStoreApps` (type: `array`):

Apple app IDs of YOUR iOS apps (numbers only), one per row. Find the number in the App Store URL after /id — apps.apple.com/app/id324684580 means the ID is 324684580.

## `competitorGooglePlayApps` (type: `array`):

Rival Android apps in the same package-name format. Their 1–2★ reviews feed the Competitor Pain Radar, which surfaces complaints you can win users with. Leave empty to skip competitor analysis.

## `competitorAppStoreApps` (type: `array`):

Rival iOS apps in the same numeric-ID format. Also feeds the Competitor Pain Radar. Leave empty to skip competitor analysis.

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

Which country stores to watch, as 2-letter codes, one per row: us (United States), in (India), gb (United Kingdom), de (Germany), br (Brazil)… Each country adds fetch work, so keep the list short for cheap daily runs.

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

Review languages for Google Play, as 2-letter codes: en (English), hi (Hindi), es (Spanish)… App Store reviews always come in all languages of the chosen country, so this setting is ignored there.

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

What this run should do. Monitor: fetch only reviews that arrived since the last run — the right choice for a scheduled Actor. Release Canary: judge how your latest release is landing. Competitor Radar: analyze competitors only. Full Report: one-shot everything. Backfill: fetch review history (App Store's public page exposes only the latest ~10 per country).

## `canaryWindowHours` (type: `integer`):

How far back to look when judging the current release, in hours. 48 means reviews from the last 2 days count towards the verdict.

## `canaryWatchOneStarMultiplier` (type: `number`):

Raise a WATCH alert when the 1★ share in the window is this many times your app's usual level. 2 means: double the normal 1★ rate is worth watching.

## `canaryRollbackOneStarMultiplier` (type: `number`):

Consider rolling back the release when the 1★ share reaches this many times the usual level — something in the new version is clearly hurting users.

## `canaryImmediateRollbackMultiplier` (type: `number`):

Drop everything: the 1★ share is this many times the usual level, which points to a severe regression (crashes, broken login…). Roll back immediately.

## `canaryMinVolume` (type: `integer`):

Never judge a release on a tiny sample: at least this many reviews must arrive in the window before any verdict is issued.

## `canaryCrashShareThreshold` (type: `number`):

A fraction of reviews in the window that mention crashes, freezes or 'broken' counts as a crash spike. 0.08 means 8% of reviews. Between 0 and 1.

## `canaryConfirmRuns` (type: `integer`):

A verdict must repeat in this many consecutive runs before an alert fires — one bad hour alone won't page you. Higher = fewer false alarms, slower reaction.

## `slackWebhookUrl` (type: `string`):

Incoming webhook of the Slack channel that receives alerts (create one at api.slack.com → Incoming Webhooks). Leave empty to skip Slack.

## `discordWebhookUrl` (type: `string`):

Webhook of the Discord channel that receives alerts (Server Settings → Integrations → Webhooks → New Webhook → Copy URL). Leave empty to skip Discord.

## `genericWebhookUrl` (type: `string`):

Any HTTP(S) endpoint that accepts a JSON POST — Zapier, Make, n8n, or your own API. The full alert is sent as JSON. Leave empty to skip.

## `quietHoursEnabled` (type: `boolean`):

Pause instant alerts during sleeping hours. Reviews that arrive at night are not lost — they appear in the next digest instead.

## `quietHoursStartHour` (type: `integer`):

Hour of the day (0–23) when instant alerts go silent. 22 = 10 PM.

## `quietHoursEndHour` (type: `integer`):

Hour of the day (0–23) when instant alerts resume. 8 = 8 AM.

## `quietHoursTimezone` (type: `string`):

Timezone for the quiet hours, as an IANA name: Asia/Kolkata, Europe/London, America/New_York…

## `muteKeywords` (type: `array`):

Reviews containing any of these words never trigger an instant alert (case-insensitive) — useful for known noise, e.g. 'update', 'beta'. They still appear in the dataset and digest.

## `maxInstantPerDay` (type: `integer`):

Daily cap on instant alerts so a busy day can't flood your chat. Extra important reviews still land in the digest. Set 0 for digest-only (no instant alerts at all).

## `stormWindowMinutes` (type: `integer`):

The time span, in minutes, watched for alert bursts. Used together with the next field to detect 'review storms'.

## `stormCount` (type: `integer`):

If this many instant alerts would fire within the storm window, they are bundled into ONE storm alert instead — e.g. a viral spike or a server outage suddenly generating hundreds of reviews.

## `reportEnabled` (type: `boolean`):

Build the HTML dashboard after each run.

## `reportPublicLink` (type: `boolean`):

Add a direct link to the report in digests. The link opens this run in Apify Console (you must be signed in to your Apify account). The report itself is always stored in the run's Key-value store under the key REPORT.

## `reportMaxReviewsShown` (type: `integer`):

Maximum example reviews listed in each section of the report — keeps the page readable at a glance.

## `digestFrequency` (type: `string`):

How often a digest may be sent: daily, weekly, or off. A digest goes out once the chosen period has elapsed since the previous one.

## `digestSectionActions` (type: `boolean`):

Reviews that deserve a reply: low ratings, questions, bug reports your team hasn't answered yet.

## `digestSectionRelease` (type: `boolean`):

How the current release is performing: canary verdicts and the rating trend since launch.

## `digestSectionCompetitors` (type: `boolean`):

Top pain points mined from your competitors' negative reviews, with example quotes.

## `digestSectionHealth` (type: `boolean`):

Ratings, review volume and crash mentions per app and country — the overall pulse.

## `keywordMining` (type: `boolean`):

Find the words and phrases users complain about most (e.g. 'battery', 'login', 'ads') and count them across reviews. Powers the competitor pain radar and the report.

## `minPhraseLength` (type: `integer`):

Words shorter than this are ignored while mining keywords — it filters out filler like 'the' or 'and'. Raise to 5–6 for cleaner keyword lists.

## `filterMinRating` (type: `integer`):

Keep only reviews rated at least this many stars. 0 = no minimum. Example: minimum 1 together with a maximum of 2 keeps only the angry 1–2★ reviews.

## `filterMaxRating` (type: `integer`):

Keep only reviews rated at most this many stars. 0 = no maximum.

## `filterIncludeKeywords` (type: `array`):

Reviews must contain at least one of these words or phrases (case-insensitive) to be kept. Empty = keep all. Example: crash, refund, login.

## `filterExcludeKeywords` (type: `array`):

Reviews containing any of these words or phrases are dropped (case-insensitive) — handy for spam or giveaway campaigns. Empty = drop nothing.

## `filterMinDate` (type: `string`):

Keep only reviews posted on or after this date, written as YYYY-MM-DD (e.g. 2026-09-01). Leave empty for no date limit.

## `filterLanguages` (type: `array`):

Keep only reviews in these languages, as 2-letter codes: en, hi, es… Empty = keep all languages.

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

Maximum reviews fetched per app per run (per country). For daily monitoring 50–100 is plenty; only Backfill mode ever needs more.

## `maxCountries` (type: `integer`):

Cap on how many countries are fetched per app — the first N entries of the Countries list above. Keeps cost predictable if the list grows.

## `backfillMax` (type: `integer`):

Maximum historical reviews fetched per app in Backfill mode. Google Play supports deep history; App Store's public page exposes only the latest ~10 reviews per country.

## `concurrency` (type: `integer`):

How many app+country fetches run at the same time. 2–4 is polite to the stores; raise only if you monitor many apps and need speed.

## `appOverrides` (type: `object`):

Optional per-app settings keyed by 'platform:appId'. Example shape: {"google-play:com.example.app": {"currentVersion": "2.1.0", "baselineVersion": "2.0.9", "countries": \["us", "in"]}}.

## Actor input object example

```json
{
  "googlePlayApps": [
    "com.spotify.music"
  ],
  "appStoreApps": [
    "324684580"
  ],
  "competitorGooglePlayApps": [],
  "competitorAppStoreApps": [],
  "countries": [
    "us"
  ],
  "languages": [
    "en"
  ],
  "mode": "monitor",
  "canaryWindowHours": 48,
  "canaryWatchOneStarMultiplier": 2,
  "canaryRollbackOneStarMultiplier": 4,
  "canaryImmediateRollbackMultiplier": 6,
  "canaryMinVolume": 10,
  "canaryCrashShareThreshold": 0.08,
  "canaryConfirmRuns": 2,
  "slackWebhookUrl": "",
  "discordWebhookUrl": "",
  "genericWebhookUrl": "",
  "quietHoursEnabled": false,
  "quietHoursStartHour": 22,
  "quietHoursEndHour": 8,
  "quietHoursTimezone": "Asia/Kolkata",
  "muteKeywords": [],
  "maxInstantPerDay": 5,
  "stormWindowMinutes": 120,
  "stormCount": 5,
  "reportEnabled": true,
  "reportPublicLink": true,
  "reportMaxReviewsShown": 10,
  "digestFrequency": "daily",
  "digestSectionActions": true,
  "digestSectionRelease": true,
  "digestSectionCompetitors": true,
  "digestSectionHealth": true,
  "keywordMining": true,
  "minPhraseLength": 4,
  "filterMinRating": 0,
  "filterMaxRating": 0,
  "filterIncludeKeywords": [],
  "filterExcludeKeywords": [],
  "filterMinDate": "",
  "filterLanguages": [],
  "maxReviewsPerApp": 100,
  "maxCountries": 5,
  "backfillMax": 500,
  "concurrency": 4,
  "appOverrides": {
    "google-play:com.example.app": {
      "currentVersion": "2.1.0",
      "baselineVersion": "2.0.9",
      "countries": [
        "us",
        "in"
      ]
    }
  }
}
```

# Actor output Schema

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

No description

## `report` (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 = {
    "googlePlayApps": [
        "com.spotify.music"
    ],
    "appStoreApps": [
        "324684580"
    ],
    "countries": [
        "us"
    ],
    "languages": [
        "en"
    ],
    "mode": "monitor",
    "canaryWindowHours": 48,
    "canaryWatchOneStarMultiplier": 2,
    "canaryRollbackOneStarMultiplier": 4,
    "canaryImmediateRollbackMultiplier": 6,
    "canaryMinVolume": 10,
    "canaryCrashShareThreshold": 0.08,
    "canaryConfirmRuns": 2,
    "quietHoursEnabled": false,
    "quietHoursStartHour": 22,
    "quietHoursEndHour": 8,
    "quietHoursTimezone": "Asia/Kolkata",
    "maxInstantPerDay": 5,
    "stormWindowMinutes": 120,
    "stormCount": 5,
    "reportEnabled": true,
    "reportPublicLink": true,
    "reportMaxReviewsShown": 10,
    "digestFrequency": "daily",
    "digestSectionActions": true,
    "digestSectionRelease": true,
    "digestSectionCompetitors": true,
    "digestSectionHealth": true,
    "keywordMining": true,
    "minPhraseLength": 4,
    "filterMinRating": 0,
    "filterMaxRating": 0,
    "maxReviewsPerApp": 100,
    "maxCountries": 5,
    "backfillMax": 500,
    "concurrency": 4,
    "appOverrides": {
        "google-play:com.example.app": {
            "currentVersion": "2.1.0",
            "baselineVersion": "2.0.9",
            "countries": [
                "us",
                "in"
            ]
        }
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("vasram/app-review-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 = {
    "googlePlayApps": ["com.spotify.music"],
    "appStoreApps": ["324684580"],
    "countries": ["us"],
    "languages": ["en"],
    "mode": "monitor",
    "canaryWindowHours": 48,
    "canaryWatchOneStarMultiplier": 2,
    "canaryRollbackOneStarMultiplier": 4,
    "canaryImmediateRollbackMultiplier": 6,
    "canaryMinVolume": 10,
    "canaryCrashShareThreshold": 0.08,
    "canaryConfirmRuns": 2,
    "quietHoursEnabled": False,
    "quietHoursStartHour": 22,
    "quietHoursEndHour": 8,
    "quietHoursTimezone": "Asia/Kolkata",
    "maxInstantPerDay": 5,
    "stormWindowMinutes": 120,
    "stormCount": 5,
    "reportEnabled": True,
    "reportPublicLink": True,
    "reportMaxReviewsShown": 10,
    "digestFrequency": "daily",
    "digestSectionActions": True,
    "digestSectionRelease": True,
    "digestSectionCompetitors": True,
    "digestSectionHealth": True,
    "keywordMining": True,
    "minPhraseLength": 4,
    "filterMinRating": 0,
    "filterMaxRating": 0,
    "maxReviewsPerApp": 100,
    "maxCountries": 5,
    "backfillMax": 500,
    "concurrency": 4,
    "appOverrides": { "google-play:com.example.app": {
            "currentVersion": "2.1.0",
            "baselineVersion": "2.0.9",
            "countries": [
                "us",
                "in",
            ],
        } },
}

# Run the Actor and wait for it to finish
run = client.actor("vasram/app-review-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 '{
  "googlePlayApps": [
    "com.spotify.music"
  ],
  "appStoreApps": [
    "324684580"
  ],
  "countries": [
    "us"
  ],
  "languages": [
    "en"
  ],
  "mode": "monitor",
  "canaryWindowHours": 48,
  "canaryWatchOneStarMultiplier": 2,
  "canaryRollbackOneStarMultiplier": 4,
  "canaryImmediateRollbackMultiplier": 6,
  "canaryMinVolume": 10,
  "canaryCrashShareThreshold": 0.08,
  "canaryConfirmRuns": 2,
  "quietHoursEnabled": false,
  "quietHoursStartHour": 22,
  "quietHoursEndHour": 8,
  "quietHoursTimezone": "Asia/Kolkata",
  "maxInstantPerDay": 5,
  "stormWindowMinutes": 120,
  "stormCount": 5,
  "reportEnabled": true,
  "reportPublicLink": true,
  "reportMaxReviewsShown": 10,
  "digestFrequency": "daily",
  "digestSectionActions": true,
  "digestSectionRelease": true,
  "digestSectionCompetitors": true,
  "digestSectionHealth": true,
  "keywordMining": true,
  "minPhraseLength": 4,
  "filterMinRating": 0,
  "filterMaxRating": 0,
  "maxReviewsPerApp": 100,
  "maxCountries": 5,
  "backfillMax": 500,
  "concurrency": 4,
  "appOverrides": {
    "google-play:com.example.app": {
      "currentVersion": "2.1.0",
      "baselineVersion": "2.0.9",
      "countries": [
        "us",
        "in"
      ]
    }
  }
}' |
apify call vasram/app-review-monitor --silent --output-dataset

```

## MCP server setup

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