# App Store App Details Tracker - Ratings, Star Counts & Versions (`neverempty/app-store-app-details-tracker`) Actor

For ASO teams, app marketers and competitor trackers: exact rating count, count per star (5 to 1), rating, version, update date, price, size and chart rank of App Store apps. Matched the live store page on 5 of 5 apps checked 2026-09-21. Monitoring mode returns only apps that changed.

- **URL**: https://apify.com/neverempty/app-store-app-details-tracker.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** SEO tools, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.83 / 1,000 app details returneds

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 App Details Tracker - Ratings, Star Counts & Versions

For ASO teams, app marketers and competitor trackers: the exact rating count, the count per star (5 to 1), the average rating, version, last update date, release notes, price, size, minimum OS, age rating and chart position of every App Store app in your list, one row per app. On 2026-09-21 the rating, rating count label, star bars, version, price and size matched what the live App Store page shows in a browser for 5 of 5 apps checked (Instagram, Facebook, Minecraft, Things 3, YouTube). Paste app ids or store URLs, get a clean table, and turn on monitoring mode to receive only the apps whose ratings, release or price changed since the last run.

Export as JSON, CSV or Excel. Unofficial. Public data only.

### What it does

- Reads the public app page `https://apps.apple.com/<country>/app/id<id>` for each app, in the country you choose.
- Many apps per run (up to 1,000), from numeric ids (`389801252`), `id389801252` or URLs, mixed.
- **Exact numbers from the app page itself.** The store shows "30M Ratings"; the row has `ratingCount` 29507938 and the count for each star, which add up to exactly that total.
- **Does not fail on bad entries.** An app that does not exist or is not sold in that country, an unknown country code, a block or a typo comes back as a free row that says why. The run carries on with the rest of the list.
- **Monitoring mode** remembers each app and returns only the apps whose rating, rating count, last update, version or price changed, with the previous value and the difference.

