# App Store Keyword Rank Tracker (ASO): 175 Countries (`mochiboo/app-store-keyword-rank-tracker`) Actor

App Store keyword rank tracker: your app's and competitors' rank for any keyword in all 175 App Store countries. One row per keyword and country with the top apps and competition signals. Schedule it daily for rank history.

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

## Pricing

from $2.00 / 1,000 results

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

## App Store Keyword Rank Tracker (ASO): 175 Countries

App Store keyword rank tracker: your app's and competitors' rank for any keyword in all 175 App Store countries. Give it keywords, countries and the apps you want to follow. For every keyword in every country it writes one row with the rank of each tracked app (1 to 200, or `null` when the app is not in Apple's top 200), the top apps for that search and a few honest competition figures.

Price: Pay per result, at the rate shown in the pricing panel of this page, plus a very small per-run start fee. You are charged only for the results delivered.

### What you get

- **One row per keyword per country, with every tracked app in it.** Track your app and its competitors together: each row lists `tracked`, one entry per app with its rank in that country for that keyword. A row is one result, however many apps you track.
- **Competition signals on each row, from the same answer.** The top-10 average and median rating count, the paid-app count in the top 10 and the top-10 average stars. They are plain figures from Apple's data, with no invented "difficulty score".
- **Built for daily tracking.** Schedule the same input every day with an Apify Schedule and chart the ranks from `scrapedAt`. The actor keeps no memory between runs: every run is a fresh set of rows.

### How the rank is measured

Ranks come from Apple's public App Store search API (iPhone and universal apps), asked once per keyword and country, with the first 200 results. The rank is the position of the app in that list.

This is **the App Store search API ranking, not a device ranking**. It matches the App Store's own web search closely but not always exactly. We checked "habit tracker" in the US on 2026-10-17: all 11 apps of the App Store web search were among the API's first 12, with small swaps (the web's number 2 was the API's number 4, the web's number 3 the API's number 2). What the App Store shows on a phone can also differ by device, language and personalisation. Use it to follow changes over time, not to settle a dispute about one exact position.

### Input

| field | meaning | default |
|---|---|---|
| Keywords | search terms to check, for example `habit tracker`, `budget app` | `habit tracker` |
| Countries | two-letter App Store codes (us, gb, de, jp ...); `["all"]` = all 175 storefronts | `us` |
| Apps to track | your app and competitors: App Store IDs (1438388363) or App Store links | none |
| Top apps per row | how many top-ranked apps to list in each row (0 to 200; 0 = none) | 10 |
| Max rows | 1 to 20000; a hard cap, and the number of rows you pay for | 1000 |

Starting the actor without changing anything checks "habit tracker" in the US: one row.

Every keyword is checked in every country, **keywords in the outer loop, countries in the inner one**: all countries of the first keyword, then the second keyword, and so on. If "Max rows" is smaller than keywords times countries, the run stops after that many rows and the summary says how many checks were left (`checksNotRun`). An unknown country code (for example `uk`; the code for the United Kingdom is `gb`) stops the run before it starts, with a plain message.

### How long a run takes

The actor is polite to Apple. It sends **one request at a time and never more than 18 requests a minute for the whole run**, which is one request every 3.3 seconds, the app lookup and any retries included. Apple's documentation for its search API mentions about 20 calls a minute, and the actor stays under that on purpose, so that Apple does not refuse a long run.

In practice Apple usually needs 3 to 6 seconds to answer a search with 200 results, which is slower than the 3.3 seconds, so a run does **about 12 requests a minute: plan for about 5 seconds per row**. Each row is one search request, so the time is in proportion to the number of rows:

- 15 rows (3 keywords in 5 countries) take about 1.3 minutes.
- 100 rows take about 8.5 minutes.
- 1,000 rows, the default cap, take about 86 minutes.
- All 175 countries for one keyword take about 15 minutes.
- 10 keywords in all 175 countries are 1,750 rows and take about 2.5 hours.

Give a run of more than about 600 rows a time limit that covers 5 seconds a row (Run options, Timeout; or `timeout=7200` in the API call, which covers about 1,400 rows). If the time limit comes first, the run stops cleanly: `terminationReason: "time_budget"`, `complete: false`, the rows written so far are in the dataset and `RUN_SUMMARY` says how many checks were not made. You can also split a big grid over several runs; each run keeps to its own pace.

If Apple refuses a request (for example with HTTP 403, or 429 for too many requests), the run stops at once with `terminationReason: "blocked"`, keeps the rows it has already written, and does not try again or work around the refusal. If it refused before any row was written, the run is marked failed. Network errors and server errors (HTTP 5xx) are retried three times (after 2, 4 and 8 seconds, at the same pace); a check that still gets no answer is listed in `problems` and the run goes on, and three such checks in a row stop the run.

If the platform moves the run to another server, the run restarts itself and carries on: the rows already in the dataset are never requested, written or charged a second time.

### Output

One row per keyword and country. Fields: `keyword`, `country`, `countryName`, `resultCount`, `tracked`, `topApps`, `top10AvgRatingCount`, `top10MedianRatingCount`, `top10PaidCount`, `top10AvgRating`, `scrapedAt`.

- `resultCount`: how many apps Apple returned for the search (at most 200).
- `tracked`: one entry per app you track, in the order you gave them: `appId`, `appName` and `rank`. The `rank` is the 1-based position in Apple's list, or `null` when the app is not among the first 200 results (or not sold in that country). The `appName` comes from Apple's US lookup, so it is the same in every country; an app the lookup does not know takes its name from the search result where it appears, otherwise it is `null`.
- `topApps`: the first "Top apps per row" apps: `rank`, `appId`, `appName`, `developer`, `rating`, `ratingCount`, `price`, `currency`, `genre`, `releaseDate`, `lastUpdated`, `url`. Names, prices and currencies are those of that country's own store, so a name can be translated. A field Apple leaves out is `null`.
- `top10AvgRatingCount`, `top10MedianRatingCount`: the mean (rounded to a whole number, an exact half to the even number) and the median of the rating counts of ranks 1 to 10. A high number means entrenched apps.
- `top10PaidCount`: how many of ranks 1 to 10 cost money (`price` above 0).
- `top10AvgRating`: the mean stars of ranks 1 to 10, to two decimals. Apps without ratings count as 0 ratings and 0 stars in the two averages and the median; with fewer than 10 results the figures use the results there are. When Apple finds no apps at all (`resultCount` is 0) they are `null` (and the paid count 0).
- `scrapedAt`: when the row was made (UTC).

Example (one row, shortened to two top apps):

```json
{
  "keyword": "habit tracker",
  "country": "us",
  "countryName": "United States",
  "resultCount": 192,
  "tracked": [
    {"appId": "1438388363", "appName": "Habit Tracker", "rank": 1},
    {"appId": "1528595748", "appName": "Finch: Self-Care Pet", "rank": 4},
    {"appId": "1232780281", "appName": "Notion: Notes, Tasks, AI", "rank": null}
  ],
  "topApps": [
    {"rank": 1, "appId": "1438388363", "appName": "Habit Tracker", "developer": "Inner Grow Limited", "rating": 4.79129, "ratingCount": 148007, "price": 0, "currency": "USD", "genre": "Productivity", "releaseDate": "2019-01-31T09:10:14Z", "lastUpdated": "2026-10-15T06:46:50Z", "url": "https://apps.apple.com/us/app/habit-tracker/id1438388363?uo=4"},
    {"rank": 2, "appId": "6443918070", "appName": "Habit Tracker - HabitKit", "developer": "Sebastian Roehl", "rating": 4.843, "ratingCount": 2484, "price": 0, "currency": "USD", "genre": "Productivity", "releaseDate": "2022-11-26T08:00:00Z", "lastUpdated": "2026-10-13T07:41:12Z", "url": "https://apps.apple.com/us/app/habit-tracker-habitkit/id6443918070?uo=4"}
  ],
  "top10AvgRatingCount": 106592,
  "top10MedianRatingCount": 13415,
  "top10PaidCount": 0,
  "top10AvgRating": 4.79,
  "scrapedAt": "2026-10-17T18:00:00.000Z"
}
```

Only public data is read: Apple's public search and lookup interfaces, with no login, no account and no cookies. The rows contain app names, developers' public App Store names, ratings and links; no personal contact details.

### Limits you should know

- Apple's search API serves at most **200 apps per search**. A `null` rank means "not in the top 200" for that keyword in that country, not "not found anywhere".
- Only iPhone and universal apps are searched (Apple's `software` search); apps made for iPad only may be missing from the list.
- Ranks are per country: the actor asks each storefront with its own country code. Apple's results move during the day; a second run an hour later can differ.
- Apple matches loosely, but a made-up word can return no apps at all (on 2026-10-23 one returned 1 app in the US and 0 in Germany). If it happens, the row has `resultCount` 0 (no apps, empty `topApps`, `null` ranks) and counts as a result like any other row. When more than 30% of the rows of a run have no apps, `RUN_SUMMARY` says so in `note`, because Apple may also answer with empty lists when it is busy: run again later if you expected results.
- If Apple's search API rejects a country with HTTP 400, that check is skipped (no row), listed in `problems` and counted in `checksRejected`.
- Tracked apps: you may list as many as you like; the lookup that names them is one request per 50 apps.

### Run summary

The key-value store record `RUN_SUMMARY` holds:

- `complete`: true for `finished` and `max_results`, false for every other ending.
- `terminationReason`: `finished` (every check of the grid was made), `max_results` (your "Max rows" was reached; `checksNotRun` says how many checks of the grid were left), `charge_limit` (your maximum cost per run was reached), `time_budget` (the run timeout was near), `blocked` (Apple refused a request), `request_failed` (one or more checks got no answer after the retries, so the result is partial) or `invalid_input` (the input was unusable; nothing was written).
- `rowsPushed`, `checksPlanned`, `checksNotRun`, `checksRejected`, `checksFailed`, `emptyResults` (rows with no apps), `requests`, `retries`, `seconds`, `requestsPerMinuteLimit` (18).
- `problems`: up to 50 short sentences about checks that were skipped or failed. `note`: null, or a plain sentence about rejected countries or many empty answers. `error`: null, or why the run stopped.
- `restarts`: only after the platform restarted the run; the last key.

`requests` and `retries` count the requests the run made; after a platform restart they can miss the last requests of the earlier container.

### Measured on the platform

Measured on 2026-10-23, at 256 MB unless stated. Apple's answer times depend on the day; the numbers about requests and refusals are the ones to trust.

- **Pace:** all 175 storefronts for one keyword ("habit tracker", one tracked app): 175 rows from 176 requests (one lookup and 175 searches) in 902 seconds, which is 11.7 requests a minute or 5.1 seconds a row, under the 18 a minute cap because Apple usually took 3 to 8 seconds to answer a search with 200 results. No refusal, no retry, no storefront rejected, no empty answer. Each storefront returned 179 to 199 apps. Every field of every row, and of every entry in `topApps`, was filled (100%).
- **3 keywords in 5 countries, one tracked app (15 rows):** 15 rows from 16 requests in 79 seconds (53 seconds on a second run, when Apple answered faster), each with `resultCount` above 0 (179 to 193), 10 apps in `topApps` and one `tracked` entry; the app ranked 1 for "habit tracker" in all five countries.
- **Default input (nothing changed):** one row for "habit tracker" in the US, in 2 seconds.
- **Max rows 1** with 2 keywords in 2 countries: exactly one row, `terminationReason: "max_results"`, `checksNotRun: 3`.
- **Time limit:** 100 keywords with a 45-second run timeout: the run stopped by itself after 36 seconds with 8 to 10 rows (two runs), `terminationReason: "time_budget"`, `complete: false`, and wrote `RUN_SUMMARY`.
- **Platform restart:** a 40-row run was restarted twice by the platform (after 13 and 26 rows) and another once (after 16 rows): each ended with 40 rows in the dataset, 40 different keyword-country pairs, no duplicate, `rowsPushed` 40 in `RUN_SUMMARY` and `restarts` 2 and 1.
- **Wrong input:** no keyword, an unknown country code (`zz`) or an app that is not an ID or link (`notion`) fail the run in about a second with the plain message and write no row.
- **Memory:** these runs had 256 MB; the peak was 121 MB over the 175-row run, 84 MB for 15 rows, and 71 MB for 20 rows that each list 200 top apps and 100 tracked apps.

Keywords: aso, app store optimization, keyword rank tracker, app store keyword ranking, app ranking tracker, ios keyword rank, aso tool, app store search rank, competitor app ranking.

# Actor input Schema

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

Search terms to check, e.g. "habit tracker", "budget app". Every keyword is checked in every country below, one row each.

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

Two-letter App Store country codes (us, gb, de, jp ...). Default: us. Use \["all"] for all 175 storefronts. The actor sends one request at a time, never more than 18 a minute, and Apple needs about 5 seconds to answer: 100 rows take about 8.5 minutes, 1,000 rows about 86 minutes.

## `trackedApps` (type: `array`):

Your app and competitors: App Store IDs (1438388363) or App Store links. Each row reports their rank (1 to 200) or null when they are not in Apple's top 200 for that keyword.

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

How many top-ranked apps to list in each row (0 = none). The competition signals always use the top 10.

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

Hard cap on rows (keyword and country checks) for the whole run, and the number of rows you pay for. Checks go keyword by keyword, all countries of one keyword before the next keyword.

## Actor input object example

```json
{
  "keywords": [
    "habit tracker"
  ],
  "countries": [
    "us"
  ],
  "trackedApps": [],
  "topN": 10,
  "maxResults": 1000
}
```

# Actor output Schema

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

One row per keyword and country: the rank of every tracked app, the top apps and the competition signals.

## `runSummary` (type: `string`):

What the run wrote and how it ended (the RUN_SUMMARY record in the default key-value store).

# 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 = {
    "keywords": [
        "habit tracker"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("mochiboo/app-store-keyword-rank-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 = { "keywords": ["habit tracker"] }

# Run the Actor and wait for it to finish
run = client.actor("mochiboo/app-store-keyword-rank-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 '{
  "keywords": [
    "habit tracker"
  ]
}' |
apify call mochiboo/app-store-keyword-rank-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mochiboo/app-store-keyword-rank-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/RzleFdCVN8O2ljHig/builds/flb7esk5aLd6q4Ngb/openapi.json
