# App Store Reviews Scraper (`zkill3r/app-store-reviews-scraper`) Actor

Scrape Apple App Store reviews and app data for any of 175 countries, no login: rating, title, text, user, date, developer replies, rating histogram, price, version. Search by keyword, filter by stars and date. $0.09 per 1,000 reviews.

- **URL**: https://apify.com/zkill3r/app-store-reviews-scraper.md
- **Developed by:** [Zee Lex](https://apify.com/zkill3r) (community)
- **Categories:** E-commerce, Developer tools, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.06 / 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 Reviews Scraper — Apple iOS Reviews, Ratings & App Data

Scrape public **Apple App Store reviews** and **app data** for any of the ~175 storefronts, with no login and no API key. It is HTTP only, with no browser, and runs at 256 MB. It is the companion to our Google Play Reviews Scraper.

- **Reviews:** star rating, title, text, user name, date, edited flag, **developer response**, app version and helpful votes (when the source has them), and a link.
- **App data:** name, bundle ID, developer, seller, price, currency, average rating and rating count (overall and current version), **rating histogram 1–5**, genres, content rating, version, release dates, release notes, description, size, minimum iOS, languages, device count, icon and screenshots.
- **Inputs:** App Store URLs (any country, with or without the app name), numeric app IDs, bundle IDs, or keyword search.
- **Filters:** countries, most recent / most helpful, minimum / maximum stars, and reviews since a date.

### Input examples

**A single app** (the prefill)

```json
{ "startUrls": ["https://apps.apple.com/us/app/whatsapp-messenger/id310633997"], "maxReviewsPerApp": 20 }
```

**By bundle ID**

```json
{ "startUrls": ["com.whatsapp.WhatsApp"], "maxReviewsPerApp": 200 }
```

**Several countries**: the same app in the US, UK, Germany and Malaysia

```json
{ "startUrls": ["310633997"], "countries": ["us", "gb", "de", "my"], "maxReviewsPerApp": 500 }
```

**1-star reviews only, last 30 days**

```json
{ "startUrls": ["310633997"], "minRating": 1, "maxRating": 1, "reviewsSince": "2026-09-28", "maxReviewsPerApp": 1000 }
```

**Search**: the top 5 meditation apps, app data plus 50 reviews each

```json
{ "searchQueries": ["meditation"], "maxAppsPerSearch": 5, "maxReviewsPerApp": 50 }
```

### Output samples

Values below are invented.

**Review**

```json
{
  "type": "review",
  "reviewId": "12320874468",
  "appId": "310633997",
  "appName": "Chatter Messenger",
  "country": "us",
  "rating": 1,
  "title": "Keeps crashing",
  "text": "Since the last update the app crashes when I open a group chat.",
  "userName": "sunny_day_42",
  "date": "2026-10-27T11:51:06.000Z",
  "isEdited": false,
  "appVersion": null,
  "developerResponse": { "text": "Sorry about that! A fix is in version 24.21.", "date": "2026-10-28T09:02:11.000Z" },
  "voteCount": null,
  "voteSum": null,
  "url": "https://apps.apple.com/us/app/id310633997?see-all=reviews#12320874468",
  "source": "amp-api",
  "input": "310633997",
  "scrapedAt": "2026-10-28T12:36:18.626Z"
}
```

**App**

```json
{
  "type": "app",
  "appId": "310633997",
  "bundleId": "com.example.chatter",
  "name": "Chatter Messenger",
  "url": "https://apps.apple.com/us/app/id310633997",
  "developer": "Example Labs Inc.",
  "developerId": "900000001",
  "developerUrl": "https://apps.apple.com/us/developer/example-labs-inc/id900000001",
  "sellerName": "Example Labs, Inc.",
  "price": 0,
  "currency": "USD",
  "isFree": true,
  "rating": 4.66,
  "ratingCount": 12345678,
  "ratingCurrentVersion": 4.66,
  "ratingCountCurrentVersion": 12345678,
  "ratingHistogram": { "1": 5000, "2": 3000, "3": 4000, "4": 20000, "5": 68000 },
  "primaryGenre": "Social Networking",
  "genres": ["Social Networking", "Productivity"],
  "contentRating": "12+",
  "version": "24.20.1",
  "releaseDate": "2009-05-03T07:00:00.000Z",
  "currentVersionReleaseDate": "2026-10-20T15:01:22.000Z",
  "releaseNotes": "Bug fixes and performance improvements.",
  "description": "…",
  "fileSizeBytes": 263544832,
  "minimumOsVersion": "15.1",
  "languages": ["EN", "DE", "FR", "MS"],
  "supportedDevices": 3,
  "iconUrl": "https://is1-ssl.mzstatic.com/image/thumb/…/512x512bb.jpg",
  "screenshotUrls": ["https://is1-ssl.mzstatic.com/image/thumb/…/s1.jpg"],
  "country": "us"
}
```

**Status** (free)

```json
{ "type": "status", "input": "555555555", "appId": "555555555", "country": "gb", "status": "not_available_in_country", "message": "Local is not available in the GB App Store." }
```

`status` is one of:

- `not_found`: no such app.
- `not_available_in_country`: the app exists, but not in that store.
- `no_reviews`: no reviews match your filters.
- `blocked`: Apple kept refusing (403/429) after 4 retries on every route.
- `error`: anything else.

Each run also stores a **RUN_SUMMARY** record. For every app and country it gives:

- reviews saved, pages, and the route used and tried
- blocks, proxy escalations and status
- whether the sort was applied
- reviews filtered by stars or date, RSS holes, and why paging stopped

It also gives totals, the percentage of each field that came back empty, and an overall status of `ok`, `partial`, `no_results` or `failed`.

#### Field names next to the Google Play scraper

| App Store | Google Play | Note |
|---|---|---|
| `rating` | `score` | 1–5 |
| `text`, `userName`, `date`, `appVersion`, `country` | same | |
| `developerResponse.text` / `.date` | `replyText` / `replyDate` | |
| `voteCount` | `thumbsUpCount` | RSS route only |
| `url` | `reviewUrl` | |
| app `name` | `title` | |
| app `rating` / `ratingCount` / `ratingHistogram` | `score` / `ratingsCount` / `histogram` | |
| `isFree`, `iconUrl`, `screenshotUrls`, `releaseDate`, `currentVersionReleaseDate` | `free`, `icon`, `screenshots`, `released`, `updated` | |

### How reviews are fetched

Each app is scraped separately in each country, through up to three routes. A route that is gone, or keeps being blocked, hands over to the next one automatically.

1. **amp-api via apps.apple.com.** This is what the App Store website uses today: `apps.apple.com/api/apps/v1/catalog/<cc>/apps/<id>/reviews`, 20 per page, with no token. It includes developer responses and the edited flag, and goes as deep as Apple lets the website go.
2. **amp-api with a bearer token** (`amp-api-edge.apps.apple.com`). The token is read from the app page (its config meta tag, or the page's JavaScript), cached for the run and refreshed on a 401.
3. **RSS feed** (`itunes.apple.com/<cc>/rss/customerreviews/…`). This has **at most 10 pages × 50 = 500 reviews per app per country and sort**, and no developer responses, but it adds `appVersion` and helpful votes.
   - Since autumn 2026 the feed has "holes": empty pages in the middle that change over time.
   - The scraper keeps going past an empty page and retries it with another URL spelling on a fresh session.
   - Pages that stay empty are listed in RUN_SUMMARY (`rssHoles`).

`source` on each review says which data source it came from (`amp-api` or `rss`).

### Use cases

- **App developers:** see every new review across countries, including 1-star reviews right after a release.
- **ASO agencies:** track ratings, histograms, versions and release notes for clients and competitors in many storefronts.
- **Competitor tracking:** compare rating trends and complaint themes across rival apps.
- **Sentiment analysis and AI datasets:** clean review text with ratings, dates, countries and developer replies.
- **Customer support QA:** find reviews with and without developer responses.

### Limitations

- **Reviews are per country.** Add every storefront you care about to `countries`. Search runs in the first country only.
- **What the website route can't give.** It has no app version or helpful votes, so those fields are null on `amp-api` reviews. The RSS route has them but no developer responses.
- **Sorting.** If Apple refuses the "most helpful" sort, the reviews come in Apple's default order and RUN_SUMMARY says `sortApplied: false`. "Reviews since" then filters instead of stopping early.
- **Star filters are applied after download.** A 1-star-only run of a mostly 5-star app reads many pages. The scraper gives up after 25 pages in a row with no match.
- **The RSS route caps at 500** reviews per app, country and sort, minus any holes.
- **Rate limits.** Apple limits each IP address. Requests that get a 403 or 429 are retried on a new session with exponential backoff and, if your plan has it, RESIDENTIAL proxy. A route that is still blocked after 4 retries hands over to the next route.
- **Run timeout.** The scraper stops 30 seconds before the run's timeout, saves what it has and exits successfully.
- **Depth.** The website route returns as many reviews as Apple's own site pages through; for very large apps that is typically thousands per country, not the full history.
- **Empty input** runs the example app (WhatsApp, US) so automated checks succeed.
- **Changes on Apple's side.** All endpoints, JSON paths and token patterns are in `src/mappings.ts`.

### Pricing

Pay per event, with discounts on higher Apify plans:

| Event | Free | Bronze | Silver | Gold+ |
|---|---|---|---|---|
| `review` (per 1,000) | $0.09 | $0.08 | $0.07 | $0.06 |
| `app` item (per 1,000, one per app per country) | $0.50 | $0.45 | $0.40 | $0.30 |

Status items (not found, not available in a country, blocked) are free. No start fee. The run stops cleanly once your maximum charge is reached. Platform usage is minimal: about 600 reviews per minute on 256 MB with datacenter proxy.

### Development

```bash
npm install
npm test                  # unit tests on synthetic fixtures (+ live-capture tests when captured)
npm run mock-server       # offline Apple mock on :4597 (see the header of test/mock-server.ts)
AS_BASE_URL=http://127.0.0.1:4597 npm start   # end-to-end against the mock, proxyConfiguration {"useApifyProxy": false}
npm run capture-fixtures  # save real Apple responses to test/fixtures/live/ (needs network)
npm run fixtures          # regenerate the synthetic fixtures
```

# Actor input Schema

## `startUrls` (type: `array`):

App Store URLs (https://apps.apple.com/<cc>/app/<name>/id<digits>, with or without the name or country), numeric app IDs (310633997) or bundle IDs (com.whatsapp.WhatsApp).

## `searchQueries` (type: `array`):

Optional. Keywords to search the App Store for (iTunes Search API, in the first country below). The top matches are scraped.

## `maxAppsPerSearch` (type: `integer`):

How many of the top search results to scrape per keyword.

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

2-letter store codes (us, gb, de, jp, my, …; ~175 storefronts). Every app is scraped in every country listed: reviews are separate per country. Clear the list to use each app URL's own country instead (us when the URL has none).

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

Scrape user reviews (one item per review, charged as "review").

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

Maximum reviews saved for each app in each country (after the star filter).

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

"Most recent" (needed for "Reviews since" to stop early) or "Most helpful". If Apple refuses the sort, the default order is used and RUN_SUMMARY says so.

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

Only keep reviews with at least this many stars (1-5). Applied after download, so it does not lower the number of pages read.

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

Only keep reviews with at most this many stars (1-5). Set min = max = 1 for 1-star reviews only.

## `reviewsSince` (type: `string`):

Optional ISO date (e.g. 2026-10-01). Older reviews are skipped; with "Most recent" paging stops as soon as older reviews appear.

## `includeAppInfo` (type: `boolean`):

One app item per app and country: name, developer, price, rating, rating counts, rating histogram, genre, version, release notes, size, languages, screenshots (charged as "app").

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

Parallel requests. Apple rate-limits per IP: lower it if RUN_SUMMARY shows many blocks.

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

Apify datacenter proxy by default. On 403 or 429 the request is retried on a new session with backoff, through RESIDENTIAL proxy if your plan has it. Max 4 retries per request.

## Actor input object example

```json
{
  "startUrls": [
    "https://apps.apple.com/us/app/whatsapp-messenger/id310633997"
  ],
  "maxAppsPerSearch": 5,
  "countries": [
    "us"
  ],
  "includeReviews": true,
  "maxReviewsPerApp": 20,
  "sortBy": "mostRecent",
  "includeAppInfo": true,
  "maxConcurrency": 10,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Reviews, apps and free status items (field "type").

## `allFields` (type: `string`):

Every field of every item.

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

Per app and country: reviews saved, pages, route used (amp-api / rss), blocks, escalations, status; totals; % missing per field; overall status.

# 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 = {
    "startUrls": [
        "https://apps.apple.com/us/app/whatsapp-messenger/id310633997"
    ],
    "maxReviewsPerApp": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("zkill3r/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 = {
    "startUrls": ["https://apps.apple.com/us/app/whatsapp-messenger/id310633997"],
    "maxReviewsPerApp": 20,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("zkill3r/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 '{
  "startUrls": [
    "https://apps.apple.com/us/app/whatsapp-messenger/id310633997"
  ],
  "maxReviewsPerApp": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call zkill3r/app-store-reviews-scraper --silent --output-dataset

```

## MCP server setup

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