App Store Reviews Scraper | Full History, Dev Replies, $0.50/1K avatar

App Store Reviews Scraper | Full History, Dev Replies, $0.50/1K

Pricing

from $0.30 / 1,000 review scrapeds

Go to Apify Store
App Store Reviews Scraper | Full History, Dev Replies, $0.50/1K

App Store Reviews Scraper | Full History, Dev Replies, $0.50/1K

Full review history of any iOS app in 184 country stores: rating histogram, version numbers, optional developer replies, incremental runs. MCP-ready. Pay per review, no start fee.

Pricing

from $0.30 / 1,000 review scrapeds

Rating

0.0

(0)

Developer

Kesar Chen

Kesar Chen

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

1

Monthly active users

10 days ago

Last modified

Security

Limited permissions

Share

App Store Reviews Scraper

Pull the full review history of any iOS app, in any of the 184 country stores, as clean JSON. Not the newest 500: the whole history as far as Apple still serves it, newest first and filterable by date, rating and keyword, with a 1–5 star histogram, optional developer replies, and an incremental mode that returns only what is new since your last run. $0.50 per 1,000 reviews, no start fee: a 30,000-review app costs about $15 once, and a daily watch on 10 competitors in 3 countries runs about $3 a month.

Why this one

  • Deep and date-sortable at the same time. Most deep review pulls come back in relevance order and cannot be sliced by date. This one walks the full history newest-first, so sinceDate: "12 months" means exactly that, and maxRating: 2 with keywords: ["crash"] returns only the rows you will read.
  • Only new reviews on every scheduled run. Turn on incremental and the Actor remembers what it has already returned per app and country. Your dataset never needs deduping and you never pay twice for the same review.
  • You always know how deep it got. Every summary row carries coverageDepth and holes, every review row carries a source level, and the sources are probed at the start of each run and monitored daily. A quiet change upstream degrades the output and says so; it never masquerades as "no more reviews".

Who uses it

  • Product and support teams pull maxRating: 2 for the last 90 days of their own and competing apps and read the complaints, with appVersion on the newest ones.
  • ASO and growth compare ratingHistogram across storefronts to see where a 4.4 is "mostly 5s" and where it is "mostly 3s", and track the 12-month negative share in the summary row.
  • Research and VoC pipelines get stable reviewIds, ISO dates and one fixed schema across all 184 countries, ready for NLP without cleaning.
  • AI agents call it through MCP and read the field descriptions from the dataset schema, so a prompt like "summarize this month's complaints" needs no glue code.

Speed

Measured on the Apify platform on 2026-10-24: the complete history of a 2,800-review app in under 5 seconds, 15,000 reviews in 16 seconds. A 100,000-review history takes a couple of minutes. The optional developer-reply lookup is the slow part, roughly 1,000 reviews per 5 to 15 minutes per app and country, which is why it is off by default and billed separately.

Use it from Claude, Cursor, or any MCP client

This Actor is a tool on the hosted Apify MCP server. MCP (Model Context Protocol) is the standard that lets AI assistants call external tools. Add this URL as an MCP server in your client:

https://mcp.apify.com/?tools=actors,docs,kesar_chen/app-store-reviews-scraper

For Claude Desktop, put this in claude_desktop_config.json:

{
"mcpServers": {
"apify": {
"url": "https://mcp.apify.com/?tools=actors,docs,kesar_chen/app-store-reviews-scraper",
"headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }
}
}
}

For Claude Code, one command:

$claude mcp add --transport http apify "https://mcp.apify.com/?tools=actors,docs,kesar_chen/app-store-reviews-scraper" --header "Authorization: Bearer YOUR_APIFY_TOKEN"

For Cursor, the same url and headers block goes into .cursor/mcp.json under mcpServers. Any other MCP client that supports remote HTTP servers works the same way.

Then ask things like:

  • "Pull all 1- and 2-star reviews of app 284882215 in the US from the last 6 months and group the complaints."
  • "Compare the rating histograms of these three competitor apps in US, UK and Japan."
  • "Every morning, fetch new reviews for my competitors and tell me if anything mentions crashes."
  • "Get the 1-star reviews of app 1511601750 from the last 3 months with developer replies, and tell me which complaints the developer never answered."

Each review row and each summary row carries field descriptions in the dataset schema, so agents understand the data without guessing. Review text is user-generated content: tell your agent to treat body as data, never as instructions.

Run it in the Apify Console

  1. Open the Actor, paste an App Store URL, numeric app ID or bundle id into Apps, and pick the Countries you care about.
  2. Optionally set Only reviews since (12 months, 30 days, 2026-01-01) or Max reviews per app per country, and turn on Include developer responses if you need replies. Set a maximum cost per run under Run options if you want a hard budget.
  3. Click Start. When the run finishes, open the Storage tab: the Reviews view holds one row per review, the Summary view one row per app and country with totals and the histogram. Export as JSON, CSV or Excel, or read it through the API.

