# Apple App Store Review Monitor (`maydit/apple-app-store-review-monitor`) Actor

Monitor recent Apple App Store reviews by app and country. Filter ratings and keywords, compare review changes, and export review text, app versions and source links.

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

## Pricing

from $0.60 / 1,000 saved review observations

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

## Apple App Store Review Monitor

Follow recent App Store feedback for selected apps and storefronts. Save review ratings, titles, text and reviewed app versions; filter by literal keywords; and compare observations across scheduled runs.

Useful for release feedback, recurring complaint tracking and competitor review watchlists. Each dataset row is one saved review observation with its source links.

### Quick start

Run this small example to save up to five recent Duolingo US review observations:

```json
{
  "apps": [{ "appId": "570060128", "country": "us" }],
  "maxResults": 5,
  "maxPagesPerApp": 1
}
```

Open **Saved review observations** after the run to view or export the dataset as JSON, CSV or Excel. Open **Run report** for a readable coverage report, or **Coverage summary** for its machine-readable source details.

No Apple login or API key is required. The Actor reads Apple's public recent-review feed and looks up current app metadata. Availability varies by app and storefront.

**Cost planning:** the base rate is $0.001 per saved review observation plus $0.00005 per run start: five rows would cost $0.00505. The Store pricing panel and Run options govern actual charges. Reports and error diagnostics create no paid review rows.

An example of selected output fields from the source probe:

```json
{
  "appId": "570060128",
  "country": "us",
  "reviewId": "14674896619",
  "rating": 3,
  "reviewTitle": "Duolingo review",
  "reviewedAppVersion": "7.144.0",
  "sourceUpdatedAt": "2026-10-16T13:59:16-07:00"
}
```

### Inputs

| Input | Default | Meaning |
|---|---|---|
| `apps` | Duolingo US if absent or empty | Objects containing a numeric `appId` string and a two-letter `country`. The input form prefills Duolingo US. |
| `maxResults` | `5` | Maximum saved review observations across the whole run, from 1 to 1,000. |
| `maxPagesPerApp` | `1` | Recent feed pages per app/storefront, from 1 to 10. |
| `maxRating` | `5` | Keep individual review ratings at or below this value, from 1 to 5. |
| `keywords` | `[]` | Optional literal terms. Match any term, case-insensitively, in the review title or body. |
| `comparePrevious` | `false` | Compare observations with saved history. |
| `onlyChanges` | `false` | Save only first-seen or changed reviews; implies comparison. |
| `snapshotName` | `"default"` | History name, 1–80 letters, digits, underscores or hyphens. |
| `maxRunSeconds` | `240` | Work budget from 30 to 900 seconds; platform and source limits can end work earlier. |

Use the numeric ID after `id` in an App Store URL. Country selects the storefront; it does not prove the reviewer's location or language.

For a complaint watchlist, set `maxRating` to `2` and add terms such as `["crash", "subscription"]`. Both the rating filter and the keyword filter must pass. Terms use literal substring matching, not regular expressions or sentiment analysis. A narrow filter may yield zero rows within the requested page window.

The examples below include Notion US and a CapCut US comparison; along with the Duolingo quick start, each is bounded to five saved rows and one feed page.

#### More ready-to-run examples

**Notion US — five recent reviews:**

```json
{
  "apps": [{ "appId": "1232780281", "country": "us" }],
  "maxResults": 5,
  "maxPagesPerApp": 1
}
```

**CapCut US — compare five saved review observations:**

```json
{
  "apps": [{ "appId": "1500855883", "country": "us" }],
  "maxResults": 5,
  "maxPagesPerApp": 1,
  "comparePrevious": true,
  "snapshotName": "appstore-example-capcut"
}
```

### Saved data

| Fields | Meaning |
|---|---|
| `appId`, `country`, `reviewId` | Source identity. Treat this combination as the review key. |
| `appTitle`, `currentAppVersion` | Metadata from the matching app lookup. A missing or mismatched lookup is a source error. |
| `reviewedAppVersion` | Version associated with this review in the feed. It can differ from the current version. |
| `rating`, `reviewTitle`, `reviewText` | Individual review rating and content. |
| `sourceUpdatedAt` | Apple's source update timestamp, not a guaranteed first-publication date. |
| `appReviewsUrl` | App-level reviews page. This is not a unique review permalink. |
| `appUrl`, `feedUrl` | App listing and exact feed page used as sources. |
| `observedAt` | When this run observed the review. |
| `keywordMatches` | Matching evidence objects containing `term`, `field` and `excerpt`. |
| `comparisonStatus`, `changedFields` | Comparison outcome and watched fields that changed. |
| `previous`, `previousObservedAt` | Earlier watched values and observation time, if available. |
| `contentHash` | Fingerprint of watched review fields. |

Review ratings are distinct from Apple's app-level average rating or rating count. Aggregate rating counts include star ratings and do not establish the number of text reviews available from this feed.

### Compare scheduled runs

```json
{
  "apps": [{ "appId": "1500855883", "country": "us" }],
  "maxResults": 100,
  "maxPagesPerApp": 1,
  "comparePrevious": true,
  "onlyChanges": true,
  "snapshotName": "appstore-example-capcut"
}
```

Reuse the snapshot name and watchlist settings on later runs. Use one sequential writer per snapshot. The larger result cap above lets the first run save the entire selected feed page. With a smaller result cap, later changes-only runs can discover additional older reviews that were never saved in the first run; those are correctly marked `new` in this history.

