webatola
← Notes

Tracking App Store keyword ranks without paying for an ASO suite

I was looking for something interesting to build, and since I enjoy building automations, I picked these two: App Store data and Google Trends.

Apple's iTunes Search API (https://itunes.apple.com/search) needs no key and no account, and for a search term like "habit tracker" it returns apps in roughly the order the App Store shows them. That's enough for a free keyword rank tracker. It drifts from the store in a few specific ways, though, and most of this post is about those.

The script

Node 18 or later, built-in fetch, no dependencies:

// rank.mjs: node rank.mjs <appId> <country> "keyword one" "keyword two"
const [, , appId, country, ...words] = process.argv;
for (const term of words) {
  const url = `https://itunes.apple.com/search?term=${encodeURIComponent(term)}&country=${country}&entity=software&limit=200`;
  const { results } = await (await fetch(url)).json();
  const i = results.findIndex((r) => String(r.trackId) === appId);
  console.log(`${term.padEnd(16)} ${i < 0 ? 'not in top ' + results.length : '#' + (i + 1)}   (#1: ${results[0]?.trackName ?? 'none'})`);
  await new Promise((r) => setTimeout(r, 3100)); // Apple allows roughly 20 requests a minute
}

Here it is on 21 October 2026, US store, for Streaks (a habit app, ID 963034692):

$ node rank.mjs 963034692 us "habit tracker" "routine" "streaks" "todo list"
habit tracker    #14   (#1: Habit Tracker)
routine          not in top 193   (#1: Routine Planner, Habit Tracker)
streaks          #1   (#1: Streaks)
todo list        not in top 181   (#1: To Do List MinimaList & Widget)

Streaks is #1 for its own name and #14 for "habit tracker". For "routine" it doesn't appear anywhere in the 193 results, which is odd for an app built around daily routines. For an app like that, "routine" is the word to try in the subtitle or keyword field.

Where the numbers go wrong

limit=200 doesn't mean 200

You ask for 200 and get fewer. For "habit tracker" on the same day, limit=25 returned 23 results, limit=50 returned 47 and limit=200 returned 191. Apple doesn't document why. So a position can be off by one or two compared with the store. Read it as a trend over weeks and don't expect it to match a particular phone.

A phone shows something else

Search ads at the top of the results, personalisation and whatever Apple is testing that week all change the on-device order. The API gives you the organic list without any of that.

Rate limits

Apple's documentation puts the limit at roughly 20 requests a minute, and going faster risks Apple refusing requests for a while, which is what the 3.1-second setTimeout is for. It adds up. 30 keywords in three countries takes about five minutes per run, which is fine for a weekly cron and annoying if you're sitting there waiting for it.

Match on trackId

Bundle IDs work in the Lookup API (/lookup?bundleId=...), but search results are easiest to match by the numeric trackId. Look yours up once and hard-code it.

gb, and never uk

A wrong country code gets you HTTP 400 with "Invalid value(s) for key(s): [country]". The script doesn't check res.ok and would crash on that, so add the check if the country comes from user input.

Top charts

Apple also publishes charts as JSON:

https://rss.marketingtools.apple.com/api/v2/us/apps/top-paid/25/apps.json

Top free and top paid exist. Top grossing is not in this feed any more, so if a tool says it gets grossing data from here, ask where it really comes from. Each entry has name, ID, artist and genre. Ratings and prices come from Lookup, which accepts a comma-separated list of IDs, so one request covers a whole chart.

US top paid, fetched 21 October 2026 at 12:58 Prague time, prices from Lookup:

1  Shadowrocket            $2.99   Utilities
2  HotSchedules            $2.99   Business
3  AnkiMobile Flashcards   $24.99  Education
4  Procreate Pocket        $5.99   Graphics & Design
5  play_music_theory       $1.99   Music

AnkiMobile costs over eight times as much as the two apps above it and still sits at #3.

The feed can be slow. Apple seems to build an uncached feed in the background, and my archive actor has hit 504s after about 30 seconds on the first request for a country, followed by a fast 200 on the retry. Use a short timeout (10 s) and retry two or three times instead of waiting.

History

Neither API keeps any. If you want to see how a rank moved over the last two months, you have to store snapshots yourself, and a cron job writing one JSON file per day into a folder or an S3 bucket is plenty. Charts change through the day, so pick a fixed hour and stick to it, otherwise you end up comparing a morning snapshot with an evening one.

The hosted version

Disclosure: these are mine and they cost money. I packaged the rank check, the chart fetch and a Google Trends reader as Apify Actors, so the schedule and the retries run on Apify instead of on someone's laptop. A keyword-rank check costs $0.002 plus $0.005 per run (with saving the full search results switched off), so a dozen keywords once a week comes to roughly 12 cents a month. The MCP server below charges a bit more per call. The script above gives you the same raw numbers for free if you're happy to run it yourself.

There is also an MCP server version, so you can ask Claude or Cursor about ranks in plain language and skip the JSON. In Cursor, add this to ~/.cursor/mcp.json:

{
  "mcpServers": {
    "app-store-trends": {
      "url": "https://mcp.apify.com/?tools=webatola/app-store-trends-mcp-server"
    }
  }
}

The first call opens a browser to sign in to Apify (OAuth), so there is no token in the file. For Claude Code, or a client without OAuth, use the direct endpoint with your Apify token in a header:

claude mcp add --transport http app-store-trends \
  https://webatola--app-store-trends-mcp-server.apify.actor/mcp \
  --header "Authorization: Bearer YOUR_APIFY_TOKEN"

A prompt like "check where 963034692 ranks in the US and UK for habit tracker, routine and streaks, and tell me which keyword I should target" runs the same lookups as the script and puts the numbers next to the competing apps' ratings. The quirks above still apply, since it's the same Apple data.

Still unexplained: why limit=200 returns 191 results. If you know what Apple filters out, tell me.

Drafted with an AI assistant from the actors I built and real runs on 21 October 2026, reviewed by me.