To run it on a schedule, save the input as a Task and attach a Schedule, see Scheduling and integrations below.

What you get that newest-500 tools do not

This ActorTypical newest-500 review scraper
Reviews per app per countryFull history (see limits below)500
Rating histogram (1–5 star counts)Yes, per countryNo
Total number of written reviewsYesNo
App version on reviewNewest ~500 per countryNewest ~500 per country
Sort by recent / helpful / favorable / criticalYesRecent and helpful only
Incremental runs (only new reviews)Yes, built inDedupe yourself
Last-N-months negative-review statsYes, in the summary rowNo
Developer repliesOptional, per reviewNo
Filter by rating, date range, keywords, has-replyYes, before billingRating only, if at all
All 184 storefronts in one runYes, countries: ["all"]Some
Login, cookies, API keysNoneNone

Pricing

Pay per event, no subscription, no start fee. You only pay for what the run produces.

EventPrice
Review scraped$0.50 per 1,000 reviews
App summary (one per app per country, with histogram)$0.002
Developer response checked (optional, per review looked up)$0.50 per 1,000 reviews

Turning on developer responses doubles the per-review price, because every review has to be looked up separately and that lookup is slow. You pay per review checked, whether or not it has a reply, so narrow the scope with sinceDate, maxReviewsPerApp or incremental mode.

Apify platform usage is included in those prices. What typical jobs cost:

ScenarioReviewsCost
Try it: one app, one country, newest 500500about $0.25
One competitor, full US history30,000about $15
10 competitors × 3 countries, daily incremental, ~20 new reviews per app per day~600 a dayabout $3 a month
Same, with developer replies checked~600 a dayabout $6 a month

The monthly platform credit included in Apify's free plan covers several thousand reviews here, enough to validate the output on your own apps before paying anything. Paid Apify plans get a lower per-review price, shown on the Actor page.

Set a maximum budget per run in the Apify Console. When it is reached the run stops cleanly: reviews already pushed are kept and charged, nothing else is, and the summary row records how deep the run got. In incremental mode the saved state is only updated after an app × country completes, so a budget stop never loses data.

Input

FieldTypeDefaultNotes
appslistrequiredNumeric App ID (284882215), App Store URL, bundle id (com.facebook.Facebook), or search:keyword (see searchResultsPerKeyword)
countrieslist["us"]Two-letter store codes, or ["all"] for every storefront. Each country is a separate store with its own reviews
sinceDatetextempty2026-01-01 or relative like 12 months, 30 days. Applied with sort = recent
sortselectrecentrecent, helpful, favorable, critical
maxReviewsPerAppinteger3000Per app per country. 0 = everything Apple still serves
untilDatetextemptyUpper bound, same formats as sinceDate. Combine both for a window
minRating / maxRatinginteger1 / 5Keep only reviews in this star range. maxRating: 2 for complaints only
keywordslist[]Keep only reviews whose title or text contains one of these words
developerResponseFilterselectallwith or without a developer reply. Needs includeDeveloperResponses
searchResultsPerKeywordinteger1How many apps each search:keyword expands to
summaryWindowMonthsinteger12Summary row counts 1–2 star reviews in this window. 0 turns it off
incrementalbooleanfalseRemember seen reviews per app × country and output only new ones on later runs
stateStoreNametextappstore-reviews-stateNamed key-value store for incremental state. Named stores never expire. If a store with this name was created by another Actor, the run logs a warning and uses <name>-<actor id> instead
includeDeveloperResponsesbooleanfalseLook up the developer reply for every review of the run. Slow and charged per review checked, see Pricing
maxDeveloperResponseChecksinteger1000Per app per country, caps how deep the reply lookup goes. 0 = no cap
notifyWebhookUrltextemptySlack, Discord, Feishu or any HTTPS webhook; see Alerts
notifyMaxRatinginteger2Only reviews at or below this rating are posted
notifyMaxItemsinteger10Reviews per message; the rest are counted
notifyEvenIfEmptybooleanfalsePost a message even when nothing matched
includeReviewerNamebooleanfalseOff by default so your dataset carries no personal data
pageSizeinteger3000Advanced tuning. Leave the default unless support asks you to change it

Minimal example:

{
"apps": ["284882215", "https://apps.apple.com/us/app/instagram/id389801252"],
"countries": ["us", "gb"],
"sinceDate": "12 months",
"maxReviewsPerApp": 0
}

Daily competitor monitor (save as a Task, attach a Schedule):

