ASO Keyword Rank Tracker & Monitor - Google Play & App Store avatar

ASO Keyword Rank Tracker & Monitor - Google Play & App Store

Pricing

from $7.30 / 1,000 rank row returneds

Go to Apify Store
ASO Keyword Rank Tracker & Monitor - Google Play & App Store

ASO Keyword Rank Tracker & Monitor - Google Play & App Store

See where apps rank in App Store and Google Play search, in the order each store's own search page shows them. Track your app's keyword rank across countries, get a row when it is not in the results, and use monitoring mode to be told only when a rank moves.

Pricing

from $7.30 / 1,000 rank row returneds

Rating

0.0

(0)

Developer

NeverEmpty

NeverEmpty

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

Share

Check where your app ranks for a keyword in Google Play and the App Store, in the same order each store's own search page shows, across as many countries as you like in one run. List your app and your competitors and you get one row per app, keyword, store and country, including a row that says not-in-shown-results when an app is not on the search page. Leave the app list empty to get every app the search page shows, with its position, developer, rating, installs and price.

Turn on monitoring mode, schedule the Actor, and a run returns a row only when a tracked app's position changed, with the previous position and the positions gained. A change is only reported, and a first position only remembered, if a second read of the page a few seconds later shows the same position.

What you can use it for

  • ASO rank tracking: follow your app's position for the keywords you target, per country, and see which keywords move after a title, subtitle or screenshot change.
  • Competitor monitoring: list rival apps next to yours and see who ranks above you for each keyword.
  • Keyword research: run a list of candidate keywords with an empty app list and see who holds each search page, how many apps it shows, their ratings and installs.
  • Localization checks: run the same keywords in us, gb, de, jp, br and more in one run and compare positions country by country.
  • Agencies and dashboards: export flat rows to a spreadsheet or BI tool, one row per app per search.

How it works

  1. For each keyword, country and store the Actor opens the store's public search page:
    • Google Play: https://play.google.com/store/search?q=<keyword>&c=apps&hl=<language>&gl=<COUNTRY>
    • App Store: Apple's own App Store search page, https://apps.apple.com/<country>/iphone/search?term=<keyword>
  2. It reads the apps in the order the page lists them. On Google Play a large card for one app (for example the Spotify app when you search "spotify") is shown above the list; that app is position 1. Shelves that are not search results (Google Play's "Similar apps", Apple's editorial story cards) are not counted.
  3. Your tracked apps are looked up on that page. An app that is not on it gets rank: null and rankStatus: "not-in-shown-results", together with appsShown, the number of apps the page showed.

How we checked the order (2026-09-16, UTC). For 10 Google Play searches (in six countries, with the page language set to English, German, Japanese or Portuguese) and 6 App Store searches (four countries), the order this Actor reads was identical to the order a browser showed after scrolling to the end of the page. Reading the same four Google Play searches three times from Apify and three times from another network gave the same order in 23 of 24 reads; the one difference was two neighbouring apps (positions 10 and 11) swapping places, which is why monitoring mode reads a page again before reporting a change. Three App Store searches read six times each gave the same order every time.

Why not the iTunes Search API? Apple's public iTunes Search API (itunes.apple.com/search) also lists apps for a term, but not in App Store search order: for "habit tracker" in the US, 5 of its first 11 apps were in a different position than on Apple's App Store search page read at the same time. This Actor does not use it for positions.

How deep it looks

Positions are what the store's search page shows, so the depth depends on the store and the keyword:

StoreApps per search pageMeasured on 2026-09-16 (UTC)
Google Playabout 10 to 50"habit tracker" (US) 22, "spotify" (US) 20, a Japanese keyword for "household budget" (Japan) 16, "vpn" (Germany) 16, "photo editor" (UK) 30
App Storeabout 11 or 12"habit tracker" (US) 11, the same Japanese keyword (Japan) 11, "vpn" (Germany) 12

If your app is further down, you get not-in-shown-results for that search, not a guessed position. Loading more results on these pages did not add apps in a browser either.

Things the stores do that show up in the rows

  • Google Play corrects spelling. Searching "instagarm" shows the results for "instagram". The row then has searchWasAutoCorrected: true and searchCorrectedTo: "instagram".
  • Google Play falls back to English for a language code it does not know. languageShown is the language the page was actually shown in.
  • Neither store says "no results" for a nonsense keyword: both still show a page of apps, and the Actor returns those positions as shown.
  • Apple sends an unknown country to the US store. The Actor compares the storefront on the page with the country you asked for and returns a free country-not-available row instead of US positions. Google Play answers some country codes it does not use (for example xx) with HTTP 400, which gives the same row.
  • Google Play still shows a search page for some countries where it is not offered. For cn and ir it returned a full page (prices in USD) on 2026-09-16 (UTC); those positions are returned as shown, so check that Google Play is available in a country before relying on its rows.
  • A dotted id can be both. Many developers use the same id for the Google Play package and the iOS bundle id (HabitKit uses com.roehl.habitkit in both stores), so a dotted id is looked up in both stores.

