# App Store Reviews Scraper | Multi-Country, Rating & Version (`dvntvent/app-store-reviews-scraper`) Actor

Extract Apple App Store reviews from multiple country storefronts in a single run. Filter by star rating to find complaints or app version to see what a release fixed or broke. Returns review text, title, rating, version, author, vote counts and timestamps and app metadata. No account/API required.

- **URL**: https://apify.com/dvntvent/app-store-reviews-scraper.md
- **Developed by:** [dvnt](https://apify.com/dvntvent) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.01 / 1,000 results

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

## App Store Reviews Scraper

Pulls Apple App Store reviews for any app, from as many storefronts as you like, in a single run. Filters by star rating and by app version.

Apple shows reviews in the store but gives you no export, no way to isolate a release, and no way to read more than one country at a time. If you ship an update and want to know what broke, the alternative is scrolling the store by hand, one storefront at a time.

### What it does that the store does not

Reviews are held per storefront. A review written in the US never appears in the UK feed, and the two sets do not overlap at all. Across a run covering GB, US and AU, review IDs did not repeat once. Reading several storefronts is the only way to get depth on an app, and it is the main reason to use this.

Version filtering answers the question you actually have after a release. Ask for version `7.136.0` and one and two star reviews, and you get the complaints about that build and nothing else.

### Example

Input:

```json
{
  "searchTerms": ["duolingo"],
  "countries": ["gb", "us", "au"],
  "minRating": 1,
  "maxRating": 2,
  "maxReviewsPerApp": 300
}
```

That resolves the term to app `570060128`, reads all three storefronts, and returns 300 one and two star reviews. One row:

```json
{
  "appId": "570060128",
  "appName": "Duolingo: Language & Chess",
  "country": "gb",
  "reviewId": "14473595439",
  "author": "Shiya 380",
  "rating": 4,
  "version": "7.137.0",
  "title": "Helps u learn",
  "content": "Duolingo is good",
  "voteSum": 0,
  "voteCount": 0,
  "updatedAt": "2026-08-26T09:40:20.000Z",
  "reviewUrl": "https://apps.apple.com/gb/app/id570060128?see-all=reviews",
  "sortOrder": "mostRecent",
  "developer": "Duolingo",
  "currentVersion": "7.137.0",
  "averageUserRating": 4.69714,
  "userRatingCount": 969158,
  "primaryGenre": "Education",
  "price": 0,
  "releaseDate": "2012-11-13T08:00:00Z"
}
```

Timestamps are converted to UTC, so rows from different storefronts sort against each other correctly. Apple returns a local offset.

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `appIds` | array | `["570060128"]` | Numeric App Store IDs. Take the digits after `id` in the store URL. |
| `searchTerms` | array | `[]` | App names to resolve instead of IDs. |
| `maxAppsPerSearchTerm` | integer | `1` | How many apps each term returns. Raise it to pull a competitor set. |
| `countries` | array | `["gb"]` | Storefront codes such as `gb`, `us`, `au`, `de`. |
| `sortBy` | string | `mostRecent` | `mostRecent` or `mostHelpful`. |
| `maxReviewsPerApp` | integer | `200` | Row limit per app, counted across all storefronts. |
| `minRating` | integer | `1` | Lower bound, inclusive. |
| `maxRating` | integer | `5` | Upper bound, inclusive. |
| `versions` | array | `[]` | Keep only these release strings. Empty means all. |
| `includeAppMetadata` | boolean | `true` | Adds developer, genre, price and the other app columns to every row. |
| `deduplicateAcrossSorts` | boolean | `false` | Reads both sort orders and drops anything seen twice. |

Provide `appIds` or `searchTerms`. Both together is fine.

### Limits worth knowing before you run it

These come from testing against the live feed, not from Apple's documentation, which does not cover any of it.

#### The feed stops at 500 per storefront

Apple caps it at 500 reviews per app, per storefront, per sort order, which is ten pages of fifty. Request page eleven and you get HTTP 400 with the body `CustomerReviews RSS page depth is limited to 10`. Multiple storefronts is how you get past 500 on an app, because each storefront has its own 500.

#### Yield varies between runs

Expect roughly 400 to 500. Apple's feed contains empty pages that sit between full ones, and their positions move over time. The same app and storefront returned a clean 500 one morning and 400 a few hours later, with holes at pages three and seven. This scraper pages through to the ceiling and steps over the gaps, so you get everything that is there. It does mean two runs of identical input can return slightly different counts with nothing wrong.

#### The row limit is per app, not per storefront

`maxReviewsPerApp` counts across every storefront you asked for. Storefronts fill in the order you list them. Setting it to 60 with `["gb", "us"]` can return 60 rows from GB and none from US. If you want depth everywhere, set the limit high enough to cover every storefront you asked for.

#### Reading both sort orders costs about double

`deduplicateAcrossSorts` roughly doubles the request count. The two sort orders overlap without being identical, so it returns more unique reviews per storefront, and it costs more.

### FAQ

**How do I find an app's ID?**
Open the app in the App Store and look at the URL. In `apps.apple.com/gb/app/duolingo/id570060128` the ID is `570060128`. You can also pass the name in `searchTerms` instead.

**Can I get more than 500 reviews for one app?**
From a single storefront, no. Apple will not serve them. Add storefronts, since each has its own separate set of up to 500, or set `deduplicateAcrossSorts` to true to pick up reviews that only appear in the other sort order.

**Why do GB and US return different app names for the same ID?**
Apple localises store listings. App `570060128` is "Duolingo: Language & Chess" in GB and AU, and "Duolingo: Language Lessons" in US. The ID is the same everywhere, and metadata is fetched per storefront, so each row carries the name used in that country.

**Can I scrape Google Play with this?**
No. This reads Apple only.

**Does it need an Apple account or any credentials?**
No. It reads public endpoints logged out, and there is nothing to configure.

**How far back do reviews go?**
As far as 500 reviews reaches for that storefront, which depends on how busy the app is. A popular app might cover a few weeks. A quiet one can go back years.

**Why did I get fewer rows than `maxReviewsPerApp`?**
Either your filters excluded them, the app has fewer reviews than the limit in the storefronts you asked for, or you hit the per storefront ceiling. The run log states what was read and what was kept for each app and storefront.

**Can I track a specific release?**
Yes, that is what `versions` is for. Pass `["7.136.0"]` to read only reviews left against that build.

**What happens if an app is not sold in a storefront I listed?**
It is skipped with a warning in the log, and the rest of the run continues.

### Output

Every row carries `appId`, `appName`, `country`, `reviewId`, `author`, `rating`, `version`, `title`, `content`, `voteSum`, `voteCount`, `updatedAt`, `reviewUrl` and `sortOrder`.

With `includeAppMetadata` on, each row also gets `developer`, `currentVersion`, `averageUserRating`, `userRatingCount`, `primaryGenre`, `price` and `releaseDate`.

Two notes on the shape. Apple publishes no permalink for an individual review, so `reviewUrl` points at the app's reviews tab in that storefront. Reviewer identity is limited to the display name, because that is all Apple exposes.

The output shape is declared in the scraper rather than passed through from Apple, so a change at their end cannot quietly alter the columns your pipeline receives.

# Actor input Schema

## `appIds` (type: `array`):

Numeric App Store IDs. Take the digits after 'id' in the store URL, so apps.apple.com/gb/app/duolingo/id570060128 gives 570060128. Fill in this field or search terms, or both.

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

App names to look up instead of IDs. Each term resolves to its best match in the first storefront listed below. Use App IDs when you need certainty about which app you get.

## `maxAppsPerSearchTerm` (type: `integer`):

How many apps each search term brings back. Leave at 1 to look up one known app. Raise it to pull a whole competitor set from one term, for example the top 10 matches for 'language learning'. Each extra app multiplies the reviews you pay for, so raise it deliberately.

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

Two-letter storefront codes such as gb, us, au, de. Apple keeps reviews separate per storefront, so a US review never appears in the GB feed. Listing several here is how you get past the 500-review ceiling on any single one.

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

Which of Apple's two review orderings to read.

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

Upper bound on rows written per app, counted across every storefront you listed. Storefronts fill in the order given.

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

Set both this and the maximum to 2 to read only the complaints.

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

Upper bound on star rating, inclusive.

## `versions` (type: `array`):

Keep only reviews left against these release strings, for example 7.137.0. Leave empty for every version. This is how you find out what a specific release broke.

## `includeAppMetadata` (type: `boolean`):

Adds developer, current version, average rating, rating count, genre, price and release date to every row.

## `deduplicateAcrossSorts` (type: `boolean`):

Reads most recent and most helpful, then drops reviews seen twice. The two orderings overlap without matching, so this returns more unique reviews per storefront at the cost of roughly double the requests.

## Actor input object example

```json
{
  "appIds": [
    "570060128"
  ],
  "searchTerms": [],
  "maxAppsPerSearchTerm": 1,
  "countries": [
    "gb"
  ],
  "sortBy": "mostRecent",
  "maxReviewsPerApp": 200,
  "minRating": 1,
  "maxRating": 5,
  "versions": [],
  "includeAppMetadata": true,
  "deduplicateAcrossSorts": false
}
```

# Actor output Schema

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

Every review matching the requested apps, storefronts, star ratings and app versions. Each row carries the review text, title, rating, app version, storefront, author, vote counts and a UTC timestamp, plus the app metadata join when it is switched on.

# 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 = {
    "appIds": [
        "570060128"
    ],
    "countries": [
        "gb"
    ],
    "maxReviewsPerApp": 200
};

// Run the Actor and wait for it to finish
const run = await client.actor("dvntvent/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 = {
    "appIds": ["570060128"],
    "countries": ["gb"],
    "maxReviewsPerApp": 200,
}

# Run the Actor and wait for it to finish
run = client.actor("dvntvent/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 '{
  "appIds": [
    "570060128"
  ],
  "countries": [
    "gb"
  ],
  "maxReviewsPerApp": 200
}' |
apify call dvntvent/app-store-reviews-scraper --silent --output-dataset

```

## MCP server setup

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