Apple doesn't offer a clean public API for App Store details, search rankings, or reviews. If you've tried building your own scraper, you've probably discovered the annoying part: Apple's legacy review feed silently returns an empty 200 response when it's rate-limited, so your scraper happily reports "0 reviews" when there are thousands. Debugging that is a bad afternoon.
In this tutorial we'll skip that pain and pull structured App Store data in a few lines of Python — no Apple Developer account, no API key, no login. We'll use the App Store Scraper actor on Apify, which handles the throttling, retries, and pagination for you. The same approach works in Node if you prefer JavaScript.
What we'll build
Three small scripts, one per mode:
- App details — full metadata for one or more apps.
- Search — ranked results for a keyword (great for ASO).
- Reviews — customer reviews with ratings, version, and helpful votes.
Prerequisites
- Python 3.8+
- A free Apify account and your API token (Settings → Integrations → API token)
- The Apify client:
pip install apify-client
Set your token as an environment variable so it's not hardcoded:
export APIFY_TOKEN="your_token_here"
1. Scrape app details
You identify an app by its App ID — the number in its App Store URL. For apps.apple.com/us/app/instagram/id389801252, the App ID is 389801252.
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run_input = {
"mode": "details",
"appIds": ["389801252", "324684580"], # Instagram, WhatsApp
"country": "us",
}
run = client.actor("freshactors/app-store-scraper").call(run_input=run_input)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(f"{item['title']} — {item['averageUserRating']}★ "
f"({item['userRatingCount']} ratings), v{item['version']}")
A details record looks like this (trimmed):
{"title":"Instagram","appId":"389801252","bundleId":"com.burbn.instagram","sellerName":"Instagram, Inc.","averageUserRating":4.7,"userRatingCount":28910344,"version":"430.1.0","price":0,"formattedPrice":"Free","primaryGenre":"Photo & Video","contentRating":"12+","currentVersionReleaseDate":"2026-05-28","url":"https://apps.apple.com/us/app/id389801252"}
Prefer bundle identifiers? Swap appIds for bundleIds in details mode:
run_input = {
"mode": "details",
"bundleIds": ["com.burbn.instagram"],
"country": "us",
}
2. Search the App Store by keyword
Search returns ranked results carrying the same rich fields as details — perfect for keyword rank tracking or competitor discovery.
run_input = {
"mode": "search",
"searchTerms": ["habit tracker", "budget app"],
"maxSearchResults": 25,
"country": "us",
}
run = client.actor("freshactors/app-store-scraper").call(run_input=run_input)
for i, item in enumerate(client.dataset(run["defaultDatasetId"]).iterate_items(), 1):
print(f"{i:>2}. {item['title']} — {item['sellerName']}"
f"({item['averageUserRating']}★)")
Because each result includes userRatingCount and averageUserRating, you can rank competitors by rating volume or spot which apps dominate a keyword in seconds.
3. Scrape App Store reviews (the throttle-proof part)
This is where naive scrapers fall over. Set mode to reviews, pass appIds, and cap the volume with maxReviewsPerApp (up to 5,000). You can sort by mostRecent or mostHelpful.
run_input = {
"mode": "reviews",
"appIds": ["389801252"],
"maxReviewsPerApp": 500,
"reviewsSort": "mostRecent",
"country": "us",
"lang": "en-US",
}
run = client.actor("freshactors/app-store-scraper").call(run_input=run_input)
reviews = list(client.dataset(run["defaultDatasetId"]).iterate_items())
print(f"Fetched {len(reviews)} reviews")
# Quick rating distribution
from collections import Counter
dist = Counter(r["rating"] for r in reviews)
for stars in range(5, 0, -1):
print(f"{stars}★: {dist.get(stars, 0)}")
Each review record:
{"_type":"review","_schemaVersion":"1.0","appId":"389801252","reviewId":"11583920002","country":"us","rating":1,"title":"Keeps crashing","body":"Crashes on launch since the update on my iPad.","userName":"mark_p","appVersion":"430.1.0","voteSum":10,"voteCount":12,"_scrapedAt":"2026-06-01T07:57:08.714Z"}
Because each review carries appVersion, you can do something genuinely useful — detect a bad release:
from collections import defaultdict
by_version = defaultdict(list)
for r in reviews:
by_version[r["appVersion"]].append(r["rating"])
for version, ratings in sorted(by_version.items()):
avg = sum(ratings) / len(ratings)
print(f"v{version}: {avg:.2f}★ over {len(ratings)} reviews")
If one version's average craters versus the previous one, you've found a regression before it sinks your overall rating.
A note on reliability
The reason this works without babysitting: the actor detects Apple's empty-but-200 throttle response and retries with backoff and proxy rotation, using the modern reviews API for deeper pagination where available and falling back to a hardened RSS path otherwise. Every record carries a _schemaVersion, and the actor is checked by a daily canary against known apps, so your ETL won't silently break when Apple changes something.
What does it cost?
Pricing is pay-per-event, so you only pay for what you get:
- App details: $0.002 per app
- Search results: $0.001 per app
- Reviews: $0.0001 per review
So 500 reviews of one app costs about $0.05 in event fees. (Apify platform compute is billed separately, as with any actor.)
Doing it in Node instead
Same actor, same inputs — just the JS client:
npm install apify-client
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('freshactors/app-store-scraper').call({
mode: 'reviews',
appIds: ['389801252'],
maxReviewsPerApp: 200,
reviewsSort: 'mostHelpful',
country: 'us',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(`Fetched ${items.length} reviews`);
Wrap-up
With one actor and a few lines of code you can pull App Store details, search rankings, and reviews as clean JSON — no API key, no login, and without writing your own throttle-handling logic. Point it at your own app to monitor release sentiment, at competitors for ASO research, or at a whole category for market analysis.
If you want to try it, the actor lives here: App Store Scraper on Apify. It runs in details, search, and reviews modes with the exact inputs shown above. Questions or edge cases? The Issues tab is monitored — keeping it working is the whole point.