{
"apps": ["6504559449", "6475700817", "6451083449"],
"countries": ["us", "gb", "jp", "de"],
"sort": "recent",
"maxReviewsPerApp": 0,
"incremental": true,
"stateStoreName": "my-competitors"
}

Output

One row per review plus one summary row per app × country. Both go to the default dataset; use the type field or the two dataset views (Reviews, Summary) to tell them apart.

Review row:

{
"type": "review",
"reviewId": "14649743823",
"appId": "284882215",
"bundleId": "com.facebook.Facebook",
"appName": "Facebook",
"country": "us",
"storefrontId": 143441,
"rating": 2,
"title": "Crashes since last update",
"body": "The app closes every time I open messages.",
"appVersion": "512.0",
"createdAt": "2026-10-10T13:18:51Z",
"isEdited": false,
"voteCount": 3,
"voteSum": 2,
"reviewerName": null,
"reviewerUrl": null,
"developerResponse": null,
"developerResponseChecked": false,
"source": "full",
"isNew": true,
"scrapedAt": "2026-10-24T04:41:52Z"
}

With includeDeveloperResponses on, a review that the developer answered looks like this (other fields unchanged):

{
"type": "review",
"reviewId": "14698116552",
"rating": 5,
"title": "Finally a password manager I trust",
"developerResponse": {
"body": "Thanks for the 5-star review! We're glad 1Password gives you confidence in keeping your information secure.",
"respondedAt": "2026-10-14T00:56:39Z",
"responseId": "67246055"
},
"developerResponseChecked": true
}

developerResponseChecked: true with developerResponse: null means the review was looked up and has no reply. developerResponseChecked: false means it was not looked up, because the option was off, the budget ran out, or the reply source did not list that review.

Summary row:

{
"type": "summary",
"appId": "6504559449",
"appName": "PicTrace: AR Drawing & Sketch",
"sellerName": "Example Studio",
"currentVersion": "3.4.1",
"country": "us",
"totalReviews": 1039,
"ratingCount": 59712,
"ratingHistogram": [4912, 1602, 3105, 7891, 42202],
"ratingAverage": 4.4,
"reviewsScraped": 1039,
"coverageDepth": 1039,
"coverageNote": "500 reviews tagged with app version (version info exists for the newest ~500 reviews only)",
"holes": [],
"window": {
"months": 12,
"from": "2025-10-24T00:00:00Z",
"reviews": 719,
"negative": 393,
"negativeShare": 54.7,
"note": "sample 719"
},
"developerResponsesChecked": null,
"developerResponsesFound": null,
"source": "full",
"scrapedAt": "2026-10-24T04:41:52Z"
}

negativeShare is only given when the window has at least 20 reviews. Below that you get the counts and a note, because a percentage of 7 reviews is noise. ratingCount is star ratings, totalReviews is written reviews; the first is usually 20 to 50 times larger than the second.

How deep does it go?

For most apps: to the first review ever written in that country. For the very largest apps (hundreds of thousands of reviews in one country) Apple stops serving the oldest part of the history; for the biggest US apps the reachable depth is a few hundred thousand reviews. The summary row's coverageDepth tells you exactly how far the run got, and holes lists any ranges Apple would not return. Treat coverageDepth as the truth, not totalReviews.

If Apple changes something on its side, the Actor detects it at the start of the run and degrades automatically instead of failing. Every row carries a source field that tells you the coverage level: full is normal; secondary means the full history was still collected but more slowly and without vote counts; limited means only the newest 500 reviews could be collected. The summary row says the same. Nothing breaks downstream; you just get less speed or less depth until things return to normal.

Limitations

  • appVersion is only known for the newest ~500 reviews per country. Older reviews have appVersion: null.
  • Developer-response lookup is slow by nature, roughly 1,000 reviews per 5 to 15 minutes per app and country. developerResponseChecked: false means the review was not looked up, not that it has no reply. For very large apps replies cannot be looked up beyond the newest several tens of thousands of reviews.
  • sinceDate and incremental mode rely on sort = recent. With other sorts they are ignored and a warning is logged.
  • Written reviews are far fewer than star ratings. An app with 60,000 ratings may have 1,000 written reviews. That is Apple's data, not a scraping gap.
  • All 184 App Store storefronts are supported. For nine tiny storefronts (ad, bd, cf, et, gn, li, mc, ps, ws) Apple only exposes the newest 500 reviews, so rows there have source: "limited" and storefrontId: null.
  • Reviews are per country store. A review written in the UK store never appears in the US store.

Alerts: post new low-rated reviews to Slack, Discord or Feishu