It only reads app pages on apps.apple.com, which its robots.txt allows. It does not use the iTunes lookup or search API or the review RSS feeds (robots.txt of itunes.apple.com disallows those for crawlers). Looking for Google Play? See the sister Actor [Google Play App Details Tracker](https://apify.com/neverempty/google-play-app-details-tracker).

### Input

| Field | What it does |
|---|---|
| `apps` | App Store ids or app URLs. Bundle ids (`com.burbn.instagram`) are not accepted: turning them into an id needs the iTunes lookup API, which robots.txt disallows. A URL's own country (`/jp/`) wins over `country` for that app. Leave it out (with monitoring off) to read two example apps. |
| `country` | Two-letter store country (`us`, `gb`, `de`, `jp`, `in` ...). The rating, rating count, count per star, price, currency and chart position are per country (Minecraft: 838,771 ratings in the US, 106,260 in Japan). Each page is in the store's own language; `pageLanguage` says which. |
| `maxApps` | With monitoring off, stop after this many charged rows (a free row says how many entries were not read). |
| `monitoringMode` | Return an app only when it changed since the row last returned for it. The first run returns every app once. |
| `changeOn` | What counts as a change in monitoring mode: `any`, `rating`, `release` or `price`. |
| `resetMonitoringState` | Forget everything remembered and start over (turn it off again after one run). |
| `useProxy` | Retry a page through a datacenter proxy only when the App Store answers with a rate limit or a block. |

Example:

```json
{
    "apps": ["389801252", "id570060128", "https://apps.apple.com/jp/app/minecraft/id479516143"],
    "country": "us",
    "monitoringMode": true,
    "changeOn": "any"
}
```

### Output

One row per app. Example (Instagram, US, read on 2026-09-21 in the first monitoring run, so it is a first check; monitoring columns that are null on a first check are left out):

```json
{
    "status": "ok",
    "appId": "389801252",
    "appUrl": "https://apps.apple.com/us/app/instagram/id389801252",
    "country": "us",
    "pageLanguage": "en-US",
    "bundleId": "com.burbn.instagram",
    "title": "Instagram",
    "subtitle": "Videos, creators & friends",
    "developer": "Instagram, Inc.",
    "developerId": "389801255",
    "category": "Photo & Video",
    "genres": ["Photo & Video", "Social Networking"],
    "ratingsShown": true,
    "rating": 4.7,
    "ratingCount": 29507938,
    "ratingCountShown": "30M Ratings",
    "ratingHistogram": { "fiveStars": 25307557, "fourStars": 2001517, "threeStars": 696535, "twoStars": 274969, "oneStar": 1227360 },
    "price": 0,
    "currency": "USD",
    "priceText": null,
    "isFree": true,
    "appleArcade": false,
    "inAppPurchases": true,
    "version": "447.0.0",
    "updatedAt": "2026-09-14T21:46:56.000Z",
    "releaseNotesStart": "Performance optimizations and stability improvements for a smoother, more reliable experience.",
    "sizeText": "603.4 MB",
    "sizeMb": 603.4,
    "minimumOsVersion": "16.3",
    "compatibility": "Requires iOS 16.3 or later. Compatible with iPhone, iPad, and iPod touch.",
    "platforms": ["phone", "pad"],
    "ageRating": "13+",
    "languagesCount": 39,
    "editorsChoice": false,
    "chartPosition": 2,
    "chartName": "Photo & Video",
    "chartType": "top-free",
    "descriptionStart": "Little moments lead to big friendships. Share yours on Instagram. ...",
    "descriptionLength": 1353,
    "iconUrl": "https://is1-ssl.mzstatic.com/image/thumb/.../512x512bb.png",
    "change": "first-check",
    "isFirstCheck": true
}
```

#### How exact are the numbers?

- **`ratingCount` is the full number** from the app page's own data (29,507,938 for Instagram); the page shows it rounded ("30M Ratings", kept in `ratingCountShown`). The iTunes lookup API that many scrapers use gave different counts for 3 of 5 apps compared at the same moment on 2026-09-21 (Facebook 13,548 lower than its page); this Actor reads the page.
- **`ratingHistogram`** is the count per star, from 5 stars to 1 star. For all 8 apps read on 2026-09-21 the five counts added up to exactly `ratingCount`, and so did 99 of 99 apps in a 100-app test run the same day. The page data sometimes carries them with a floating-point tail (1020965.0000000001 for Facebook); they are rounded to whole numbers. The star bars shown in a browser matched the split for 5 of 5 apps.
- **`rating`** is the average the page shows, with one decimal (4.7).
- **Numbers did not move between reads.** Reading 8 apps 4 times, one minute apart, and 3 apps again from Apify's own network, gave the same rating count, count per star, average, version, update date, price and size every time. The App Store appears to update these numbers in steps, so any difference in monitoring mode is reported, with no tolerance band.
- **`ratingsShown` is false** when the page shows no rating at all (measured: Final Cut Pro, a Mac-only app, whose page data says 0 ratings). `rating`, `ratingCount` and `ratingHistogram` are then null, never 0.
- **`updatedAt`** is the date of the latest version (UTC); `version` is the version number shown under "What's New" and `releaseNotesStart` is the start of its notes.
- `price` is in `currency` (6.99 USD for Minecraft in the US, 1300 JPY in Japan); `priceText` is what the store shows on the buy button of a paid app ("$6.99") and null for free apps. **Apple Arcade games** (measured: Hello Kitty Island Adventure) come back with `appleArcade: true`, `isFree: false` and `price` null: the page data says 0, but playing needs a paid Apple Arcade plan.
- In monitoring mode, a value that is missing from the page in one run (it was there before) is not reported as a change, and the previous value stays remembered, so a value that disappears and comes back is never charged twice.
- `sizeText` is the size the page shows ("1.3 GB"); `sizeMb` is the same value in MB (1 GB = 1,000 MB, as the store counts).
- `minimumOsVersion` is the first version in `compatibility` (iOS for iPhone apps, macOS for Mac-only apps).
- `chartPosition`, `chartName` and `chartType` (`top-free` or `top-paid`) are the category chart rank shown on the page, when it shows one.
- `category` is the category shown on the page; `genres` lists every category the app is filed under. Both are in the store's language.
- Personal contact details that appear inside `descriptionStart` or `releaseNotesStart` (emails, phone numbers) are replaced with `[email removed]` / `[phone removed]`.

#### Rows that are not charged

Every row has `status`. Only `ok` rows are charged. The others say why in `note`:

| status | Meaning |
|---|---|
| `ok` | The app page was read. Charged. |
| `no-such-app` | The App Store answered 404 for this id in that country. It gives the same answer when the app does not exist, was removed, or is not sold in that country (measured: PayPay is 200 in Japan and 404 in the US), so the row does not say which. |
| `country-not-available` | The App Store sent the request to another country's store (it redirects codes it does not know, such as `xx`, to the US store). The other store's numbers are not returned. |
| `different-app-returned` | The page belonged to another app than the one asked for; not sold as this app. |
| `blocked` | A rate limit, block or empty answer, even after retries. The app is not reported as missing. |
| `unreadable` | The page loaded but its data block was missing or had an unknown shape. |
| `invalid-input` | Not an App Store id or app URL (bundle ids included), or an input setting is wrong. |
| `duplicate` | Same app and country as an earlier entry in the list. |
| `no-change` | Monitoring mode: nothing changed since the rows last returned. Only the check fee applies. |
| `not-checked` | Entries after `maxApps` rows, or beyond the first 1,000. |
| `budget-reached` | The run's maximum total charge left no room for another row; says how many entries were not read. |

In monitoring mode, an app that disappears (404) keeps its remembered values and its free row carries `lastSeenRatingCount`, `lastSeenVersion` and `lastSeenAt`.

### Monitoring mode

Schedule the Actor (for example daily) with `monitoringMode: true`:

1. First run: every app comes back with `change: "first-check"`.
2. Later runs: an app comes back only with `change: "details-changed"`, listing `changedFields` and the previous values (`previousRating`, `previousRatingCount` and `ratingCountDelta`, `previousUpdatedAt`, `previousVersion`, `previousPrice`, `previousCurrency`, `previousCheckedAt`, `previousDetailsReadAt`, `hoursSincePreviousDetails`).
3. An app that is not returned is not charged a row; only the check fee applies to it.

Details are remembered per app, country and `changeOn`, across all your runs; a schedule with a different `changeOn` keeps its own memory and starts with a first check. The same app in two countries is two separate apps (their numbers differ). Do not put the same app in two schedules that can run at the same time: the store has no atomic update, so one run can overwrite what the other remembered.

### Pricing

Pay per event:

- **$2.50 per 1,000 app rows**, charged only for rows with `status: "ok"`.
- In monitoring mode, also **$0.30 per 1,000 app checks**: every app whose page was read is a check, changed or not. 100 apps checked every day is 3,000 checks a month, which is **$0.90** on top of the rows for the apps that changed.
- Free rows (missing apps, blocked pages, bad input, duplicates, notes) are never charged.

The Actor never reads more apps than your run's maximum total charge can pay for. In monitoring mode it reserves a check and a change row for each app before reading it. If the maximum total charge stops a run early, a free row says how many entries were not read.

### Tips

- Paste the URL from your browser: `https://apps.apple.com/gb/app/instagram/id389801252` reads the UK store.
- The same app in several countries: list its URL once per country (`/us/`, `/jp/` ...) or run once per `country`.
- For release tracking only, use `changeOn: "release"`; for price drops, `changeOn: "price"`.

# Actor input Schema

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

Apps to read: a numeric App Store id such as 389801252 (or id389801252), or an app URL such as https://apps.apple.com/us/app/instagram/id389801252. Bundle ids such as com.burbn.instagram are not accepted. If a URL carries its own country (/us/, /jp/ ...), that country wins over the field below for that app. A repeated app (same country) is read and charged once and the repeat gets a free 'duplicate' row. If you leave this field out with monitoring off, two example apps are read; with monitoring on it is required. An empty list is rejected.

## `country` (type: `string`):

Two-letter country code of the App Store to read, such as us, gb, de, jp or in. The rating, rating count, count per star, price, currency and chart position are per country (Minecraft: 838,771 ratings in the US, 106,260 in Japan). The page is in the store's own language (Japanese for jp); the pageLanguage column says which. An app that is not sold in that country answers the same as an app that does not exist (a free 'no-such-app' row). The App Store sends codes it does not know to the US store; those come back as a free 'country-not-available' row instead of US numbers.

## `maxApps` (type: `integer`):

With monitoring off, reading stops once this many charged rows have been returned, and a free row says how many entries at the end of the list were not read. Apps that do not exist and unreadable pages do not count. In monitoring mode it does not limit the changes returned: every app in the list is checked, as far as the run's maximum total charge allows. Up to 1,000 entries are read per run.

## `monitoringMode` (type: `boolean`):

Off = every app comes back with its current details, charged per row. On = the Actor remembers each app's details and, on later runs, returns an app only when its rating, rating count, last update date, version or price changed since the row last returned for it (narrow it with changeOn), with the previous values and the difference. The first run returns every app once to set the baseline. **In monitoring mode every app whose page is read costs $0.30 per 1,000 checks, changed or not** (apps that do not exist, unreadable pages and blocked pages are free), plus the row price for the rows returned. Example: 100 apps every day = 3,000 checks a month = $0.90. The Actor reads only as many apps as the run's maximum total charge can pay for with a check and a change row each. Details are remembered per app, country and changeOn; do not put the same app in two schedules that can run at the same time.

## `changeOn` (type: `string`):

any = the rating, the rating count, the last update date, the version or the price changed. rating = only the rating or the rating count. release = only the last update date or the version. price = only the price or currency. Any difference counts: reading the same pages repeatedly on 2026-09-21 gave the same numbers every time, so there is no tolerance band. Each changeOn setting keeps its own memory, so a schedule with a new setting starts with a first check.

## `resetMonitoringState` (type: `boolean`):

Clears every remembered app for this Actor, so the next monitoring run returns each app once again as a first check. This affects all your monitoring runs, because details are stored per app rather than per list. Turn it off again after one run: left on in a schedule, every run returns every app as a first check and charges the row price for it.

## `useProxy` (type: `boolean`):

Every app page is requested directly first. Only if the App Store answers a page with a rate limit, a block, an empty response or no response is that page retried through a datacenter proxy session; the next app is requested directly again. With it off, that answer is retried directly and then returned as a free 'blocked' row, and no proxy is used.

## Actor input object example

```json
{
  "apps": [
    "389801252",
    "https://apps.apple.com/us/app/duolingo-language-lessons/id570060128"
  ],
  "country": "us",
  "maxApps": 100,
  "monitoringMode": false,
  "changeOn": "any",
  "resetMonitoringState": false,
  "useProxy": true
}
```

# Actor output Schema

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

One row per App Store app whose page was read: app id, bundle id, title, subtitle, developer, category, average rating, exact rating count, count per star (5 to 1), price and currency, in-app purchases, version, last update, release notes, size, minimum OS, platforms, age rating, number of languages, chart position, Editors' Choice, the start of the description and the icon URL; in monitoring mode also which fields changed and their previous values. Apps that do not exist or are not sold in the country, unknown countries, blocked pages and invalid input come back as free rows that say why.

# 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": [
        "389801252",
        "https://apps.apple.com/us/app/duolingo-language-lessons/id570060128"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/app-store-app-details-tracker").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": [
        "389801252",
        "https://apps.apple.com/us/app/duolingo-language-lessons/id570060128",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/app-store-app-details-tracker").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": [
    "389801252",
    "https://apps.apple.com/us/app/duolingo-language-lessons/id570060128"
  ]
}' |
apify call neverempty/app-store-app-details-tracker --silent --output-dataset

```

## MCP server setup

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

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/3VaRbv3vZZTePTZoR/builds/npvyucOJdV2nFqmaI/openapi.json