- `not_requested`: comparison was disabled.
- `new`: first observation of this review in this saved history. It may be an older review.
- `changed`: watched review fields differ from the previous saved observation.
- `unchanged`: watched fields match. With `onlyChanges: true`, this row is skipped.

The first comparison run establishes history as observations are saved. The comparison includes the review's source update timestamp. Current app metadata, keyword evidence and observation time are excluded from the content fingerprint, so a new app release alone is not a review edit.

A review leaving the recent feed is not a deletion signal. Changing the app/storefront list, filters or page limits creates a separate comparison scope within the named store. Saved comparisons cannot recover edits that occurred outside the sampled feed window.

### Reports and empty results

The default key-value store contains:

- **`SUMMARY`** — machine-readable counts, source outcomes and coverage limits.
- **`RUN_REPORT`** — an HTML report generated from that summary.

Inspect these records when a run saves fewer rows than requested. Zero output can mean no matches, unchanged observations, a source failure or a run limit. Do not interpret zero rows as proof that an app has no reviews. Diagnostic reports are separate from paid review rows.

### Cost

**Base pricing:** $0.001 per saved review observation plus $0.00005 per run start. At those rates, five saved observations cost $0.00505; 100 cost $0.10005. Final Store pricing and the run's charge limit govern actual charges.

The unit is a saved observation, so saving the same review again on a later run can incur another result charge. `onlyChanges` reduces repeat output. Set `maxResults`, page limits and the maximum charge in Run options to bound spend. Source failures and filters can produce fewer rows than the selected cap.

### Coverage and source limits

- This is a bounded recent public feed window. It does not promise all reviews, a complete historical archive, developer replies or deleted-review detection.
- The observed feeds returned 50 reviews on page 1 and linked through page 10. Those observations are not a guaranteed page size or permanent coverage limit.
- Review order, availability, timestamps and app metadata are controlled by Apple. Current app version and reviewed app version describe different things.
- The customer-review feed has no availability or schema guarantee established by these probes. Source changes, unavailable storefronts or access restrictions can cause partial or failed runs.
- Apple's Search API documentation gives an approximate 20-call-per-minute limit for search/lookup, subject to change. That is not a documented quota for the review feed.
- Public access does not grant reuse permission. Review and comply with applicable Apple terms and rights in review content before redistribution. Keep source attribution. This Actor is not affiliated with Apple or the monitored apps.

#### Sources

- [Apple Search API and ID lookup documentation](https://performance-partners.apple.com/search-api)
- [Apple Media Services terms](https://www.apple.com/legal/internet-services/itunes/us/terms.html)
- [Duolingo US review feed](https://itunes.apple.com/us/rss/customerreviews/page=1/id=570060128/sortby=mostrecent/json)
- [Notion US review feed](https://itunes.apple.com/us/rss/customerreviews/page=1/id=1232780281/sortby=mostrecent/json)
- [CapCut US review feed](https://itunes.apple.com/us/rss/customerreviews/page=1/id=1500855883/sortby=mostrecent/json)

### Local development

Node.js 20.19 or later is required; the Docker image uses Node.js 20. The sole direct runtime dependency is Apify SDK 3.7.2, with a pinned `basic-ftp` 6.2.2 override. Run with `npm start` after installing the locked dependencies. Use `CRAWLEE_STORAGE_DIR` to isolate local data and comparison history.

# Actor input Schema

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

App Store numeric IDs and two-letter country codes. Different storefronts have different reviews. If omitted or empty, the code uses Duolingo US (570060128).

## `maxResults` (type: `integer`):

Run-wide cap on real saved rows after filters. Limits are ceilings, not guaranteed result counts.

## `maxPagesPerApp` (type: `integer`):

Read at most this many recent-feed pages for each app and storefront. Public feeds are a limited recent window, not a complete archive.

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

Keep reviews with a rating at or below this value. Use 2 to focus on one- and two-star feedback.

## `keywords` (type: `array`):

Optional literal, case-insensitive terms. A review must match any one term in its title or body. No regular expressions or semantic sentiment analysis.

## `comparePrevious` (type: `boolean`):

Compare the same app, storefront and review ID with saved history. First-seen means new to this snapshot, not necessarily newly published.

## `onlyChanges` (type: `boolean`):

Implies comparison. Later unchanged runs may save zero rows. Reviews missing from the feed are not reported as deleted.

## `snapshotName` (type: `string`):

Letters, digits, underscores and hyphens only. Reuse this name for scheduled comparisons; use one sequential writer per snapshot.

## `maxRunSeconds` (type: `integer`):

Maximum work budget. Source delays, platform timeout and output limits can end the run earlier.

## Actor input object example

```json
{
  "apps": [
    {
      "appId": "570060128",
      "country": "us"
    }
  ],
  "maxResults": 5,
  "maxPagesPerApp": 1,
  "maxRating": 5,
  "keywords": [],
  "comparePrevious": false,
  "onlyChanges": false,
  "snapshotName": "default",
  "maxRunSeconds": 240
}
```

# Actor output Schema

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

No description

## `summary` (type: `string`):

No description

## `runReport` (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": [
        {
            "appId": "570060128",
            "country": "us"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("maydit/apple-app-store-review-monitor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "apps": [{
            "appId": "570060128",
            "country": "us",
        }] }

# Run the Actor and wait for it to finish
run = client.actor("maydit/apple-app-store-review-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "apps": [
    {
      "appId": "570060128",
      "country": "us"
    }
  ]
}' |
apify call maydit/apple-app-store-review-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,maydit/apple-app-store-review-monitor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/HQvrPjZcHfdulGc6g/builds/Vzbtg5fniz8Hlcnxf/openapi.json