Input

FieldDefaultWhat it does
keywordsexample keywordSearch terms, one per line (commas also separate them). A repeated keyword is searched once and gets a free duplicate row. Up to 200 keywords per run.
appsemptyApps to track: a Google Play package name or URL, an App Store id or app page URL (/app/...), or a dotted id (looked up in both stores). A dotted id listed after the Google Play URL of the same app is used as its App Store bundle id. Empty = one row for every app each search page shows. If the list has entries but none of them can be used, the run is rejected with a free invalid-input row and nothing is searched (so a mistyped list never turns into every app on the page). A store none of your apps can be looked up in is not searched and a free no-tracked-app-for-store row says so. Up to 50 apps.
storesbothgooglePlay, appStore or both.
countries["us"]Two-letter country codes. Every keyword is searched in every country. Up to 50 countries; at most 2,000 searches (keywords x countries x stores) per run.
languageenThe language Google Play is asked to show the search in. It changes the Google Play order. The App Store search uses the storefront's own language.
maxResults1000With monitoring off, stop after this many rows and say what was left out. In monitoring mode it does not cut changes.
monitoringModefalseRemember each tracked app's position per keyword, store and country and return a row only when it changes. Needs at least one app.
resetMonitoringStatefalseForget every remembered position and start a fresh baseline. Turn it off again after one run.
useProxytrueRetry a page through a proxy only if a store answers it with a bot check or no answer. The country of the results comes from countries, not from where the request comes from.
{
"keywords": ["habit tracker", "habit app", "daily planner"],
"apps": [
"https://play.google.com/store/apps/details?id=org.isoron.uhabits",
"https://apps.apple.com/us/app/habit-tracker/id1438388363",
"com.roehl.habitkit"
],
"countries": ["us", "gb", "de"],
"monitoringMode": true
}

If you leave keywords out with monitoring off, the example keyword "habit tracker" is searched (the run log says so). In monitoring mode keywords and apps are required, so a schedule never pays to watch the example. An empty list is rejected with a free invalid-input row.

Output

One row per app per search. This one was built from a Google Play search page read through Apify on 2026-09-16 (UTC); the timestamps and the monitoring fields show what a later monitoring run returns after the app moved from position 5 to 3:

{
"source": "app-store-keyword-rank",
"status": "ok",
"scrapedAt": "2026-09-17T09:00:00.000Z",
"keyword": "habit tracker",
"store": "google-play",
"country": "us",
"languageRequested": "en",
"languageShown": "en",
"searchUrl": "https://play.google.com/store/search?q=habit%20tracker&c=apps&hl=en&gl=US",
"searchCorrectedTo": null,
"searchWasAutoCorrected": false,
"isTrackedApp": true,
"trackedAppInput": "com.roehl.habitkit",
"rank": 3,
"rankStatus": "ranked",
"appsShown": 22,
"appId": "com.roehl.habitkit",
"appBundleId": "com.roehl.habitkit",
"appName": "Habit Tracker - HabitKit",
"appSubtitle": null,
"developer": "Sebastian Röhl",
"rating": 4.6,
"ratingCountText": null,
"installs": "500,000+",
"genre": "Productivity",
"price": 0,
"currency": "USD",
"priceText": null,
"isFeaturedCard": false,
"appleResultType": null,
"iconUrl": "https://play-lh.googleusercontent.com/TdubEdRfoPscyyf_UncRx2VKLa08b07wHTNjdGyAP4W63r4Jht1kEry7UF-6UkqzSLSmEUu91hRmkvDhAvPgNg",
"appUrl": "https://play.google.com/store/apps/details?id=com.roehl.habitkit",
"rank1AppId": "org.isoron.uhabits",
"rank1AppName": "Loop Habit Tracker",
"change": "rank-changed",
"isFirstCheck": false,
"previousRank": 5,
"positionsGained": 2,
"previousCheckedAt": "2026-09-17T08:00:00.000Z",
"previousRankReadAt": "2026-09-16T09:00:00.000Z",
"confirmedByRereading": true
}

A tracked app that is not on the page comes back like this (App Store, same search):

{
"store": "app-store",
"trackedAppInput": "https://apps.apple.com/us/app/example/id1234567890",
"rank": null,
"rankStatus": "not-in-shown-results",
"appsShown": 11,
"appId": "1234567890",
"appName": null,
"rank1AppId": "1438388363",
"rank1AppName": "Habit Tracker"
}

