# Apple App Store Review Monitor

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.

Canonical product page: https://mayd-it.com/data-tools/actors/apple-app-store-review-monitor/

## Example input

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

## Full guide

# 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.

