# Apple App Store Reviews Scraper — Ratings, Versions & Countries (`lukehunter/app-store-reviews-scraper`) Actor

Export Apple App Store reviews for any iOS app: rating, title, text, app version, date and country. Multi-country, rating and date filters, via Apple's own public feeds. For app developers, ASO and product research.

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

## Pricing

$0.20 / 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.

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

## Apple App Store Reviews Scraper & Review Alerts — Countries

**For app developers, ASO agencies, and product/UX and competitor-monitoring teams who need App Store review data.** One run returns customer reviews for one or more iOS apps — star rating, title, text, reviewed app version, reviewer nickname, date, helpfulness votes, and country — exported as a clean dataset, no App Store login or scraping of apps.apple.com pages involved. Turn on **review alerts** mode (`onlyNewSinceLastRun`) and run it on a schedule to get only newly posted reviews each time — route 1-2 star reviews to Slack with the rating filter and a webhook.
Pay-per-result: **$0.0002 per review — 1,000 reviews = $0.20.**
Try it free with Apify's monthly platform credit.

This Actor talks **only to Apple's own published, documented endpoints**: the iTunes Search API, the iTunes Lookup API, and Apple's customer-reviews RSS/JSON feed. It never fetches or parses an apps.apple.com web page.

### Quick start (2 minutes)

1. Find the app on the App Store and copy its page URL (e.g. `https://apps.apple.com/us/app/facebook/id284882215`), or just note its numeric id (`284882215`). A plain search term (e.g. `"facebook"`) also works — it resolves to the top matching app.
2. Open the **Input** tab and use this prefill (swap in your own apps/countries):

```json
{
  "apps": ["https://apps.apple.com/us/app/facebook/id284882215"],
  "countries": ["us"],
  "maxItems": 20
}
```

3. Click **Start**. Export the resulting dataset to CSV/Excel/JSON, or pull it through the API shown below.

### Use cases

- **App developers and ASO agencies** tracking what users say about a release, filtered by `minRating`/`maxRating` or `sinceDate` to focus on recent complaints or praise.
- **Product and UX researchers** mining review `text` for recurring feature requests or pain points across `version`s.
- **Competitor monitoring** teams running the same input against a competitor's app id on a schedule, comparing `averageRating` and review volume over time.
- **Localization/market teams** comparing sentiment and volume across `countries` for the same app.

### Review alerts: get only new reviews