What the columns mean:

  • rank is the position on the search page, starting at 1. appsShown is how many apps the page showed.
  • isTrackedApp is true for apps from your apps list (their input is in trackedAppInput) and false when the app list was empty.
  • appId is the Google Play package name or the App Store id. appBundleId is the iOS bundle id on App Store rows (the package name again on Google Play rows).
  • installs (for example "500,000+") is Google Play's install range as the page writes it, in the page's number format. ratingCountText is the rating count as the page writes it: Apple rounds it ("147K" in the US store, a Japanese abbreviation in the Japanese store), and Google Play shows it only for the large card at the top ("36,311,030").
  • price is the price in currency (0 = free); priceText is the price as shown, when the app is paid. The App Store search page shows neither, so both are null there.
  • appSubtitle is the App Store subtitle. appleResultType is Apple's card type (AppSearchResult, or AppEventSearchResult when the app is shown with an in-app event).
  • isFeaturedCard is true for Google Play's large card above the list.
  • languageRequested is language (Google Play only); languageShown is the language of the page.
  • searchWasAutoCorrected and searchCorrectedTo are Google Play only (null on App Store rows).
  • rank1AppId / rank1AppName is the app at position 1 of that search.
  • Every row also has source and scrapedAt.

Monitoring fields

change is null with monitoring off, and in monitoring mode one of:

changeMeaning
"first-check"The app had no remembered position for this keyword, store and country. Every pair is returned once to set the baseline.
"rank-changed"The position is different from the one in the row last returned for the pair.
"entered-results"The app was not on the search page last time and is now.
"left-results"The app was on the search page last time and is not now (rank: null).

previousRank is the position in the row last returned for the pair, positionsGained is previousRank - rank (positive = moved up), previousCheckedAt is when the pair was last checked and previousRankReadAt when that position was read. confirmedByRereading is true when the page was read a second time a few seconds later and showed the same position. If the second read shows a different position, or cannot be read, nothing is returned for the pair and the remembered position stays as it was (a pair with no remembered position yet stays unremembered and is a first check again next time). Because of this second read, a run that finds changes or first checks reads those search pages twice.

Free rows

These rows are never charged and always say why:

statusWhen
no-changeMonitoring mode: none of the tracked apps changed position. Only the check fee applies.
country-not-availableThe store rejected the country code (Apple answered with another storefront, or Google Play answered HTTP 400).
blockedThe store answered with a bot check, an empty page or no answer, even after retrying. Nothing is guessed.
unreadableThe search page could not be read (for example the page layout changed). Nothing is guessed.
no-apps-shownWith an empty app list, the search page showed no apps.
no-tracked-app-for-storeNone of your apps can be looked up in one of the stores you picked, so that store was not searched.
invalid-inputA keyword, app or country could not be used, or the input as a whole was rejected.
duplicateA keyword, app or country repeated an earlier one.
not-checkedRows or searches left out because of maxResults or the per-run limits.
budget-reachedRows or searches left out because the run's maximum total charge was reached.

Pricing

  • $10.00 per 1,000 rank rows (lower on higher Apify plans), charged only for rows with status: "ok". A tracked app that is not on the search page is a charged row too: not-in-shown-results is the answer to "where does my app rank". Free rows are never charged.
  • Monitoring mode: $0.30 per 1,000 app checks, charged for every tracked app on every search page that was read, changed or not. Example: 20 keywords in both stores for one app, checked every hour, is 28,800 checks a month, which is $8.64, plus $10.00 per 1,000 rows for the changes returned.
  • One search page is one request. With an empty app list a search returns every app the page shows (for example 22 rows for "habit tracker" on Google Play in the US), so a run of 10 keywords in both stores returns roughly 300 rows.
  • The run's maximum total charge is respected: the Actor stops reading before a row or a check would go over it and says what was left out.

Scheduling and monitoring tips

  • Save your input as a task, turn on monitoringMode and schedule it (daily, or hourly for launches).
  • Positions are remembered per app, keyword, store, country and Google Play language, for this Actor across all your runs. Do not put the same pairs in two schedules that can run at the same time (Apify's key-value store has no atomic update, so overlapping runs can overwrite each other's records).
  • Use resetMonitoringState once to start a fresh baseline, then turn it off again.

Limits

  • Positions are those of the public web search pages of Google Play (play.google.com) and the App Store (apps.apple.com). The store apps on phones can personalize search, show ads and show a different number of results; this Actor does not see those.
  • Depth is what the search page shows (see the table above); positions further down are not available.
  • Search volume, keyword difficulty and download estimates are not provided.
  • Up to 200 keywords, 50 apps, 50 countries and 2,000 searches per run.