# App Store & Google Play Reviews Scraper API | iOS + Android (`aaronoptimus/app-store-reviews-scraper`) Actor

Scrape App Store and Google Play reviews in one run: ratings, text, dates, versions, developer replies, sentiment and topics, app details and top charts. One stable schema, CSV/Excel-ready. Multi-country, star/date/keyword filters, new-reviews-only monitoring. From $0.10 per 1,000 reviews.

- **URL**: https://apify.com/aaronoptimus/app-store-reviews-scraper.md
- **Developed by:** [Aaron Tuttle](https://apify.com/aaronoptimus) (community)
- **Categories:** Developer tools, Marketing, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.10 / 1,000 reviews

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
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 API | iOS + Android

**Scrape App Store and Google Play reviews in one run, in one stable schema, from $0.10 per 1,000 reviews.** Ratings, review text, dates, app versions, developer replies, free sentiment and topic tags, app details, search results and top charts, ready for CSV, Excel, Google Sheets or your API.

| Both stores, one run | App Store (iOS) | Google Play (Android) |
|---|---|---|
| Reviews: stars, title, text, date, reviewer name, link | ✅ | ✅ |
| App version and helpful votes per review | ✅ | ✅ |
| Developer replies (text and date) | Partly (Apple's reviews feed leaves them out) | ✅ |
| Sentiment + topics per review (free, no AI service) | ✅ | ✅ |
| App details: rating, histogram, price, version, category… | ✅ | ✅ + installs, data safety |
| Search by name, top charts per category | ✅ | ✅ |
| Many countries in one run | ✅ + sweep presets (`top10`, `top25`, `all`) | ✅ + many languages |
| Same fields for both stores (items or flat rows) | ✅ | ✅ |

Paste app URLs, Apple app ids or Android package names (or search by name), pick countries, click **Start**.

### 💸 Pricing

Pay per result. Compute is included, there are no platform fees, and summaries, sentiment/topic tags and the competitor comparison are free. Prices drop as your Apify plan's discount tier goes up:

| Event | Free plan | Bronze | Silver | Gold | Platinum | Diamond |
|---|---|---|---|---|---|---|
| Review | $0.00015 | $0.00013 | $0.00012 | $0.0001 | $0.0001 | $0.0001 |
| App details (per app × country) | $0.002 | $0.002 | $0.0015 | $0.001 | $0.0008 | $0.0006 |
| Search result or top-chart entry | $0.0006 | $0.0005 | $0.0004 | $0.0003 | $0.0003 | $0.0002 |
| Run start | $0.001 | $0.001 | $0.001 | $0.001 | $0.001 | $0.001 |

That is **$0.15 per 1,000 reviews on the free plan and from $0.10 per 1,000 reviews** on Gold and above.

| Example | Free plan | Gold |
|---|---|---|
| 1,000 reviews | $0.15 | $0.10 |
| 1 Google Play app with 200 reviews + app details | $0.033 | $0.022 |
| 10 Google Play apps × 200 reviews + app details | $0.32 | $0.21 |
| Daily monitoring of 5 apps, ~20 new reviews a day, for 30 days | ≈ $0.12 | ≈ $0.09 |
| 100 apps, details only | $0.20 | $0.10 |
| Top 100 of one chart | $0.061 | $0.031 |

**Never charged:** apps that can't be found, storefronts where an app isn't sold, requests the stores refuse after all retries, reviews outside your date / star / keyword / reply filters, reviews already delivered before (monitoring mode), and duplicate reviews across countries. To cap a run, set Apify's *Maximum cost per run*: the scraper stops cleanly at the limit, across all event types and parallel sessions. Free-plan accounts are limited to 5 apps and 50 reviews per app per run (Apify Store rule); the log says so.

### 🚀 Quick start

**1. Reviews of one app from both stores** (the prefilled input):

```json
{ "apps": ["https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580", "https://play.google.com/store/apps/details?id=com.spotify.music"], "countries": "us", "maxReviewsPerApp": 200 }
```

**2. Export reviews to Excel**: complaints only (1–2★) from the last 30 days, as flat rows. Run it, then click **Export → Excel** (or CSV) on the results:

```json
{ "apps": ["com.spotify.music", "324684580"], "countries": "us, gb, de", "maxRating": 2, "reviewsNewerThan": "30 days", "outputFormat": "rows" }
```

**3. Only new reviews, every day**: schedule this as a task and connect Slack or a webhook (see *Monitor new app reviews daily*):

```json
{ "apps": ["com.spotify.music", "324684580"], "onlyNewReviews": true, "includeAppDetails": false, "reviewSort": "newest", "maxReviewsPerApp": 200 }
```

### How to scrape App Store reviews

1. Paste App Store URLs (`https://apps.apple.com/us/app/…/id324684580`) or numeric Apple ids into **Apps**. iOS bundle ids work too when *Stores* is set to App Store.
2. Set **Countries**. Each App Store storefront has its own reviews, so more countries means more reviews. For a sweep, use a preset: `top5`, `top10`, `top25`, `top50` or `all` (every storefront). Reviews that show up in several storefronts are delivered once, and the run reports reviews per country.
3. Choose **Max reviews per app** (per country), **Review order** and any filters, then click **Start**.

Apple shows at most the 500 most recent reviews per app per country (see *Why fewer reviews than requested?*). App Store reviews have a title, app version and helpful votes.

### How to scrape Google Play reviews

1. Paste Google Play URLs (`https://play.google.com/store/apps/details?id=com.spotify.music`) or package names (`com.spotify.music`).
2. Set **Languages** (e.g. `en, de`): Google Play returns a different set of reviews per language. Thousands of reviews per app are possible.
3. Use **Only reviews newer than** with **Newest first** so paging stops at the date you need.

Google Play reviews include the app version, thumbs-up count and the developer's reply with its date.

### Export App Store and Google Play reviews to CSV, Excel or Google Sheets

- Set **Output format** to *Flat rows*: one row per record, with the **same columns on every row and in every run**, so spreadsheets, BI imports and pivot tables keep working.
- After the run, use **Export** for CSV, Excel, JSON or XML, or the dataset API URL in Google Sheets (`=IMPORTDATA(...)` with `format=csv`), Make, Zapier or n8n.
- The dataset has ready-made views: **Overview**, **Reviews**, **App details**, **Search results & top charts**, **Per-app summary**, **Competitor comparison** and **Flat rows**.

### Monitor new app reviews daily

Turn on **Only new reviews (monitoring)**. The scraper remembers, per store × app × country, which reviews it has already delivered (in your account's key-value store `app-reviews-monitor`), and **delivers and charges only new ones**. The first run is the baseline: it delivers what it fetches. Later runs that find nothing new succeed with 0 reviews and cost only the run start. The run's `OUTPUT` record and the per-app summaries report `newReviewsCount`.

Recipe:

1. Create a **task** with your apps, `"onlyNewReviews": true`, `"reviewSort": "newest"` and, usually, `"includeAppDetails": false` (details are charged on every run).
2. **Schedule** it (Apify Console → Schedules) daily or hourly.
3. Get the new reviews where you work:
   - **Slack**: add the Slack integration to the task, or a webhook on *Run succeeded* that posts `{{resource.defaultDatasetId}}` items to a Slack incoming webhook.
   - **Webhook / your backend**: an Apify webhook on `ACTOR.RUN.SUCCEEDED` calls your URL; fetch `https://api.apify.com/v2/datasets/{defaultDatasetId}/items?clean=true`.
   - **Email, Sheets, Jira, Zendesk**: Make, Zapier or n8n with the Apify app, trigger "Task run finished".
4. Optional: `"maxRating": 2` to alert on complaints only, `"developerReplyFilter": "withoutReply"` for reviews still waiting for an answer, or `"includeKeywords": ["refund", "crash"]`. Filtered-out reviews are remembered too, so they never come back as "new".

Use **Monitor name** (`monitorKey`) to keep separate monitors apart, for example one per team.

### Compare competitor app reviews

List 2 or more apps (any mix of stores) and keep **Competitor comparison** on. You get one free `comparison` item with, per app: store rating, average rating of the fetched reviews, **% of 1–2★ reviews**, sentiment counts, **top topics in negative reviews**, **developer reply rate**, **median reply time** (hours), and **average rating by week and by app version**. In flat rows mode it becomes one row per app × metric, ready for a pivot table.

```json
{ "apps": ["com.spotify.music", "com.apple.android.music", "deezer.android.app"], "maxReviewsPerApp": 500, "reviewsNewerThan": "90 days" }
```

Every review also carries `sentiment` (`positive`, `neutral`, `negative`: from the stars, nudged by a small word list with negation) and `topics` (`crash-bug`, `login-account`, `ads`, `pricing-subscription`, `performance`, `ui-ux`, `support`, `feature-request`, `privacy`), from keyword dictionaries in English, Spanish, German, French and Portuguese. They're computed locally, cost nothing, and can be turned off.

### ✅ Built for the problems other review scrapers have

**Why fewer reviews than requested?** The scraper says why, in the run log and, with app details on, in the app item's `warnings`:

- **Apple's cap:** Apple shows at most the 500 most recent reviews per app per country. Asking for 2,000 App Store reviews in one country gets 500 at most. For more, add countries or use a sweep preset (`top25`, `all`): each storefront has its own 500, and duplicates are removed.
- **Your filters:** reviews outside your date, star, keyword or reply filters are dropped, and they're free. `OUTPUT.reviewsFiltered` counts them.
- **The app has fewer reviews** in that country or language.
- **Your cost limit:** the run stops cleanly at *Maximum cost per run* and says so.

**No schema drift.** Every review, app, listing and summary item has exactly the same fields in the same order for both stores, in every run. Missing data is `null`, never a missing key. New fields are only ever added at the end (as `sentiment` and `topics` were), and flat rows have a fixed column list (below).

**The right app and country.** Every item carries `store`, `appId`, `appTitle` and `country`, and the country always comes from your *Countries* setting, so you can check exactly what you got.

**One error never crashes the run.** Requests that get a 429, 403, 5xx or network error are retried on a fresh session with capped backoff. An app that still fails becomes an `error` item, **not charged**, and the rest of the run continues. Runs resume after restarts without charging anything twice.

**Dates and versions.** Dates are ISO 8601 (UTC). App versions are filled whenever the store publishes them.

### 📊 Output fields

**Review** (`type: "review"`), same fields for both stores:

| Field | Description |
|---|---|
| `store` | `ios` or `android` |
| `appId`, `appTitle` | Apple id / package name and app name |
| `country`, `language` | Storefront and language the review was fetched for |
| `reviewId` | The store's review id |
| `userName` | Reviewer's public display name |
| `rating` | Stars, 1–5 |
| `title`, `text` | Review title (App Store) and text |
| `date` | Review date, ISO 8601 |
| `version` | App version the review was written for |
| `thumbsUp`, `voteCount` | Helpful votes |
| `developerReplyText`, `developerReplyDate` | Developer's reply |
| `url` | Link to the review / reviews page |
| `scrapedAt` | When it was scraped |
| `sentiment` | `positive`, `neutral` or `negative` (`null` if tagging is off) |
| `topics` | Topic ids, e.g. `["crash-bug", "ads"]` (`null` if tagging is off) |

**App details** (`type: "app"`): title, developer and developer id, rating, rating count and 1–5 star histogram, price, currency, free / in-app purchase flags, installs (Android), version, released / updated dates, release notes, category, content rating, size, minimum OS, store URL, icon, screenshot count, data safety summary (Android; `privacy` is `null` for App Store apps), `warnings`.

**Per-app summary** (`type: "summary"`, free, one per app × country): average rating, counts by star, % negative, average rating by version, top words in 1–2★ reviews, sentiment counts, topic counts, top negative topics, developer reply rate, oldest / newest review date, and `newReviewsCount` in monitoring mode.

**Listing** (`type: "listing"`): `listingType` (`search` or `top-chart`), `query` or `collection`/`category`, `rank`, `appId`, `title`, `developer`, `rating`, `price`, `currency`, `free`, `url`. **Comparison** (`type: "comparison"`): see above. **Error** (`type: "error"`): app, country and what went wrong; never charged.

**Flat rows** (Output format: rows): every row has these columns, in this order, in every run. Columns are only ever added at the end.

`recordType`, `store`, `appId`, `appTitle`, `country`, `language`, `reviewId`, `userName`, `rating`, `title`, `text`, `date`, `version`, `thumbsUp`, `developerReplyText`, `developerReplyDate`, `developer`, `developerId`, `ratingCount`, `price`, `currency`, `free`, `installs`, `category`, `contentRating`, `released`, `updated`, `releaseNotes`, `listingType`, `query`, `collection`, `rank`, `summaryMetric`, `summaryKey`, `summaryValue`, `url`, `error`, `scrapedAt`, `sentiment`, `topics`

`recordType` is `review`, `app`, `listing`, `summary`, `comparison` or `error`. Summary and comparison metrics use `summaryMetric` / `summaryKey` / `summaryValue` (e.g. `averageRatingByWeek` / `2026-W40` / `3.8`). `topics` is one cell like `crash-bug; ads`.

A review item (Google Play):

```json
{
  "type": "review",
  "store": "android",
  "appId": "com.spotify.music",
  "appTitle": "Spotify: Music and Podcasts",
  "country": "us",
  "language": "en",
  "reviewId": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d51",
  "userName": "Priya S",
  "rating": 1,
  "title": null,
  "text": "Keeps crashing on my phone since the latest update. Ads every two songs and shuffle is broken.",
  "date": "2026-10-04T15:02:44.000Z",
  "version": "9.0.88.100",
  "thumbsUp": 48,
  "voteCount": null,
  "developerReplyText": "Hi Priya, sorry about the crashes. Please reach out to us on the Spotify Community so we can take a closer look.",
  "developerReplyDate": "2026-10-05T08:10:00.000Z",
  "url": "https://play.google.com/store/apps/details?id=com.spotify.music&reviewId=b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d51",
  "scrapedAt": "2026-10-06T09:00:00.000Z",
  "sentiment": "negative",
  "topics": ["crash-bug", "ads"]
}
```

### Input reference

| Input | Default | What it does |
|---|---|---|
| `apps` | Spotify on both stores | App Store / Google Play URLs, Apple ids, package names (bundle ids with *Stores* = App Store) |
| `searchTerms` | none | Find apps by name; the top `maxSearchResults` per store are fetched |
| `stores` | `both` | `both`, `ios` or `android` |
| `countries` | `us` | Country codes, e.g. `us, gb, de`; App Store sweep presets `top5`, `top10`, `top25`, `top50`, `all` |
| `languages` | `en` | Languages for app details and Google Play reviews |
| `includeAppDetails` | `true` | One app details item per app × country (charged) |
| `includeReviews` | `true` | Review items (charged per review) |
| `maxReviewsPerApp` | `200` | Per app, country and language |
| `reviewSort` | `newest` | `newest`, `rating` or `helpfulness` |
| `reviewsNewerThan` | none | Date (`2026-01-31`) or period (`30 days`) |
| `minRating`, `maxRating` | `1`, `5` | Star range |
| `includeKeywords`, `excludeKeywords` | none | Keep / drop reviews containing these words (case- and accent-insensitive) |
| `developerReplyFilter` | `any` | `any`, `withReply` or `withoutReply` |
| `addSentimentAndTopics` | `true` | Free `sentiment` and `topics` on every review |
| `includeSummary` | `true` | Free per-app summary per country |
| `includeComparison` | `true` | Free comparison item when 2+ apps are covered |
| `onlyNewReviews` | `false` | Monitoring: deliver and charge only reviews not delivered before |
| `monitorKey` | `default` | Name of the monitor (keeps independent monitors apart) |
| `includeSearchResults` | `false` | Also deliver search result lists (charged per entry) |
| `maxSearchResults` | `20` | Apps per search and entries per top chart |
| `topCharts` | none | Charts as `collection:category`, e.g. `topselling_free:GAME` |
| `outputFormat` | `items` | `items` (JSON) or `rows` (flat, spreadsheet-ready) |
| `proxyConfiguration` | off | Apify Proxy, if you see many retries |
| `maxConcurrency` | `4` | Parallel sessions (1–10) |
| `maxRetries` | `6` | Retries per request on a fresh session |

### More input examples

Find competitors by name and get their details and reviews, plus the search result lists:

```json
{ "searchTerms": ["budget planner", "expense tracker"], "stores": "both", "maxSearchResults": 10, "includeSearchResults": true, "maxReviewsPerApp": 100 }
```

App Store sweep: the 25 biggest storefronts, newest 100 reviews each, reviews only:

```json
{ "apps": ["324684580"], "stores": "ios", "countries": "top25", "maxReviewsPerApp": 100, "includeAppDetails": false }
```

Top charts only:

```json
{ "topCharts": ["topselling_free:GAME", "topgrossing:FINANCE", "topfreeapplications:6014", "topgrossingapplications"], "countries": "us, jp", "maxSearchResults": 50 }
```

Google Play chart names: `topselling_free`, `topselling_paid`, `topgrossing`, optionally with a category such as `GAME`, `GAME_PUZZLE`, `FINANCE`, `PRODUCTIVITY`, `SOCIAL`. App Store feeds: `topfreeapplications`, `toppaidapplications`, `topgrossingapplications`, `newapplications`, `newfreeapplications`, `newpaidapplications`, optionally with a numeric genre id (`6014` Games, `6015` Finance, `6005` Social Networking, `6007` Productivity).

### 🔌 Integrations: API, Make, Zapier, n8n, MCP

JavaScript:

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

const client = new ApifyClient({ token: 'MY_APIFY_TOKEN' });
const run = await client.actor('AaronOptimus/app-store-reviews-scraper').call({
    apps: ['com.spotify.music', 'https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580'],
    countries: 'us',
    maxReviewsPerApp: 100,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("MY_APIFY_TOKEN")
run = client.actor("AaronOptimus/app-store-reviews-scraper").call(run_input={
    "apps": ["com.spotify.music", "324684580"],
    "countries": "us",
    "maxReviewsPerApp": 100,
})
items = client.dataset(run["defaultDatasetId"]).list_items().items
```

- **Make, Zapier, n8n:** use the Apify app/node: "Run Actor" or "Run task", then "Get dataset items". Pair with *Only new reviews* for alerts.
- **Webhooks and Slack:** Apify integrations on the task (Slack, email, webhooks, GitHub, Google Drive).
- **AI agents (MCP):** call the scraper as a tool through Apify's MCP server: "get the latest 1-star reviews of app X and their top topics" becomes one structured tool call.

### 🎯 Use cases

- **ASO and product teams:** ratings by version and by week, the complaints behind a rating drop, iOS vs Android side by side.
- **Competitor monitoring:** weekly comparison of your category's apps; top negative topics and reply rates.
- **Support and QA:** new 1–2★ reviews without a developer reply, routed to the right team.
- **Investors and analysts:** review velocity, rating trends and installs across a portfolio and markets.
- **Research and AI:** a clean, tagged corpus of reviews with ratings, dates and versions.

### 💡 Tips

- **Big lists:** raise *Parallel sessions* to 8–10, turn off app details if you only need reviews, and use *Only reviews newer than* with *Newest first*.
- **Sweeps** are polite on purpose: App Store requests are spaced out across all parallel sessions, so a `top50` or `all` sweep takes a few minutes per app.
- **Rate limits:** proxy is off by default because both stores accept moderate datacenter traffic. If the log shows many retries, turn on Apify Proxy or lower *Parallel sessions*.
- **App Store privacy labels** aren't included (`privacy` is `null` for App Store apps); Google Play apps get the data safety summary.

### ❓ FAQ

**How many reviews can I get?** Google Play: thousands per app, country and language (150 per page, paginated). App Store: up to the 500 most recent per app per country, so a `top25` sweep can return up to 12,500 per app.

**Is Apple's reviews feed reliable?** Mostly. Apple's public reviews feed is tried first on every run. On days when it comes back empty or unavailable for an app (it did briefly in early October 2026), the scraper falls back to the reviews shown on the public App Store web page: about 10 per device view, so 10–40 per app per country, chosen by Apple, with developer replies but without app version or helpful votes. The app item's `warnings` and the log say when that happened, and `OUTPUT.appleWebPageFallbacks` counts it.

**Does it sort by rating on the App Store?** Apple only offers "most recent" and "most helpful". For *Highest rating first*, the most recent reviews are fetched and sorted by rating locally.

**How does "Only new reviews" decide what's new?** A review is new if its id wasn't delivered (or filtered out) by an earlier run with the same monitor name, and it isn't older than the newest review seen before minus 3 days (stores sometimes publish reviews late). Edited reviews keep their id, so they aren't sent again.

**How accurate are sentiment and topics?** They're keyword-based: fast, free, transparent and consistent between runs, but not an AI model. Sentiment leans on the stars, so a 1★ review is always negative and a 5★ review always positive. For nuanced analysis, feed the review text to your LLM of choice; the tags are a good first filter.

**What happens when a store blocks a request?** It's retried on a fresh session with capped backoff (and a fresh IP with Apify Proxy). If an app still fails, it's delivered as an error item and **not charged**; the rest of the run continues.

**Can I search for apps by name?** Yes. *Search terms* are searched in the selected store(s) and the top *Max results per search* apps are fetched with details and reviews. Turn on *Include search results* to also get the ranked result list.

**Is it legal?** The scraper only reads publicly available store pages and feeds: no login, no CAPTCHA solving, no private APIs. Reviewer names are the public display names the stores show to everyone. You're responsible for using the data in line with the stores' terms and your local laws, including data-protection rules if you store reviewer names.

### 🛠 Support

Found a problem or need a feature? Open an issue on the **Issues** tab. See the **Changelog** for updates.

# Changelog

This Actor's version history is a separate document: https://apify.com/aaronoptimus/app-store-reviews-scraper/changelog.md

# Actor input Schema

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

One app per line: an App Store URL (https://apps.apple.com/us/app/…/id324684580), a Google Play URL (https://play.google.com/store/apps/details?id=com.spotify.music), a numeric Apple app id (324684580) or an Android package name (com.spotify.music). Each app is fetched for every country you list.

## `searchTerms` (type: `array`):

Optional. App names or keywords to search in the selected store(s). The top "Max results per search / chart" apps of each search are fetched like the apps above (details + reviews). Turn on "Include search results" to also get the search result list itself.

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

Which store(s) to search and accept apps from. With "both", URLs and ids decide the store per app; search terms and top charts are run in both stores.

## `countries` (type: `string`):

Store country code(s), e.g. us or us, gb, de. Every app is fetched for every country (details, reviews and charts differ by country). App Store sweep presets: top5, top10, top25, top50 or all (every App Store storefront) fetch App Store apps in those storefronts, de-duplicate reviews across them and report reviews per country; Google Play apps, searches and charts use the codes you list (or us). Apple shows at most 500 recent reviews per app per country, so more countries means more App Store reviews.

## `languages` (type: `string`):

Language code(s) for app details and Google Play reviews, e.g. en or en, de. Google Play returns a different set of reviews per language, so each listed language is fetched; Apple reviews are per country only (the first language is used for details).

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

One item per app × country with title, developer, rating, rating count and histogram, price, installs (Android), version, release notes, category, content rating, size, minimum OS, data safety summary (Google Play) and more. Charged per app detail.

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

One item per review: reviewer name as displayed, rating, title, text, date, app version, helpful votes, developer reply, a link, plus free sentiment and topic tags. Charged per review delivered.

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

Per app, country and language. Google Play is paginated, so thousands are possible. App Store: Apple shows at most the 500 most recent reviews per app per country (use more countries or a sweep preset for more). You only pay for reviews delivered. Free-plan accounts get at most 50.

## `reviewSort` (type: `string`):

Newest first, highest rating first, or most helpful first. Apple's feed has no rating order, so for App Store apps the most recent reviews are fetched and sorted by rating. Reviews taken from the App Store web page are sorted the same way.

## `reviewsNewerThan` (type: `string`):

Optional. A date like 2026-01-31, or a relative period like "30 days" or "6 months". With "Newest first", paging stops as soon as older reviews are reached, so this also keeps runs short. Older reviews are not delivered or charged.

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

Only keep reviews with at least this many stars (1–5). Reviews outside the rating range are not delivered or charged.

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

Only keep reviews with at most this many stars (1–5). Set 1–2 to get complaints only.

## `includeKeywords` (type: `array`):

Optional. Keep only reviews whose title or text contains at least one of these words or phrases (case- and accent-insensitive), e.g. crash, refund, dark mode. Other reviews are not delivered or charged.

## `excludeKeywords` (type: `array`):

Optional. Drop reviews whose title or text contains any of these words or phrases. Dropped reviews are free.

## `developerReplyFilter` (type: `string`):

All reviews, only reviews the developer answered, or only unanswered reviews (handy for support teams). Reviews left out are free.

## `addSentimentAndTopics` (type: `boolean`):

Tag every review with sentiment (positive / neutral / negative, from the stars plus a small word list) and topics (crash-bug, login-account, ads, pricing-subscription, performance, ui-ux, support, feature-request, privacy). Computed locally with keyword dictionaries for English, Spanish, German, French and Portuguese: no AI service, no extra cost.

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

One free item per app × country: average rating of the reviews fetched, counts by star, average rating by app version, and the most frequent words in 1–2 star reviews (stop words removed).

## `includeComparison` (type: `boolean`):

When the run covers 2 or more apps, add one free comparison item: average rating, % of 1–2★ reviews, top negative topics, developer reply rate, median reply time, and rating by week and by app version, side by side.

## `onlyNewReviews` (type: `boolean`):

Deliver and charge only reviews that earlier runs haven't delivered yet, per store × app × country. The first run is the baseline (delivers what it fetches); schedule the task daily or hourly and connect a webhook, Slack or email to get new reviews only. Works best with "Newest first".

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

Optional. Keeps independent monitors apart (e.g. one per team or task). Runs with the same name share what was already delivered. Stored in your account's key-value store "app-reviews-monitor".

## `includeSearchResults` (type: `boolean`):

Also deliver the search result list for each search term (rank, app id, title, developer, rating, price). Charged per listing entry. Off = search terms are only used to find apps.

## `maxSearchResults` (type: `integer`):

How many apps to take from each search (these apps are then fetched) and how many entries to deliver per top chart.

## `topCharts` (type: `array`):

Optional. One chart per line as collection:category. Google Play: topselling_free, topselling_paid or topgrossing, with an optional category like GAME, GAME_PUZZLE, FINANCE, PRODUCTIVITY (e.g. topselling_free:GAME). App Store: topfreeapplications, toppaidapplications, topgrossingapplications, newapplications, newfreeapplications or newpaidapplications with an optional numeric genre id (e.g. topfreeapplications:6014 for Games). Fetched for every country; charged per entry.

## `outputFormat` (type: `string`):

Items: one JSON record per review / app / listing entry / summary, with the same fields for both stores (best for API / code). Rows: one flat row per record with identical columns everywhere (best for CSV, Excel, Google Sheets, BI tools).

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

Off by default: both stores accept moderate datacenter traffic. Turn on Apify Proxy if you see many retries or run very large lists; sessions and IPs then rotate automatically on every block.

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

How many apps / searches to fetch at the same time, each with its own session. Higher is faster for big lists; lower is gentler on rate limits.

## `maxRetries` (type: `integer`):

How many times to retry with a fresh session (and IP, with a proxy) when a store returns 429 / 403 / 5xx or a network error.

## Actor input object example

```json
{
  "apps": [
    "https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580",
    "https://play.google.com/store/apps/details?id=com.spotify.music"
  ],
  "searchTerms": [],
  "stores": "both",
  "countries": "us",
  "languages": "en",
  "includeAppDetails": true,
  "includeReviews": true,
  "maxReviewsPerApp": 200,
  "reviewSort": "newest",
  "minRating": 1,
  "maxRating": 5,
  "includeKeywords": [],
  "excludeKeywords": [],
  "developerReplyFilter": "any",
  "addSentimentAndTopics": true,
  "includeSummary": true,
  "includeComparison": true,
  "onlyNewReviews": false,
  "includeSearchResults": false,
  "maxSearchResults": 20,
  "topCharts": [],
  "outputFormat": "items",
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "maxConcurrency": 4,
  "maxRetries": 6
}
```

# Actor output Schema

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

No description

## `runSummary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

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

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "apps": [
        "https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580",
        "https://play.google.com/store/apps/details?id=com.spotify.music"
    ],
    "countries": "us",
    "languages": "en",
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("aaronoptimus/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/spotify-music-and-podcasts/id324684580",
        "https://play.google.com/store/apps/details?id=com.spotify.music",
    ],
    "countries": "us",
    "languages": "en",
    "proxyConfiguration": { "useApifyProxy": False },
}

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

```

## MCP server setup

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