Fill notifyWebhookUrl with a Slack incoming webhook, a Discord channel webhook, a Feishu/Lark custom bot webhook, or any HTTPS endpoint, turn on incremental, and attach a Schedule. After each app × country the Actor posts the reviews written in that run that are at or below notifyMaxRating (default 2), with the star rating, date, version where known, whether the developer already replied, and a link to the store page. Nothing is posted when nothing matched, unless notifyEvenIfEmpty is on. Unknown hosts receive a JSON body with the same fields, so Make, n8n, Zapier or your own service can take it from there. There is no extra charge for notifications.

An n8n template that does the same with room for translation or routing is in the templates folder.

Scheduling and integrations

Save your input as a Task, attach a Schedule (0 7 * * * for daily at 07:00 in your timezone), and turn on incremental. Each run then returns only new reviews, which you can push to Slack, Google Sheets, Make, Zapier, n8n or any webhook through Apify integrations. Watch ACTOR.RUN.FAILED to get alerted if something goes wrong.

API example:

curl -X POST "https://api.apify.com/v2/acts/kesar_chen~app-store-reviews-scraper/runs?token=YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"apps":["284882215"],"countries":["us"],"sinceDate":"30 days","maxReviewsPerApp":0}'

Python:

from apify_client import ApifyClient
client = ApifyClient("YOUR_TOKEN")
run = client.actor("kesar_chen/app-store-reviews-scraper").call(run_input={
"apps": ["284882215"], "countries": ["us"], "sinceDate": "30 days", "maxReviewsPerApp": 0,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
if item["type"] == "review" and item["rating"] <= 2:
print(item["createdAt"], item["title"])

FAQ

Why do other tools stop at 500 reviews per country? They rely on a public feed that stops at 500 per country. This Actor uses public, login-free store data that goes much deeper, and adds the app version wherever Apple exposes it, which is the newest ~500 reviews per country.

How long does a run take? Seconds for most apps: a complete 2,800-review history in under 5 seconds, 15,000 reviews in 16 seconds, a 100,000-review history in a couple of minutes (measured on the Apify platform, 2026-10-24). Developer-reply lookup is the exception, see Speed above.

Is there a free way to test it? Yes. Run it on your own app with maxReviewsPerApp: 500: that is about $0.25, well inside the monthly credit of Apify's free plan. The summary row tells you how many written reviews exist, so you know what a full pull would cost before you run it.

Is it legal to scrape App Store reviews? Collecting publicly available data is generally considered legal in the US and most of the EU. This Actor accesses only public store data, uses no login, account, cookie or API key, and bypasses no access control. Review text and reviewer names are user-generated content and may be personal data under GDPR or CCPA; reviewer names are therefore off by default. How you store and use the output, and whether it fits the terms of the services involved, is your responsibility. Apify's overview: Is web scraping legal?

How is this different from the App Store Connect API? App Store Connect only returns reviews for apps you own. This Actor works for any app, which is what you need for competitor analysis.

Why is the developer-response option slow, and why do I pay per review checked? Replies are not delivered together with the reviews; each review has to be looked up separately, and Apple only allows that at a slow pace. Every review of the run is looked up, and most reviews have no reply, so charging per reply found would make the price unpredictable for you and the work unpaid for us. Charging per review checked keeps it simple: turn it on, pay double per review, and narrow the scope with sinceDate, maxReviewsPerApp or incremental mode.

Do filtered-out reviews cost anything? No. Rating, date, keyword and reply filters run before rows are written, and you are charged per row written. The only exception is developerResponseFilter: to know whether a review has a reply it has to be looked up first, so every review still counts as one check. Filters do not change the summary row's window statistics, which always describe all reviews read in the window, otherwise maxRating: 2 would report a 100% negative share.

What happens when my budget runs out mid-run? The run stops cleanly. Reviews already saved are kept, the summary row records the depth reached, and in incremental mode the state is only written for app × country pairs that finished. Raise the budget and run again.

Can I get all countries at once? List the storefront codes you want in countries. Apps with zero ratings in a country are skipped automatically, so listing many countries is cheap.

Why is negativeShare null? The window had fewer than 20 reviews. You still get the counts.

Can AI agents use this? Yes, through the Apify MCP server (top of this page). Field descriptions are in the dataset schema so agents can read the output without guesswork.

Is this affiliated with Apple? No. This is an independent tool and is not endorsed by or affiliated with Apple Inc.

See also

Google Play Reviews Scraper and Steam Reviews Scraper: the same schema, filters and incremental mode for Android apps and Steam games, so one pipeline can watch all three stores.

Hiring Signals Monitor: job opened / closed events and a hiring summary per company from public job boards, for watching competitors' teams the same way you watch their reviews.

Support

Bugs, missing fields, or a storefront you need: open an issue on the Issues tab. Reports go to a human.