# App Store Top Charts Monitor - Rank Changes & New #1 Alerts (`neverempty/app-store-top-charts-monitor`) Actor

App Store Top Charts (Top Free, Top Paid) for 175 countries from Apple's own RSS feed: rank, app, developer, app ID, URL. Monitoring mode returns only apps that entered, left, moved N places or took number 1, with the previous rank. Track your app and rivals by ID.

- **URL**: https://apify.com/neverempty/app-store-top-charts-monitor.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.40 / 1,000 app 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 Top Charts Monitor - Rank Changes & New #1 Alerts

Get the **App Store Top Charts** (Top Free Apps and Top Paid Apps) for any of 175 countries, one clean row per app: rank, app name, developer, app ID, URL, country, chart type and the time it was read. Then turn on **monitoring mode**, schedule the Actor, and each run returns only what changed: apps that **entered the chart**, apps that **left it**, apps that **moved by at least the number of places you set**, and a **new number 1**, each with its previous rank.

Built for app developers, ASO and user acquisition teams, publishers and analysts who look at their own rank and their competitors' ranks every day, and for anyone who needs a dated record of which app was number 1 in a country (for example to check how an "App Store number 1" prediction market resolves).

- **Many countries and both charts in one run.** `us`, `gb`, `de`, `jp`, `fr`, `br`, `in`, `kr`... Each country and chart type is one chart, up to 100 charts per run.
- **Monitoring mode for alerts.** The first run remembers the chart (free). Later runs return only the changes, with `previousRank`, `rankChange` and, for a new number 1, the app it replaced. Send the rows to Slack, email, a sheet or a webhook with an Apify integration.
- **Watch only your apps.** Put App Store IDs or app URLs in `appIds` and only those apps are returned or watched: your app and your competitors.
- **Apple's own feed.** The ranks come from the JSON feed that Apple's RSS Feed Generator (rss.marketingtools.apple.com) publishes. No login, no API key, no browser, no proxy.
- **Nothing is guessed.** A value Apple does not give is `null`. An app that is not in the chart, a country that is not a storefront, a chart Apple did not answer for, or a run that hit its charge limit comes back as a free row that says why.

Unofficial. Not affiliated with, endorsed by or sponsored by Apple. App Store is a trademark of Apple Inc.