Turn on `onlyNewSinceLastRun` and put this Actor on a **schedule** to get a genuine **App Store review alert**: every run after the first delivers (and charges for) only reviews it hasn't delivered before for that exact search. It's the same wedge as [Wellfound Jobs Scraper](https://apify.com/lukehunter/wellfound-jobs-scraper)'s job-alerts mode, applied to App Store reviews.

1. Set your apps/countries/filters as usual, plus:

```json
{
  "apps": ["https://apps.apple.com/us/app/facebook/id284882215"],
  "countries": ["us"],
  "maxRating": 2,
  "onlyNewSinceLastRun": true,
  "maxItems": 200
}
```

2. Click **Schedule** on the run page (or create one under **Schedules** in the Apify Console) — daily works well for an actively-reviewed app.
3. **The first scheduled run is a baseline**: it delivers every review currently in the feed (it's all new to you) and remembers it — you're charged normally for that run. Every run after that only delivers reviews it hasn't seen before for this exact search.
4. Add a **Webhook** under the schedule's **Integrations** for `ACTOR.RUN.SUCCEEDED`, pointing at:
   - **Slack**: Apify's own Slack integration (or a Zapier/Make webhook step) posting each run's new dataset items to a channel — combine with `maxRating: 2` to route only new 1-2 star reviews to your support channel.
   - **Email**: a Zapier/Make "on webhook, send email" step, or Apify's own email integration, summarising that run's new reviews.
   - **Google Sheets**: the Apify-to-Google-Sheets integration, appending each run's rows to a sheet you watch.

The run's status message tells you what happened: `"First run: 12 review(s) delivered and remembered; next runs return only new ones."` on the baseline, then `"3 new review(s) since last run (9 already seen)."` on later runs. A run that finds nothing new still **succeeds** with an empty dataset — that's the point of the mode, not a failure.

A review's identity for this mode is scoped to its app **and** country (the same `reviewId` under two countries counts as two distinct reviews), and each distinct combination of `apps`/`countries`/`minRating`/`maxRating`/`sinceDate` is tracked as its own watch, so you can run several alert schedules without them mixing up each other's history. `stateStoreName` only needs changing if you want to reset a watch's memory or explicitly isolate it.

### Input

```json
{
  "apps": ["https://apps.apple.com/us/app/facebook/id284882215"],
  "countries": ["us"],
  "minRating": null,
  "maxRating": null,
  "sinceDate": null,
  "maxReviewsPerApp": 500,
  "maxItems": 100,
  "includeAppDetails": false,
  "resolveSearchTerms": true,
  "onlyNewSinceLastRun": false,
  "stateStoreName": "app-store-reviews-scraper-seen-reviews"
}
```

| Field | Type | Default | Description |
|---|---|---:|---|
| `apps` | string\[] | required | 1–20 App Store URLs, numeric ids, or search terms |
| `countries` | string\[] | `["us"]` | 1–20 ISO-2 App Store storefront country codes |
| `minRating` / `maxRating` | integer | none | Only deliver reviews within this 1–5 star range |
| `sinceDate` | string | none | Only deliver reviews on/after this date (also stops paging early) |
| `maxReviewsPerApp` | integer | 500 | Cap per app per country. Apple's own feed exposes at most 500 (10 pages × 50) |
| `maxItems` | integer | 100 | 1–20000. Hard cap on reviews delivered across the whole run |
| `includeAppDetails` | boolean | `false` | Attach `appName`/`developer`/`averageRating`/`ratingCount`/`currentVersion` to every row (one extra, unbilled request per app) |
| `resolveSearchTerms` | boolean | `true` | Allow free-text `apps` entries to be resolved via the Search API. Turn off to require URLs/ids only |
| `onlyNewSinceLastRun` | boolean | `false` | Review alerts mode — see above. Delivers and charges only reviews not delivered by a previous run of the same search. |
| `stateStoreName` | string | `app-store-reviews-scraper-seen-reviews` | Name of the store that remembers what a review-alerts search has already delivered. Change it only to isolate or reset a watch. |

For most users, the only settings that matter are **Apps** and **Maximum reviews**.

### App Store review data fields

| Category | Fields |
|---|---|
| Identity | `appId`, `country`, `reviewId` |
| Review | `rating`, `title`, `text`, `version`, `author`, `date` |
| Helpfulness | `voteSum`, `voteCount`, `reviewUrl` |
| App details (when `includeAppDetails` is on) | `appName`, `developer`, `averageRating`, `ratingCount`, `currentVersion` |
| Freshness | `scrapedAt` |

Missing values are returned as `null`. The Actor does not invent missing data.

### Output example

```json
{
  "appId": "284882215",
  "appName": null,
  "country": "us",
  "reviewId": "100000000001",
  "rating": 5,
  "title": "Great app",
  "text": "Works well and keeps me connected.",
  "version": "482.0",
  "author": "moviebuff_22",
  "date": "2026-08-14T16:15:00.000Z",
  "voteSum": 3,
  "voteCount": 4,
  "reviewUrl": "https://itunes.apple.com/us/review?id=284882215&type=Purple+Software",
  "developer": null,
  "averageRating": null,
  "ratingCount": null,
  "currentVersion": null,
  "scrapedAt": "2026-09-27T00:00:00.000Z"
}
```

`appName`, `developer`, `averageRating`, `ratingCount` and `currentVersion` are populated on every row only when `includeAppDetails` is `true` — otherwise they are `null`, and the dataset's column set stays identical either way.

### Use it as an App Store reviews API

The Actor can be called from your own application through the Apify API. No App Store login or Apple developer account is required — everything comes from Apple's own public APIs.

#### Python

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

run = client.actor("lukehunter/app-store-reviews-scraper").call(
    run_input={
        "apps": ["284882215"],
        "countries": ["us", "gb"],
        "maxItems": 200,
    }
)

for review in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(review["rating"], review["title"], review["author"])
```

Use Apify schedules for recurring review pulls, and webhooks or integrations to send completed datasets into the rest of your workflow.

### Pricing and cost control

This Actor uses **pay per delivered review** pricing.

**Current configured rate: $0.0002 per review delivered.** Check the Apify **Pricing** tab for the latest published rate.

| Reviews delivered | Cost at $0.0002/review |
|---:|---:|
| 100 | $0.02 |
| 1,000 | $0.20 |
| 20,000 | $4.00 |

There is no charge for merely starting a run, and no charge for the optional `includeAppDetails` lookup. `maxItems` gives you a clear upper bound on the number of billable reviews.

Reviews are de-duplicated by `reviewId` within each country.

### Reliability and data quality

- If Apple's API returns nothing usable for an app/country, that outcome is reported honestly rather than hidden inside an apparently-successful empty run.
- The parser is defensive: a missing or reshaped field on a review becomes `null` rather than crashing the run or silently fabricating data.
- Requests are spaced at least 3 seconds apart, in line with Apple's own guidance for the Search API, applied to every request this Actor makes.
- A free-text `apps` entry that matches no app fails that one entry with a clear reason; other apps in the same run still complete.

### Important limitations

- **Apple's own ceiling, not ours.** The customer-reviews feed exposes at most the 500 most-recent reviews (10 pages × 50) per app per country. Older reviews are not retrievable through this endpoint at all.
- **Public nicknames only.** `author` is the public reviewer nickname shown on the App Store, not a real name, email, or any other personal identifier. No data beyond what Apple already publishes on the review page is collected.
- **App details reflect one storefront.** When `includeAppDetails` is on, app-level fields are fetched once per app from the first country in `countries`, not separately per country.
- Ratings, review counts and content can change after the run; `scrapedAt` records the observation time.
- This Actor does not use CAPTCHA bypass, proxy rotation, credential capture, or impersonation — it only calls Apple's own public, documented APIs.
- This is an independent tool and is **not affiliated with, endorsed by, or connected to Apple Inc.** Apple and App Store are trademarks of their owner. You are responsible for using the data in accordance with applicable terms and laws.

### FAQ

#### Can I scrape reviews for a private or unlisted app?

No. Only apps published on the public App Store, in the requested country's storefront, can be found via Apple's Search/Lookup APIs and reviews feed.

#### Is it legal to collect App Store review data this way?

This Actor only collects public review data through Apple's own published, documented APIs (the iTunes Search API, the iTunes Lookup API, and the customer-reviews RSS/JSON feed) — it does not bypass any login, CAPTCHA, or paywall, and it does not scrape apps.apple.com web pages. It is an independent tool, not affiliated with or endorsed by Apple Inc., and you are responsible for using the collected data in accordance with Apple's terms and the laws that apply to you.

#### How far back do the reviews go?

As far as Apple's own feed exposes: up to the 500 most-recent reviews per app per country. There is no way to retrieve reviews beyond that through this endpoint.

#### Can I filter to just 1-star (or 5-star) reviews?

Yes — set `minRating` and/or `maxRating`. Filtered-out reviews are never delivered or charged for.

#### Does this work for apps in multiple countries?

Yes. Add each ISO-2 country code to `countries`; each is fetched, filtered, and de-duplicated separately, and every row records which `country` it came from.

#### What happens if I enter a search term instead of a URL or id?

By default (`resolveSearchTerms: true`) it resolves to the top matching app from Apple's Search API. Turn this off to require exact URLs/ids only.

#### How do I get a review alert instead of the same reviews every time?

Set `onlyNewSinceLastRun: true` and put the Actor on a schedule — see "Review alerts" above. The first scheduled run delivers everything (and remembers it); later runs only deliver what's new. Combine with `maxRating` and a Slack webhook to get alerted only on new low-rated reviews.

### Related Actors

Other data tools from the same developer, built to the same standard: official or public sources, hard cost caps, and honest documentation of limits.

- **[Google Play App Scraper](https://apify.com/lukehunter/google-play-scraper)**: ratings, installs, developer contact info and pricing for any Google Play app.
- **[Spotify Scraper](https://apify.com/lukehunter/spotify-scraper)**: play counts, monthly listeners and playlist track lists for any public Spotify artist, playlist, album or track.
- **[Walmart Category Scraper](https://apify.com/lukehunter/walmart-category-scraper)**: product names, prices, was-prices and ratings from Walmart category pages.
- **[Shopify Store Products Scraper](https://apify.com/lukehunter/shopify-store-products-scraper)**: full product catalogues from any Shopify store, with prices, sale prices, variants and stock.
- **[Vinted Scraper](https://apify.com/lukehunter/vinted-scraper)**: Vinted search results with prices, brands, sizes and favourites, across any Vinted country.
- **[AliExpress Search Scraper](https://apify.com/lukehunter/aliexpress-scraper)**: AliExpress search results with prices, discounts, ratings and sold counts, by keyword.
- **[Hospital Price Transparency Enforcement Leads](https://apify.com/lukehunter/hospital-price-transparency-enforcement-leads)**: hospitals with recent CMS price transparency warning notices, CAP requests and CMP notices.
- **[Hospital Ownership Change Radar](https://apify.com/lukehunter/hospital-chow-radar)**: hospitals that just changed owner, with buyer, seller and effective date from CMS filings.

# Actor input Schema

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

App Store apps to scrape reviews for: a full App Store URL (https://apps.apple.com/us/app/facebook/id284882215), a bare numeric App Store id (284882215), or a free-text search term resolved to its top App Store match (see "Resolve search terms"). Maximum 20 apps per run.

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

ISO-2 App Store storefront country codes to fetch reviews from, e.g. "us", "gb", "jp". Each app's reviews are fetched separately per country. Default \["us"]. Maximum 20 countries per run.

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

Only deliver reviews rated at least this many stars, 1-5. Leave empty for no minimum.

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

Only deliver reviews rated at most this many stars, 1-5. Leave empty for no maximum.

## `sinceDate` (type: `string`):

Only deliver reviews updated/posted on or after this date (e.g. 2026-01-01). Leave empty for no date filter. The feed is read most-recent-first, so this also stops paging early once older reviews are reached.

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

Cap on reviews delivered for one app in one country, 1-500, default 500. Apple's own customer-reviews feed exposes at most 500 (10 pages of 50) most-recent reviews per app per country, so this cannot be set higher.

## `maxItems` (type: `integer`):

Hard cap on reviews delivered across every app and country in this run, 1-20000, default 100. You are charged per delivered review, so this is your cost cap.

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

Attach appName, developer, averageRating, ratingCount and currentVersion (from the iTunes Lookup API) to every review row. Adds one extra, unbilled request per app. Off by default.

## `resolveSearchTerms` (type: `boolean`):

When an "apps" entry is not a URL or numeric id, resolve it to the iTunes Search API's top matching app. Turn off to require every "apps" entry to already be a URL or id (the run fails loudly instead of guessing an app from a term).

## `onlyNewSinceLastRun` (type: `boolean`):

Turn this on and run the Actor on a schedule to get only reviews you haven't seen before: reviews already delivered on a previous run for the same apps/countries/rating/date filters are skipped and never charged again. The first run for a given search delivers everything (it's all new) and remembers it; later runs only deliver what's new since then. Combine with "Maximum star rating" (e.g. 2) and a webhook to route only new low-rated reviews to Slack. Off by default so a one-off run always gets the full current results.

## `stateStoreName` (type: `string`):

Only used when "Review alerts" is on. Name of the key-value store that remembers which reviews this search has already delivered, so it persists across scheduled runs. Leave as the default unless you're running several different alert searches that must not share history — give each its own name in that case. Letters, numbers and hyphens only.

## Actor input object example

```json
{
  "apps": [
    "https://apps.apple.com/us/app/instagram/id389801252"
  ],
  "countries": [
    "us"
  ],
  "maxReviewsPerApp": 20,
  "maxItems": 20,
  "includeAppDetails": false,
  "resolveSearchTerms": true,
  "onlyNewSinceLastRun": false,
  "stateStoreName": "app-store-reviews-scraper-seen-reviews"
}
```

# Actor output Schema

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

No description

# API

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

## JavaScript example

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

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

// Prepare Actor input
const input = {
    "apps": [
        "https://apps.apple.com/us/app/instagram/id389801252"
    ],
    "countries": [
        "us"
    ],
    "maxReviewsPerApp": 20,
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("lukehunter/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/instagram/id389801252"],
    "countries": ["us"],
    "maxReviewsPerApp": 20,
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("lukehunter/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/instagram/id389801252"
  ],
  "countries": [
    "us"
  ],
  "maxReviewsPerApp": 20,
  "maxItems": 20
}' |
apify call lukehunter/app-store-reviews-scraper --silent --output-dataset

```

## MCP server setup

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