App Store Rank Tracker – iOS Keyword Rankings & App Changes
Pricing
from $4.00 / 1,000 keyword checkeds
App Store Rank Tracker – iOS Keyword Rankings & App Changes
Track where iOS apps rank for your keywords in any App Store country, plus version, price and rating changes between runs. Uses Apple's official public iTunes Search and Lookup APIs.
Pricing
from $4.00 / 1,000 keyword checkeds
Rating
0.0
(0)
Developer
skuang
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
6 days ago
Last modified
Categories
Share
Track where iOS apps rank for your keywords in any App Store country, and get told what changed since the last run: rank moves, apps entering or leaving the top N, new versions, price changes and rating changes. Data comes from Apple's official public iTunes Search API and iTunes Lookup API. No login, no scraping of the App Store app, no AI.
About the ranks. The ranks are the order in which Apple's public iTunes Search API returns results for a keyword. That order approximates, but is not identical to, the ranking a user sees when searching inside the App Store app on an iPhone (which can be personalized and is not published by Apple). Apple's ranking can differ from one request to the next even when nothing about the apps changed; a rank move is reported only when two searches a few seconds apart agree (see Rank changes are confirmed). Trends over several runs are still more reliable than a single move.
Who it is for
- App developers and indie studios checking whether their app ranks for the keywords they target, per country.
- ASO consultants and agencies reporting keyword positions and competitor releases to clients on a schedule.
- Competitor watchers who want to know when a rival ships a new version, changes its price or gains ratings.
What it does
- Keyword rankings. For every keyword × country it asks Apple's Search API for N + 10 results (at most 200)
(
https://itunes.apple.com/search?term=...&country=..&entity=software&limit=N+10), because Apple often returns a few fewer than asked, and keeps the top N. It writes onerankingrow with the ranked list and, for every app you track, its rank ornullwith "not in top N". - App snapshots. For every tracked app × country it reads Apple's Lookup API (up to 50 apps in one call per
country) and writes one
approw: version, version release date, release notes, price, average rating, number of ratings, genres, seller and more. - Changes. It compares with the snapshot saved by the previous run and writes one
changerow per change. The first run only saves the baseline; changes appear from the second run on. - A run summary (per target: ok / partial / failed / skipped, ranks, changes, errors) goes to the
OUTPUTrecord.
Input
| Field | Default | What it does |
|---|---|---|
keywords | ["habit tracker"] | Up to 50 search terms. Keywords × countries can be at most 100 searches per run. Case and extra spaces are ignored for duplicates. |
countries | ["us"] | Up to 10 two-letter App Store country codes. uk is accepted as gb. |
trackApps | ["1438388363"] | Up to 50 App Store URLs or numeric app IDs (yours and competitors'). The default is "Habit Tracker" by Inner Grow Limited. Can be empty. |
topN | 10 | 1–200 results kept per keyword and country, and how deep tracked apps are looked for. Apple often returns a few fewer than requested. |
trackChanges | true | Compare with the previous run and write change rows. |
outputMode | all | all (rankings, app snapshots, changes), changes_only (cheapest for daily monitoring) or rankings_only (no app lookups and no changes). |
Example:
{"keywords": ["habit tracker", "meditation"],"countries": ["us", "gb"],"trackApps": ["https://apps.apple.com/us/app/habit-tracker/id1438388363", "1528595748"],"topN": 10,"trackChanges": true,"outputMode": "all"}
A keyword that is empty, a country code that is not two letters or that Apple does not recognize, or an entry in
trackApps that is not an App Store URL or ID is reported as failed in OUTPUT; the rest of the run continues.
Output
All rows go to the run's dataset; type tells them apart. The dataset has three views: Keyword rankings, App
snapshots and Changes.
Ranking row (type: "ranking", one per keyword × country)
Trimmed from a live run on 2026-10-12:
{"type": "ranking","keyword": "habit tracker","country": "us","topN": 10,"resultsReturned": 9,"trackedApps": [{ "trackId": 1438388363, "name": "Habit Tracker", "rank": 1, "note": null },{ "trackId": 1528595748, "name": "Finch: Self-Care Pet", "rank": 4, "note": null }],"topResults": [{"rank": 1,"trackId": 1438388363,"name": "Habit Tracker","seller": "Inner Grow Limited","price": 0,"currency": "USD","averageUserRating": 4.79168,"userRatingCount": 147818},"... up to topN entries"],"checkedAt": "2026-10-12T11:35:20.112Z"}
An app that is not among the results has "rank": null, "note": "not in top 10".
App row (type: "app", one per tracked app × country)
{"type": "app","trackId": 1438388363,"name": "Habit Tracker","seller": "Inner Grow Limited","bundleId": "com.davetech.habit","url": "https://apps.apple.com/us/app/habit-tracker/id1438388363","version": "2.25.1","currentVersionReleaseDate": "2026-10-09T23:42:16Z","releaseNotes": "- support iOS 27\n- fix bugs\n...","price": 0,"formattedPrice": "Free","currency": "USD","averageUserRating": 4.79168,"userRatingCount": 147818,"averageUserRatingForCurrentVersion": 4.79168,"userRatingCountForCurrentVersion": 147818,"primaryGenreName": "Productivity","genres": ["Productivity", "Health & Fitness"],"releaseDate": "2019-01-31T09:10:14Z","minimumOsVersion": "15.0","fileSizeBytes": 412291072,"contentAdvisoryRating": "4+","iconUrl": "https://is1-ssl.mzstatic.com/image/thumb/.../100x100bb.jpg","country": "us","checkedAt": "2026-10-12T11:35:16.420Z"}
Change row (type: "change")
Illustrative example (the values show the shape, not a recorded change):
{"type": "change","changeType": "rank_changed","country": "us","trackId": 1528595748,"appName": "Finch: Self-Care Pet","keyword": "habit tracker","topN": 10,"before": 4,"after": 3,"releaseNotes": null,"detectedAt": "2026-10-13T11:35:20.112Z"}
changeType | Target | before → after |
|---|---|---|
rank_changed | keyword × country | old rank → new rank (both within the top N) |
entered_top_n | keyword × country | null → rank |
left_top_n | keyword × country | rank → null |
new_version | app × country | { version, releaseDate } → { version, releaseDate }, only when the release date moved forward; releaseNotes holds the notes |
price_changed | app × country | old price → new price (in the country's currency) |
rating_changed | app × country | average rating, when it moved by 0.05 or more from the value last reported |
rating_count_changed | app × country | number of ratings, when it moved by at least max(10, 1%) from the value last reported |
Rank changes are reported for the apps in trackApps, not for every app in the top N (the full list is in each
ranking row). An app you add to trackApps later starts its own baseline; it gets no change row on its first run.
Rank changes are confirmed
Apple's ranking can differ from one request to the next even when nothing about the apps changed. When a run finds that a
tracked app's rank differs from the saved one, it repeats the same search once (same result count, within the rate
limit) and reports only the moves both searches agree on: the same new rank, or out of the top N in both. A move the
second search does not repeat is not reported and not charged; the saved rank stays as it was, so the app is checked
again on the next run. The number of such unconfirmed moves is in OUTPUT (changesUnconfirmed). If the confirming
search itself fails, or the run is stopping (charge limit reached, aborted, Apple refusing requests) before it can be
made, no rank change is reported for that keyword in that run and the saved ranks stay as they were.
new_version is reported only when the version differs and its release date is later than the saved one. Apple's
Lookup API can briefly serve an older record; such a step back is ignored and the saved version is kept. A field Apple
does not report in a run keeps its saved value.
Both rating changes compare with the value last reported (the baseline, or the "after" of the last row of that type),
so a slow drift adds up and is reported once it crosses the threshold. rating_changed needs a move of 0.05 or more.
rating_count_changed needs a move of at least 10 ratings or 1% of the last reported count, whichever is larger
(for example 10 for an app with 300 ratings, 1,479 for one with 147,818), so a popular app gaining ratings every day
does not produce a row on every run. The thresholds are fixed.
Run summary (OUTPUT record)
OUTPUT in the default key-value store has totals, apps (one entry per app × country), keywords (one per
keyword × country, with the tracked ranks) and invalidInputs. Each entry has:
status:ok,partial(some rows were delivered, then the charge limit or an error stopped it),failed(Apple's API could not be read after retries, the country is unknown, the app is not available in that country, or the input entry is invalid) orskipped(not processed because the charge limit was reached or the run was aborted).baseline:created(first run, nothing compared),compared,reset-top-n-changed,reset-request-changed(the saved ranks were fetched with a different request size, for example after an update of this actor),kept(nothing delivered, the previous snapshot was left untouched) ornot-tracked.errorTypeanderrorwhen something went wrong.
The run fails only if every target failed.
How change tracking works
- First run = baseline. It saves a snapshot per keyword × country and per app × country and reports no changes. Schedule the actor (daily is typical); each later run compares with the previous one.
- Snapshots live in your account, in a named key-value store called
app-store-rank-tracker-state-wwc4swiilbljk0oas(the name ends with this actor's ID in lowercase, so it does not clash with the state store of another actor that has the same name), one record per target. All runs of this actor in your account share them, so two schedules on the same keyword see only the changes since the other one ran, as long as they do not run at the same time; overlapping runs can both report (and charge for) the same change. Delete a record to start that target over. - Keep
topNthe same between runs. Apple's order can depend on how many results are requested, so a differenttopN(or a different request size, which the snapshot records) starts a new rank baseline for that keyword instead of reporting rank changes. - Undelivered changes come back, delivered ones do not. If the charge limit stops a run while change rows are being written, the snapshot keeps the old value for each change that was not delivered, so the next run reports it again.
rankings_onlyneither reads nor updates the snapshots.
Pricing
Pay per event. You pay only for rows actually written to your dataset:
| Event | When | Price |
|---|---|---|
keyword-checked | One ranking row written (one keyword × one country) | $0.004 ($4.00 per 1,000 searches) |
app-snapshot | One app snapshot row written (one app × one country) | $0.002 ($2.00 per 1,000 snapshots) |
change-detected | One change row written | $0.003 ($3.00 per 1,000 changes) |
Changes are only written from the second run on (the first run sets the baseline). Searches, lookups and failed targets are not charged.
Example: 10 keywords × 2 countries with 3 tracked apps, outputMode: "all", daily: 20 rankings ($0.08) + 6 app
snapshots ($0.012) + the day's changes (say 8 × $0.003 = $0.024) ≈ $0.12 per run. With changes_only you pay only for
the changes.
If you set a maximum cost per run, the actor stops cleanly when it is reached, says so in the status message and in
OUTPUT, and marks targets that were not started skipped and targets cut short partial.
Limitations
- Ranks come from Apple's public iTunes Search API, which approximates but is not identical to the ranking inside the App Store app, and can vary between runs. There is no search volume, keyword difficulty or download data.
- Apple returns at most 200 results per search, often a few fewer than requested (for example 9 for
limit=10). For a term that matches nothing, Apple still returns a handful of loosely related apps. - iPhone and iPad apps only (
entity=software); no Mac apps, no Apple Arcade filter. - Rate limit. Apple documents roughly 20 calls per minute per IP. The actor starts at most one call every 3.5 s
(about 17 per minute) and retries HTTP 403, 429, 5xx, timeouts and network errors up to 3 times (waiting 10, 20, then
40 s, or what Apple's Retry-After says, up to 60 s). After that the target is reported
failed. When 3 Apple calls in a row fail that way, the actor stops calling Apple and marks the remaining targetsfailedwith "Apple is refusing requests from this IP". - Run time. A full run (100 searches, up to 100 confirming searches when ranks moved, 10 lookups) makes at most 210 calls, about 12 minutes when Apple answers normally (a default run with one keyword takes under 15 s). Each failing call can take up to about 5 minutes (4 attempts with a 30 s timeout each, plus up to 3 waits of 60 s). Three failures in a row stop the run, but failures mixed with successes do not, so in the worst case a run can take hours; set a run timeout (for example 1 hour) in the run options if that matters to you.
- Prices, ratings and versions are per country storefront, as Apple reports them. Ratings are Apple's all-time averages (with a separate current-version average in the app row); there are no review texts.
- These are public Apple APIs that Apple can change or limit at any time.
FAQ
Why does the rank differ from what I see on my iPhone? The App Store app's search is personalized and uses signals Apple does not publish. This actor reports what Apple's public Search API returns, which is a consistent, reproducible approximation.
Why did the first run report no changes? There was nothing to compare with yet. Changes appear from the second run.
Do I need proxies? No. The actor calls Apple's public API directly, with a clear user agent, below Apple's documented rate limit.
What happens if the run is moved to another server or restarted? PROGRESS is saved after every push of rows to
the dataset, right after they are saved and charged. When Apify restarts the run elsewhere (a migration, or a
resurrected run after a crash), it skips what was already delivered, so nothing is charged twice. Targets that had
finished before the move are not redone.