Sister Actors: [ASO Keyword Rank Tracker & Monitor](https://apify.com/neverempty/app-store-keyword-rank-tracker) (where an app ranks in App Store and Google Play **search**) and [App Store App Details Tracker](https://apify.com/neverempty/app-store-app-details-tracker) (ratings, star counts, versions and price of each app).

### What you get

One row per app:

| Field | Example | Meaning |
|---|---|---|
| `country`, `countryName` | `us`, `United States` | The storefront you asked for |
| `chartType`, `chartName` | `top-free`, `Top Free Apps` | The chart, and its title as Apple writes it (in the storefront's language) |
| `rank` | `3` | Position in Apple's list. `null` on a `dropped-out` row |
| `appId` | `6448311069` | The App Store ID |
| `appName`, `developer` | `ChatGPT`, `OpenAI OpCo, LLC` | As Apple lists them in that storefront |
| `appUrl` | `https://apps.apple.com/us/app/chatgpt/id6448311069` | The app's store page |
| `primaryGenre`, `genres`, `genreIds` | `Utilities`, `["Utilities","Entertainment"]`, `["6002","6016"]` | Genres when Apple's feed gives them. See the limits below: they are `null` for the first 10 apps of every chart |
| `releaseDate` | `2023-05-18` | The app's release date in the feed |
| `iconUrl` | image URL | 100 x 100 icon |
| `chartSize`, `topRequested` | `50`, `50` | How many apps Apple returned for the chart, and how many you asked for |
| `changeType` | `new-entry` / `dropped-out` / `moved-up` / `moved-down` / `new-number-one` | Monitoring mode only; `null` otherwise |
| `previousRank`, `rankChange`, `previousRankAt` | `8`, `5`, ISO time | Monitoring mode: the rank this watch last reported (or remembered) for the app, the places gained (negative = lost) and when that rank was remembered |
| `replacedAppId`, `replacedAppName` | | On a `new-number-one` row: the app that was number 1 before |
| `feedGeneratedAt` | ISO time | The `updated` time in Apple's feed. It is the time Apple built the answer, not the time the ranks last changed |
| `checkedAt` | ISO time | When this run read the chart |

Example row (US Top Free, monitoring mode):

```json
{
  "status": "ok",
  "changeType": "moved-up",
  "country": "us",
  "countryName": "United States",
  "chartType": "top-free",
  "chartName": "Top Free Apps",
  "rank": 15,
  "previousRank": 20,
  "rankChange": 5,
  "previousRankAt": "2026-10-06T10:00:00.000Z",
  "appId": "1532016072",
  "appName": "Cinnabon",
  "developer": "Cinnabon® Franchisor SPV LLC.",
  "primaryGenre": "Food & Drink",
  "appUrl": "https://apps.apple.com/us/app/cinnabon/id1532016072",
  "chartSize": 30,
  "topRequested": 30,
  "checkedAt": "2026-10-06T11:00:00.000Z"
}
```

#### Free rows (not charged)

| `status` | When |
|---|---|
| `baseline-saved` | Monitoring mode, first check of a chart: the ranks were remembered, no app rows are returned |
| `no-change` | Monitoring mode: nothing to report for that chart (the chart check itself is charged, see Pricing) |
| `not-in-chart` | An app from `appIds` is not in the part of the chart that was read (monitoring off) |
| `unknown-country` | The code is not one of the 175 storefronts (for example `uk`; Apple uses `gb`). Nothing is requested for it |
| `bad-input` | A value is not a country code or not an app ID, an option is out of range, or monitoring mode was started without a country |
| `empty-chart` | Apple answered with an empty list |
| `short-list` | Monitoring mode: Apple returned a much shorter list than last time, so it was not compared |
| `unreadable`, `blocked` | Apple did not answer for that chart after the retries, or refused |
| `robots-unreadable`, `disallowed-by-robots` | The site's robots.txt could not be read (and there is no copy from the last 24 hours), or it disallows the address. Nothing is requested then |
| `timed-out` | The run was about to reach its timeout, so that chart was not read |
| `budget-reached` | The run reached its maximum total charge; the row lists what was not returned |

If no chart could be read at all, the run is not charged and ends with an error so your schedule notices.

### Input

| Field | Default | Notes |
|---|---|---|
| `countries` | empty = `us` (monitoring mode needs at least one) | Two-letter storefront codes. Upper case is accepted. Duplicates are read once |
| `charts` | `top-free` | `top-free`, `top-paid` or both |
| `topN` | 50 | 1 to 100 apps per chart |
| `appIds` | empty = whole chart | IDs (`389801252`), `id389801252` or apps.apple.com app URLs, up to 200 |
| `monitoringMode` | off | Only return changes (below) |
| `minRankChange` | 5 | Monitoring mode: report an app that moved at least this many places |
| `watchName` | empty | Separate memory for separate schedules |
| `resetMonitoringState` | off | Forget the watch memory at the start of this run |

Example: watch the top 50 free and paid apps in three countries.

```json
{
  "countries": ["us", "gb", "jp"],
  "charts": ["top-free", "top-paid"],
  "topN": 50,
  "monitoringMode": true,
  "minRankChange": 5
}
```

Example: watch only your app and two competitors in the US top 100.

```json
{
  "countries": ["us"],
  "topN": 100,
  "appIds": ["6448311069", "6477489729", "https://apps.apple.com/us/app/claude-by-anthropic/id6473753684"],
  "monitoringMode": true,
  "minRankChange": 3
}
```

### Monitoring mode

1. **First run.** Each chart is remembered as a starting point. No app rows, nothing charged; one free `baseline-saved` row per chart. (Run once with monitoring off if you also want the full chart.)
2. **Later runs.** Each chart is compared with what the watch remembers and you get a row for:
   - `new-entry`: an app that is in the top `topN` now and was not before.
   - `dropped-out`: an app that was in it and is not any more (`rank` is `null`, `previousRank` says where it was; name and developer are the remembered ones).
   - `moved-up` / `moved-down`: an app whose rank differs by at least `minRankChange` from the rank in **the last row you received for it** (or from the starting point). An app that climbs 2 places a day is reported on the day the total reaches your number, not never.
   - `new-number-one`: the app at rank 1 is not the one that was there at the last check. This is always reported, whatever `minRankChange` is, with `replacedAppId` and `replacedAppName`.
3. **No change.** A chart with nothing to report returns one free `no-change` row and costs one chart check.
4. With `appIds`, only those apps are compared and reported. An app you add later is remembered on its first run and reported from the next one.
5. If a run reaches its maximum total charge before all changes are returned, the changes that were not returned are not marked as reported, so the next run returns them.

The memory is kept per `watchName`, country, chart type, `topN` and whole-chart or `appIds` use. Changing `topN` starts a new starting point. Two runs with the same `watchName` at the same moment can overwrite each other's memory; give each schedule its own name.

An app that sits right at the edge of your `topN` can enter and leave on consecutive runs. If that is noise for you, watch a deeper chart or list the apps you care about in `appIds`.

### Pricing

Pay per event, no start fee:

| Event | Price |
|---|---|
| `app-returned` | $2.00 per 1,000 app rows (lower on higher Apify plans) |
| `chart-checked` | $1.00 per 1,000 chart checks: charged in monitoring mode only for a chart that was compared and had nothing to report |

A full top-100 chart is $0.20. Watching the US Top Free 50 every hour costs about $0.024 a day in chart checks on a day with no change, plus $0.002 for each change returned. The first monitoring run and all the free rows above cost nothing.

### Where the data comes from, and its limits

- **Source.** `https://rss.marketingtools.apple.com/api/v2/<country>/apps/<top-free|top-paid>/<count>/apps.json`, the feed URLs Apple's RSS Feed Generator hands out. The site's robots.txt has no rules. It is read on every run and each address is checked against it; when robots.txt itself times out, the copy read in the last 24 hours is used, and without one nothing is requested. `itunes.apple.com` is not used.
- **Which chart this is.** On 2026-10-06 the first 25 apps of the US `top-free` feed were the same apps in the same order as the first 25 of the Top Free Apps list for iPhone on apps.apple.com (25 of 25). This was checked once, for one country. The ranks in the rows are the ranks in Apple's feed.
- **Only Top Free and Top Paid, only the overall chart, up to 100 apps.** Apple's generator offers no Top Grossing feed and no per-category feed for apps (a `top-grossing` address answers "Not Found"), and a count above 100 is refused. iPad-only and Mac charts are not offered either.
- **Apple sometimes returns a few apps fewer than asked.** On 2026-10-06, asked for 50, the UK Top Paid feed held 47 apps and the Japan Top Paid feed 49 (the other 8 charts checked held 50). `chartSize` is the number Apple returned, and `rank` is the position in that list.
- **Genres are missing for the first 10 apps.** In every feed checked (US 100, Japan 50, US Paid 10) Apple left `genres` empty for positions 1 to 10 and filled them from position 11. Those rows have `primaryGenre: null`; nothing is filled in from elsewhere.
- **Apple builds each feed when it is asked.** A feed that nobody asked for recently takes 10 to 30 seconds or times out, and the same address answers in about a second right after. Measured from Apify on 2026-10-06: 40 requests (10 countries, both charts, twice) gave 30 answers; 18 of the 20 charts answered at least once in two tries. The Actor waits up to 45 seconds, asks up to 3 times in a row and once more at the end of the run; a chart that still does not answer comes back as a free `unreadable` row and is read again on your next run.
- **How often the ranks change.** Not in real time. `feedGeneratedAt` changes on every read, so it does not tell you when the ranks changed. In our check on 2026-10-05 the US Top Paid and Japan Top Free feeds (top 50) were read every 15 minutes from 16:23 to 18:20 UTC (about 1 hour 57 minutes) and the US Top Free feed from 16:23 to 17:48 UTC (its later reads failed), and not one rank changed in that time, so the feed is refreshed less often than that; how often exactly was not measured. Schedule the Actor hourly or daily rather than every few minutes.
- Each run reads each chart once. Requests go out one at a time, directly, with a User-Agent that names this Actor.

### FAQ

**Can I get Top Grossing or a category chart (Games, Finance...)?** Not from this Actor. Apple's feed generator does not publish them for apps.

**Can I get Google Play charts?** No. This Actor reads the App Store only.

**Why is an app I know is popular not in the rows?** It is not in the top `topN` of that chart in that country, or you are looking at the paid chart for a free app. With `appIds` and monitoring off you get a `not-in-chart` row for it.

**Is the rank the same as on my iPhone?** The feed matched the iPhone Top Free list when it was compared (see above). Apple can show different charts per device, and the feed can be a little behind the store.

### Thanks for using this Actor

We build these tools for people who run them every day, and we improve them from what users tell us.

- **Missing a field, or need another filter?** Tell us in the **Issues** tab. If the data is there, we add it.
- **Found a bug or a wrong value?** Post the run ID in the **Issues** tab. Wrong data is the thing we fix first.

If this Actor saved you time, a short review helps other people find it.

# Actor input Schema

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

Two-letter App Store storefront codes (us, gb, de, jp, fr, br, in, kr...), one per line. 175 storefronts are available, the same list as in Apple's RSS Feed Generator. A code that is not a storefront (for example uk instead of gb) comes back as a free row that says so. Each country and chart type is one chart. Up to 100 charts per run. Empty = us (not allowed in monitoring mode).

## `charts` (type: `array`):

Which charts to read for each country. Apple's feed offers Top Free and Top Paid apps (no Top Grossing and no per-category charts). Empty = Top Free.

## `topN` (type: `integer`):

How deep to read each chart, from 1 to 100 (Apple's feed stops at 100). Empty = 50. With monitoring off, each app is one result row. In monitoring mode this is the part of the chart that is watched: an app that falls below it counts as leaving the chart. Changing this number starts a new starting point.

## `appIds` (type: `array`):

Optional. App Store app IDs (389801252), id389801252, or apps.apple.com app URLs, one per line. When given, only these apps are returned or watched: your app and your competitors. An app that is not in the chart comes back as a free row. Up to 200. Empty = the whole chart.

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

On = for scheduled runs and alerts. The first run remembers each chart as a starting point and returns no app rows (free). Later runs return a row only for an app that entered the chart, left it, moved by at least the number of places set below, or became number 1, each with its previous rank. A chart with no change costs one chart check and returns a free no-change row. Off (or empty) = always return the chart.

## `minRankChange` (type: `integer`):

Monitoring mode only. An app still in the chart is returned when its rank differs by at least this many places from the rank in the last row you received for it (or from the starting point). Empty = 5. Entering the chart, leaving it and a new number 1 are always returned.

## `watchName` (type: `string`):

Optional, used with monitoring mode. Runs with the same watch name share what is remembered. Give a different name to each schedule. Do not run two schedules with the same watch name at the same time. Letters, digits, dot, dash and underscore.

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

Forget what this watch remembered at the start of this run, so every chart gets a new starting point. Turn it off again for scheduled runs.

## Actor input object example

```json
{
  "countries": [
    "us"
  ],
  "charts": [
    "top-free"
  ],
  "topN": 50,
  "minRankChange": 5
}
```

# Actor output Schema

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

One row per app in a chart: country, chart type, rank, app name, developer, app ID, genres when Apple gives them, URL and the time it was read. In monitoring mode only apps that entered the chart, left it, moved by the number of places you set or became number 1, with the previous rank. A country that is not a storefront, an app that is not in the chart, a chart Apple did not answer for, or a run that hit its maximum charge comes back as a free row that says 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 = {
    "countries": [
        "us"
    ],
    "charts": [
        "top-free"
    ],
    "topN": 50,
    "minRankChange": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/app-store-top-charts-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 = {
    "countries": ["us"],
    "charts": ["top-free"],
    "topN": 50,
    "minRankChange": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/app-store-top-charts-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 '{
  "countries": [
    "us"
  ],
  "charts": [
    "top-free"
  ],
  "topN": 50,
  "minRankChange": 5
}' |
apify call neverempty/app-store-top-charts-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/app-store-top-charts-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/SnaQa4wuREqNYg5MM/builds/XJ0xIgEPOMr24RbiJ/openapi.json
