App Store Reviews Scraper | Full History, Dev Replies, $0.50/1K
Pricing
from $0.30 / 1,000 review scrapeds
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
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
1
Monthly active users
10 days ago
Last modified
Categories
Security
Limited permissionsShare
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, andmaxRating: 2withkeywords: ["crash"]returns only the rows you will read. - Only new reviews on every scheduled run. Turn on
incrementaland 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
coverageDepthandholes, every review row carries asourcelevel, 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: 2for the last 90 days of their own and competing apps and read the complaints, withappVersionon the newest ones. - ASO and growth compare
ratingHistogramacross 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
- Open the Actor, paste an App Store URL, numeric app ID or bundle id into Apps, and pick the Countries you care about.
- 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. - 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 Actor | Typical newest-500 review scraper | |
|---|---|---|
| Reviews per app per country | Full history (see limits below) | 500 |
| Rating histogram (1–5 star counts) | Yes, per country | No |
| Total number of written reviews | Yes | No |
| App version on review | Newest ~500 per country | Newest ~500 per country |
| Sort by recent / helpful / favorable / critical | Yes | Recent and helpful only |
| Incremental runs (only new reviews) | Yes, built in | Dedupe yourself |
| Last-N-months negative-review stats | Yes, in the summary row | No |
| Developer replies | Optional, per review | No |
| Filter by rating, date range, keywords, has-reply | Yes, before billing | Rating only, if at all |
| All 184 storefronts in one run | Yes, countries: ["all"] | Some |
| Login, cookies, API keys | None | None |
Pricing
Pay per event, no subscription, no start fee. You only pay for what the run produces.
| Event | Price |
|---|---|
| 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:
| Scenario | Reviews | Cost |
|---|---|---|
| Try it: one app, one country, newest 500 | 500 | about $0.25 |
| One competitor, full US history | 30,000 | about $15 |
| 10 competitors × 3 countries, daily incremental, ~20 new reviews per app per day | ~600 a day | about $3 a month |
| Same, with developer replies checked | ~600 a day | about $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
| Field | Type | Default | Notes |
|---|---|---|---|
apps | list | required | Numeric App ID (284882215), App Store URL, bundle id (com.facebook.Facebook), or search:keyword (see searchResultsPerKeyword) |
countries | list | ["us"] | Two-letter store codes, or ["all"] for every storefront. Each country is a separate store with its own reviews |
sinceDate | text | empty | 2026-01-01 or relative like 12 months, 30 days. Applied with sort = recent |
sort | select | recent | recent, helpful, favorable, critical |
maxReviewsPerApp | integer | 3000 | Per app per country. 0 = everything Apple still serves |
untilDate | text | empty | Upper bound, same formats as sinceDate. Combine both for a window |
minRating / maxRating | integer | 1 / 5 | Keep only reviews in this star range. maxRating: 2 for complaints only |
keywords | list | [] | Keep only reviews whose title or text contains one of these words |
developerResponseFilter | select | all | with or without a developer reply. Needs includeDeveloperResponses |
searchResultsPerKeyword | integer | 1 | How many apps each search:keyword expands to |
summaryWindowMonths | integer | 12 | Summary row counts 1–2 star reviews in this window. 0 turns it off |
incremental | boolean | false | Remember seen reviews per app × country and output only new ones on later runs |
stateStoreName | text | appstore-reviews-state | Named 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 |
includeDeveloperResponses | boolean | false | Look up the developer reply for every review of the run. Slow and charged per review checked, see Pricing |
maxDeveloperResponseChecks | integer | 1000 | Per app per country, caps how deep the reply lookup goes. 0 = no cap |
notifyWebhookUrl | text | empty | Slack, Discord, Feishu or any HTTPS webhook; see Alerts |
notifyMaxRating | integer | 2 | Only reviews at or below this rating are posted |
notifyMaxItems | integer | 10 | Reviews per message; the rest are counted |
notifyEvenIfEmpty | boolean | false | Post a message even when nothing matched |
includeReviewerName | boolean | false | Off by default so your dataset carries no personal data |
pageSize | integer | 3000 | Advanced 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
appVersionis only known for the newest ~500 reviews per country. Older reviews haveappVersion: null.- Developer-response lookup is slow by nature, roughly 1,000 reviews per 5 to 15 minutes per app and country.
developerResponseChecked: falsemeans 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. sinceDateand incremental mode rely onsort = 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"andstorefrontId: 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 ApifyClientclient = 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.