For years the way to get App Store reviews as data was Apple's RSS feed: itunes.apple.com/us/rss/customerreviews/id=310633997/sortBy=mostRecent/json, fifty reviews a page, ten pages, one storefront per URL. In 2026 that feed answers an empty feed for most big apps, and when it does answer it stops at 500. The App Store's own web page never stopped: the reviews panel on apps.apple.com calls a JSON endpoint ten reviews at a time by offset, and it keeps answering deep into an app's history. That is the source to read, and this is how to read it for every storefront at once — Apple app reviews, iOS app reviews, from the US to Japan, as rows with a country column.
1. One run, every storefront
The App Store Reviews Scraper on Apify takes app ids, App Store URLs or plain app names, and a list of two-letter storefront codes. Reviews live per storefront, so a US-only export of a global app misses most of them; here countries is a list and the run reads each app on each one.
curl -X POST "https://api.apify.com/v2/acts/kestrel~app-store-reviews-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"appIds": ["310633997"], "countries": ["us", "gb", "de", "fr", "jp", "br", "in"], "sort": "most_recent", "maxReviewsPerApp": 200}'
Seven storefronts, up to 1,400 review rows, seven free app rows and seven free status rows. A review row:
{ "type": "review", "review_id": "13657831289", "app_id": "310633997", "app_name": "WhatsApp Messenger", "country": "us",
"rating": 5, "title": "WhatsApp not bad",
"text": "WhatsApp's not bad at all—it's actually great for what it does. End-to-end encryption keeps your actual messages and calls private…",
"author": "Ed Bradway", "version": null, "is_edited": false,
"review_date": "2026-01-21T04:48:38Z", "review_day": "2026-01-21",
"developer_response": null, "developer_response_date": null,
"url": "https://apps.apple.com/us/app/id310633997?see-all=reviews", "fetched_at": "2026-08-29T06:10:04+00:00" }
rating is an integer 1–5. developer_response is null until the developer answers, which makes it the "unanswered" flag. version is null when Apple's endpoint does not attach one, which it often does not. A review Apple lists on two storefronts is delivered once, by the first storefront to return it.
The free app row is the part the RSS feed never had: per storefront, rating_avg, rating_count, the histogram hist_1 through hist_5, written_reviews, version, price and genre. Seven storefronts in one run is app store ratings by country as a table — WhatsApp's US row carries 18,484,827 ratings, 630,438 of them one star, against 198,881 written reviews.
2. The complaints feed bills only what it keeps
minRating, maxRating, sinceDate and requireText run before billing. A run that reads 300 reviews in most-critical order and keeps 40 pays for 40; the status row's filtered column says how many were dropped for free.
{ "appNames": ["Duolingo"], "countries": ["us", "gb", "de"], "sort": "most_critical", "maxRating": 2, "requireText": true, "maxReviewsPerApp": 300 }
appNames resolves through Apple's own search API on each storefront, so there is no id hunting; when a name is ambiguous, use the id. For app review monitoring on a schedule the pair is sort: "most_recent" and a relative sinceDate — "1 day" on a daily run, "7 days" on a weekly one — never a fixed date that goes stale.
3. Python: reviews and the histogram per storefront
import csv, os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("kestrel/app-store-reviews-scraper").call(run_input={
"appIds": ["310633997", "570060128"], "countries": ["us", "gb", "de", "jp"],
"sort": "most_recent", "sinceDate": "7 days", "maxReviewsPerApp": 500})
rows = list(client.dataset(run["defaultDatasetId"]).iterate_items())
with open("app_ratings.csv", "a", newline="") as f:
w = csv.writer(f)
for r in rows:
if r["type"] == "app": w.writerow([r["fetched_at"][:10], r["app_name"], r["country"], r["rating_avg"], r["rating_count"], r["hist_1"], r["hist_5"], r["written_reviews"]])
for r in rows:
if r["type"] == "review" and r["rating"] <= 2 and r["developer_response"] is None:
print(r["country"], r["app_name"], r["rating"], r["review_day"], (r["title"] or "")[:60])
Run it daily and app_ratings.csv becomes the rating history per storefront that nobody exports for you: whether a release moved rating_avg in Germany, whether the one-star share in Japan is climbing. The second loop is the reply queue — low stars, nobody answered yet.
4. Slack without code (n8n)
The shape is the one the Airbnb and Agoda review templates use in the n8n/ folder of kestrel-actors-examples: an 08:00 Schedule trigger, one HTTP Request to the run-sync endpoint with the complaints input above plus sinceDate: "1 day", a Code node that drops review_ids seen before via workflow static data, an IF on whether anything is left, a Google Sheets append, and one Slack message per review carrying country, rating, title and text. Porting the Airbnb one is the URL, the body and one field: host_reply becomes developer_response.
5. Cost and limits
- $0.004 per delivered
reviewrow;appandstatusrows, filtered reviews, unknown apps and failed storefronts are free. 200 reviews on one storefront: $0.80. The seven-storefront run above, full: $5.60. A daily complaints feed that keeps three reviews: $0.012. - Apple pages ten reviews per request and answers about one request a second per IP; a large run wants several proxy sessions, which the actor manages.
-
rating_countcounts star ratings, most of them with no text. Only written reviews are pageable;written_reviewson theapprow is how many the storefront holds. - It reads the App Store only. Google Play is a different store and a different tool.
- Reviews are public content Apple shows without an account, read through the same endpoint a browser uses. A nickname plus review text is personal data under GDPR, so keep what you need, and read Apple's terms for the App Store website. Not legal advice.
That is what the RSS feed used to be, and more: one call, every storefront, rows past 500, complaints filtered before billing. Full input/output reference and FAQ on the actor page: apify.com/kestrel/app-store-reviews-scraper.
Every actor's inputs, output fields, a sample row and its honest limits are documented at mtedj.github.io/kestrel-actors-examples, including a side-by-side comparison of every review scraper — what each source really carries and where its ceiling is.
Top comments (0)