App Store & Google Play Reviews Scraper | $0.01/App, No Cap
Pricing
from $6.00 / 1,000 apps
App Store & Google Play Reviews Scraper | $0.01/App, No Cap
Reviews, metadata and keyword rankings for iOS and Android apps, in one common schema, across every storefront. Full review sweep that does not lose reviews on the way. No login, no cookies.
Pricing
from $6.00 / 1,000 apps
Rating
0.0
(0)
Developer
Alexis Morain
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
1
Monthly active users
10 days ago
Last modified
Categories
Share
App Store & Google Play Reviews Scraper
You pay per app, not per review. One app swept on one storefront costs $0.01 on the Free plan and $0.006 on Gold, whatever the number of reviews. Here is what that means next to a scraper metered at $0.10 per 1,000 reviews, the usual rate on this market:
| Job | Billed here | Here, Free plan | Here, Gold | At $0.10 / 1K reviews |
|---|---|---|---|---|
| Spotify, US App Store, 5,000 reviews | 1 app & storefront | $0.01 | $0.006 | $0.50 |
| Spotify, Google Play US, 20,000 reviews | 1 app & storefront | $0.01 | $0.006 | $2.00 |
| 10 competitor apps, 1 storefront, 2,000 reviews each | 10 apps & storefronts | $0.10 | $0.06 | $2.00 |
Review counts are illustrative, not measurements: what you actually get depends on the app, the storefront and the maxReviewsPerApp you set. On top of that, each run pays a $0.02 start fee at the default 1 GB of memory, once, whatever it collects. The Pricing section has the full arithmetic with it included.
Why the Store card says "per 1,000". Apify prints every event price per 1,000 events, so the card reads "from $6.00 / 1,000 apps": that is $0.006 for one app on one storefront on Gold, $0.01 on the Free plan, with all its reviews included.
An app store reviews scraper and a google play reviews scraper in one Actor. Give it an iOS app id, an Android package name or a store URL, and it returns every review the store serves, plus the app's store listing, plus its rank for the search terms you track on the App Store and on Google Play. iOS and Android come out in the same schema, with the same field names and the same date format.
Pricing is $0.01 per app on one storefront on the Free plan, down to $0.006 on Gold, reviews included, however many there are. A 5,000-review app costs three cents to sweep with the run start included, not fifty. Reviews are not billed per unit here, so depth is free.
No login, no cookies, no browser. Both stores are read over plain HTTP through endpoints that need no token.
Why most iOS review scrapers quietly return nothing
Apple publishes a public RSS review feed. It is the obvious source, it is what most iOS scrapers are built on, and it does not work. It caps at ten pages, it caps at roughly 500 reviews, and worse, it serves empty pages scattered between full ones. A scraper that paginates the sane way, stopping when a page comes back empty, returns nothing at all on real apps. Page one is often the empty one.
That failure is silent. The run succeeds, the dataset is empty or short, and nothing in the log says the feed lied. You find out when the analysis you built on it says an app with forty million ratings has a hundred reviews.
Measured on 19 August 2026, from a bare IP, no proxy:
| App | Storefront | Public RSS feed | This scraper |
|---|---|---|---|
| Spotify | US | 0 reviews across all 10 pages | 1,200+, still paginating at offset 5,000 |
| US | 100, or 0 if it stops on the first empty page | 600+ | |
| Doctolib | FR | 150 | deep pagination confirmed |
This Actor does not read the RSS feed. It reads the catalog API that apps.apple.com itself calls, through Apple's own first-party proxy, which injects the bearer token server-side so the request goes through unauthenticated. Twenty reviews per page, offset pagination, depth measured past 5,000 on a single storefront. It never stops on an apparently empty page: it advances the offset as long as Apple returns rows and a next pointer, so a hollow page in the middle costs nothing.
Google Play gets the same discipline. The Actor calls the internal batchexecute RPC the Play web UI uses, 100 reviews per page, and follows the continuation token to the end instead of stopping at a round number. Measured throughput: 8,000 reviews in 76 seconds from a bare datacenter IP, no rate limiting, continuation token still alive at the end.
There are two ways to lose reviews on the way. Stopping too early, and being throttled into stopping. This Actor handles both: exhaustive pagination on one side, exponential backoff on Apple's per-IP 429 on the other.
What unlimited reviews per app actually changes
The going rate on this market is per review. It sounds cheap until you sweep something real. An app with 5,000 reviews on one storefront costs $0.50 at $0.10 per 1,000 reviews. Five competitors across three countries at that depth is $7.50 before you have looked at the data.
Here the billable unit is the app-storefront pair, at $0.01 on the Free plan and $0.006 on Gold, and the reviews inside it are free. The same 5,000-review app costs $0.01 plus the $0.02 run start. The deeper you go, the further the two models diverge. See the scenario table under Pricing for the arithmetic per 1,000 reviews.
The practical consequence: you stop rationing depth. Raise maxReviewsPerApp to whatever the analysis needs, add storefronts, add competitors. The bill moves with the number of apps you look at, not with how much they have to say.
How to scrape the App Store and Google Play
From the Apify Console
- Open the Input tab. Two apps are already prefilled, one from each store, so you can press Start immediately and see the output shape before committing to anything.
- Replace them with your own. Paste the store URL straight from your browser, or use the App Store numeric id (
310633997) or the Google Play package name (com.whatsapp). Mixed lists are fine, the Actor sorts out which store each entry belongs to. - Set the Storefronts. Both stores are partitioned by country: a US review never appears in the French store. Start with
us, add countries once you have seen the data. - Leave Reviews and App metadata both ticked. They are covered by the same billable unit, so taking both costs exactly what taking one costs.
- Raise Max reviews per app and storefront if you want the full backlog. The default is 500, which is a sample, not a sweep. The price does not change with this number.
- Press Start, then export the dataset as JSON, CSV, Excel or XML from the Storage tab.
From the App Store scraper API
Every run is also an API call. Get your token from Settings → Integrations in the Console, then use one of the snippets in Code examples below. The synchronous endpoint blocks until the run finishes and returns the dataset in the response body, which is the shortest path from zero to rows.
Which fields should I fill?
| If you want to... | Fill in | Leave alone |
|---|---|---|
| Read what users say about one app | apps, one entry in countries | everything else |
| Compare an app across markets | apps, several countries | everything else |
| Get the full review backlog, not a sample | maxReviewsPerApp at a high value | everything else |
| Monitor new feedback daily | mode: new-only, plus a schedule | everything else |
| Get called when new reviews arrive | mode: new-only, webhookUrl, plus a schedule | everything else |
| Track App Store search positions | add rankings to include, fill keywords | apps can stay empty |
| Track Google Play search positions too | same, plus rankingStores: ["apple", "google"] | apps can stay empty |
| Run a competitor app analysis | every competitor in one apps list | everything else |
| Cut a run short for a quick look | maxReviewsPerApp at 100 | everything else |
Only apps really matters, and even that can be empty if you run keyword rankings alone. Everything else has a working default.
Input reference
| Field | Type | What it does | Example |
|---|---|---|---|
apps | array of strings | The apps to collect. Accepts an App Store numeric id, a Google Play package name, a full App Store URL or a full Google Play URL, freely mixed. Unreadable entries are skipped with an explanation in the log and the rest of the list still runs. | ["310633997", "com.spotify.music"] |
countries | array of strings | ISO 3166-1 codes of the storefronts to sweep. Case-insensitive. Every app is swept on every storefront listed. Empty means us. | ["us", "fr", "gb"] |
include | array of strings | Any combination of reviews, metadata and rankings. Reviews and metadata share one billable unit. Rankings are billed separately, per keyword, storefront and store. | ["reviews", "metadata"] |
mode | string | all returns everything the store serves on every run. new-only keeps a persistent registry per app and storefront and returns only reviews it has never seen. | "all" |
webhookUrl | string | Optional. The Actor POSTs a JSON summary here at the end of the run. In new-only mode, only when at least one new review came in. Stored encrypted, never written to the log. | "https://hooks.slack.com/services/..." |
maxReviewsPerApp | integer | Upper bound on reviews per app and per storefront. Does not affect the price. Default 500. | 5000 |
keywords | array of strings | Search terms to track. Used only when rankings is in include. Works with an empty apps list. | ["music streaming"] |
rankingDepth | integer | How many ranked apps to record per keyword, storefront and store, 1 to 200. Default 50. Google Play serves about 30 results per search, so Play rows stop there. Depth does not affect the price. | 50 |
rankingStores | array of strings | Which stores to search for each keyword: apple, google or both. Default ["apple"], the behavior of earlier versions. One keyword ranking unit per keyword, storefront and store. | ["apple", "google"] |
proxyConfiguration | object | Off by default and it should stay off. Both stores are read over plain HTTP and need no proxy in normal use. Turn it on only if you sweep hundreds of app-storefront pairs in one run and see rate limits in the log. | {"useApifyProxy": false} |
A full input:
{"apps": ["310633997","com.spotify.music","https://apps.apple.com/fr/app/id925339063"],"countries": ["us", "fr"],"include": ["reviews", "metadata", "rankings"],"mode": "all","maxReviewsPerApp": 5000,"keywords": ["music streaming", "meditation"],"rankingDepth": 50,"rankingStores": ["apple", "google"],"proxyConfiguration": { "useApifyProxy": false }}
Google Play keyword rankings
Rankings now cover both stores. Add google to rankingStores and every keyword is searched on Google Play as well as on the App Store, storefront by storefront, with the same row format: store tells you which search the position comes from.
{"include": ["rankings"],"keywords": ["music streaming", "meditation"],"countries": ["us", "fr"],"rankingStores": ["apple", "google"],"rankingDepth": 30}
That run makes 2 keywords x 2 storefronts x 2 stores = 8 keyword ranking units: $0.016 on the Free plan, under a cent on Gold, plus the run start. Set rankingStores to ["google"] to search Google Play alone.
What to know before you rely on it:
- Google Play serves about 30 results per search, against up to 200 on the App Store. With
rankingDepthabove 30, Play rows simply stop where Google stops. An app beyond that is not in the output for that keyword: absent means "not in the top 30". - On a brand query (
spotify), Google shows a featured card first. It counts as position 1, the list follows from 2. rating_countis filled on the featured card only. The result list carries the rating, not the count.- The search is the public web page, logged out, requested in English (
hl=en) with the storefront as country (gl). The personalized Android app can order results slightly differently.
Your existing runs do not change. When rankingStores is absent, as in every API call and saved task written before this field existed, only the App Store is searched and the bill is the same as before.
Webhook: get called when new reviews arrive
Set webhookUrl and the Actor POSTs a small JSON summary to it at the end of the run. Combined with mode: new-only and a daily schedule, it is a push notification: nothing happens on quiet days, and your endpoint gets a call the day users post something.
{"apps": ["310633997", "com.spotify.music"],"countries": ["us"],"mode": "new-only","webhookUrl": "https://hooks.example.com/your-endpoint"}
The body your endpoint receives:
{"actor": "app-store-play-store-scraper","runId": "aBcD1234eFgH5678","datasetId": "iJkL9012mNoP3456","mode": "new-only","reviews": 14,"reviewsByApp": [{ "store": "apple", "app_id": "310633997", "country": "us", "reviews": 9 },{ "store": "google", "app_id": "com.spotify.music", "country": "us", "reviews": 5 }],"appProfiles": 2,"rankingRows": 0,"failedPairs": 0,"stoppedAtChargeLimit": false}
Pull the rows themselves from the dataset with datasetId. The rules:
- In
new-onlymode, the call happens only when at least one new review came in. Inallmode, it happens whenever the run returned data. - Any 2xx answer counts as delivered, whatever the body, so Slack's
okand Discord's empty 204 both work. - Redirects: a 307 or 308 is followed once, with the same POST. A 301, 302 or 303 is not followed, since following it would turn the POST into a GET and lose the body. The log says so and asks you to paste the final URL.
- A webhook that fails, times out or answers an error is logged as a warning. The run itself still succeeds and its data stays in the dataset.
- The field is stored encrypted, like a password, because a webhook URL often carries its own access token. The URL never appears in the log, the status message or the dataset.
Output fields
All records land in one dataset. Every record carries a type field, so you can split them with a single filter: review, metadata or ranking. The dataset also ships three ready-made views, one per type, selectable in the Storage tab and through the API with ?view=reviews, ?view=metadata or ?view=rankings.
Review fields (iOS and Android app reviews, one schema)
| Field | Type | Description | Example |
|---|---|---|---|
type | string | Always review for these rows. | "review" |
store | string | apple or google. | "apple" |
app_id | string | App Store numeric id, or Android package name. Always a string, both stores. | "310633997" |
country | string | Storefront this review was collected from, lowercase. | "us" |
review_id | string | Store-side review id. Stable, and what the delta mode deduplicates on. | "13657831289" |
rating | integer | 1 to 5. Clamped to that range on both stores. | 5 |
title | string or null | Review title. App Store only, Google Play does not have review titles, so it is null there. | "Potential improvements" |
body | string or null | Review text, as posted, in the storefront's language. | "I love the app and it's my main..." |
author | string or null | Display name of the reviewer. | "Ed Bradway" |
version | string or null | App version the review was left on. Google Play only, null on App Store rows. | "9.1.64.1676" |
posted_at | string or null | ISO 8601 UTC, second precision, Z suffix. Normalized on both stores, never an epoch, never a localized string. | "2026-01-21T04:48:38Z" |
is_edited | boolean or null | Whether the review was edited after posting. App Store only, null on Google Play rows. | false |
reply_body | string or null | The developer's public reply. Google Play only. | "Thanks for the feedback..." |
reply_at | string or null | When the developer replied, same ISO 8601 UTC format. Google Play only. | "2026-08-02T09:12:00Z" |
Four fields are store-specific and the table says which: title and is_edited come from Apple, version and the reply pair come from Google. They are always present as keys, set to null on the other store, so a CSV export has stable columns and a dataframe has stable dtypes.
App metadata fields (app metadata API for both stores)
| Field | Type | Description | Example |
|---|---|---|---|
type | string | Always metadata for these rows. | "metadata" |
store | string | apple or google. | "google" |
app_id | string | Numeric id or package name. | "com.spotify.music" |
country | string | Storefront the listing was read from. | "us" |
name | string or null | App name as shown on that storefront. | "Spotify: Music and Podcasts" |
developer | string or null | Publisher name. | "Spotify AB" |
rating_average | number or null | Average star rating, unrounded. | 4.3448367 |
rating_count | integer or null | Number of ratings. | 36244267 |
review_count | integer or null | Number of written reviews. On the App Store this equals rating_count, Apple does not split the two. | 1850955 |
version | string or null | Current version on that storefront. Reliable on the App Store, sometimes null on Google Play. | "26.34.74" |
price | number or null | Listing price in the storefront currency. 0.0 for a free app. | 0.0 |
currency | string or null | Currency of price, as the storefront shows it (USD, EUR, GBP...), on both stores. | "USD" |
genre | string or null | Primary category. | "Music & Audio" |
installs | string or null | Install bucket as Google publishes it. Google Play only, null on App Store rows. | "1,000,000,000+" |
description | string or null | Full store description text. | "With the Spotify music and podcast app..." |
icon_url | string or null | Direct URL to the app icon. 512px on the App Store. | "https://play-lh.googleusercontent.com/..." |
url | string or null | Canonical store listing URL. | "https://play.google.com/store/apps/details?id=com.spotify.music" |
Keyword ranking fields (app store keyword ranking)
One row per app per position, for each keyword, each storefront and each store in rankingStores.
| Field | Type | Description | Example |
|---|---|---|---|
type | string | Always ranking for these rows. | "ranking" |
store | string | apple or google, the store whose search the position comes from. | "apple" |
country | string | Storefront the search was run in. | "us" |
keyword | string | The search term, exactly as you submitted it. | "music streaming" |
position | integer | Rank in the result list, starting at 1. | 2 |
app_id | string | Numeric App Store id, or Google Play package name, of the ranked app. | "324684580" |
name | string or null | Name of the ranked app. | "Spotify: Music and Podcasts" |
developer | string or null | Its publisher. | "Spotify" |
rating_average | number or null | Its average rating on that storefront. | 4.77453 |
rating_count | integer or null | Its number of ratings on that storefront. On Google Play, filled on the featured top result only, null on the rest of the list. | 41912880 |
Example output
Real rows, collected on 5 September 2026, trimmed for length.
[{"type": "metadata","store": "apple","app_id": "310633997","country": "us","name": "WhatsApp Messenger","developer": "WhatsApp Inc.","rating_average": 4.68457,"rating_count": 18529394,"review_count": 18529394,"version": "26.34.74","price": 0.0,"currency": "USD","genre": "Social Networking","installs": null,"description": "WhatsApp from Meta is a free messaging and calling app used by over 2 billion people across 180+ countries...","icon_url": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/05/ee/c3/05eec37b/AppIcon.png/512x512bb.jpg","url": "https://apps.apple.com/us/app/whatsapp-messenger/id310633997?uo=4"},{"type": "review","store": "apple","app_id": "310633997","country": "us","review_id": "10031881920","rating": 4,"title": "Potential improvements","body": "I love the app and it's my main communication method. I just have some ideas that would make the app experience better. 1. It would be useful to have the possibility to filter the group chats and individual chats...","author": "Lol_hahahahahah","version": null,"posted_at": "2023-06-14T06:43:29Z","is_edited": false,"reply_body": null,"reply_at": null},{"type": "metadata","store": "google","app_id": "com.spotify.music","country": "us","name": "Spotify: Music and Podcasts","developer": "Spotify AB","rating_average": 4.3448367,"rating_count": 36244267,"review_count": 1850955,"version": null,"price": 0.0,"currency": "USD","genre": "Music & Audio","installs": "1,000,000,000+","description": "With the Spotify music and podcast app, you can play millions of songs, albums and original podcasts for free...","icon_url": "https://play-lh.googleusercontent.com/IzQgYCcnCFCD08GR-3bdtcT8xzOvrNkC84avGT5CwTX2VIqmTmKKJcP_Cd4JoBOdmCMlTndlOzV6hrthg2fOWA","url": "https://play.google.com/store/apps/details?id=com.spotify.music"},{"type": "review","store": "google","app_id": "com.spotify.music","country": "us","review_id": "5dc32489-93a6-440a-8d62-18a0b42106a4","rating": 5,"title": null,"body": "very good","author": "Ronik Ronik","version": "9.1.64.1676","posted_at": "2026-09-04T13:28:17Z","is_edited": null,"reply_body": null,"reply_at": null},{"type": "ranking","store": "apple","country": "us","keyword": "music streaming","position": 2,"app_id": "324684580","name": "Spotify: Music and Podcasts","developer": "Spotify","rating_average": 4.77453,"rating_count": 41912880}]
Code examples: Python, JavaScript and cURL
Python
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run_input = {"apps": ["310633997", "com.spotify.music"],"countries": ["us", "fr"],"include": ["reviews", "metadata"],"mode": "all","maxReviewsPerApp": 5000,}run = client.actor("alexmorain/app-store-play-store-scraper").call(run_input=run_input)reviews, listings = [], []for item in client.dataset(run["defaultDatasetId"]).iterate_items():if item["type"] == "review":reviews.append(item)elif item["type"] == "metadata":listings.append(item)print(f"{len(reviews)} reviews, {len(listings)} store listings")# Straight into pandas, both stores in one frame, no merging step.import pandas as pddf = pd.DataFrame(reviews)df["posted_at"] = pd.to_datetime(df["posted_at"])print(df.groupby(["store", "country"])["rating"].mean())
JavaScript
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('alexmorain/app-store-play-store-scraper').call({apps: ['310633997', 'com.spotify.music'],countries: ['us', 'fr'],include: ['reviews', 'metadata'],mode: 'all',maxReviewsPerApp: 5000,});const { items } = await client.dataset(run.defaultDatasetId).listItems();const reviews = items.filter((i) => i.type === 'review');const oneStar = reviews.filter((r) => r.rating === 1);console.log(`${reviews.length} reviews, ${oneStar.length} of them one-star`);
cURL
Run synchronously and get the dataset back in the same response:
curl -X POST "https://api.apify.com/v2/acts/alexmorain~app-store-play-store-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \-H 'Content-Type: application/json' \-d '{"apps": ["310633997", "com.spotify.music"],"countries": ["us"],"include": ["reviews", "metadata"],"maxReviewsPerApp": 1000}'
Ask for only the reviews, as CSV, using the dataset view:
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?view=reviews&format=csv&token=<YOUR_APIFY_TOKEN>" \-o reviews.csv
What people use it for
Watching your own reviews across every country store
Your app is live in twenty markets and the store console shows you one country at a time. Put your app id in apps, list every storefront you ship to in countries, and one run gives you the whole picture in one table with a country column. Twenty storefronts is twenty billable units: twenty cents on the Free plan, twelve on Gold, plus the two-cent run start, whatever the review volume behind them. Schedule it daily in new-only mode and it becomes a feed of what changed rather than a re-download of the same corpus.
Competitor app analysis
Put five or ten competitors in apps in one go, keep metadata ticked, and you get their ratings, rating counts, install buckets, current versions and full descriptions alongside every review their users left. The reviews are where the useful part is: the complaints that repeat across three competitors are the feature gap, and the compliments that repeat are what you are up against. Because reviews are not metered, going ten thousand deep on a competitor costs the same single unit as going a hundred deep, which is what makes this kind of sweep practical at all.
ASO keyword tracking and app store optimization data
Tick rankings, put your target search terms in keywords, and each run records the ranked list of apps for each term on each storefront, position by position, up to 200 deep on the App Store and about 30 on Google Play. Ten keywords on one storefront and one store cost two cents on the Free plan, just over one cent on Gold; add Google Play and it doubles. Run it on a schedule and the dataset becomes a position history: you see the day you moved from 14 to 6 on your main term, and you see the competitor who took the slot above you. This is the segment nobody serves well on this market, and it is the same run as your review collection, so the ASO data and the voice-of-customer data arrive together.
Catching a regression after a release
You ship a version, and the question is whether it broke something. Reviews carry the app version on Google Play, so filter version to the new build and look at the rating distribution against the previous one. On the App Store, filter on posted_at after the release date instead. A daily new-only run with a webhookUrl gives you this within hours of the release rather than at the end of the sprint, which is the difference between a hotfix and a bad month.
Feeding a sentiment or topic model
Review text is good training and evaluation material, and it is annoying to collect at volume. Here the bodies come out clean, in one schema, with a normalized UTC timestamp and an integer rating that works as a label out of the box. Because the price does not follow the row count, building a corpus of a hundred thousand reviews across twenty apps on one storefront costs twenty cents on the Free plan, twelve on Gold, plus the run start. The store field lets you check whether your model behaves the same on iOS and Android text, which it usually does not.
Market research on a category
Search a category term with rankings at depth 200, take the app ids that come back, feed them into apps on a second run, and you have the full picture of a category: who ranks, what their ratings are, what their users complain about. The rating_count and installs fields give you the rough size of each player. This is a two-run pipeline and it costs well under a dollar for a fifty-app category.
Why this scraper
One schema for iOS and Android app reviews. An Apple review and a Google review come out with the same field names, the same types and the same date format. Ratings are integers 1 to 5 on both. Timestamps are ISO 8601 UTC with a Z suffix on both, never an epoch on one side and a localized string on the other. Ids are strings on both, because Apple and Google do not type them the same way. You do not write a merge step.
A review sweep that does not lose reviews. Exhaustive pagination on both stores, no early stop on an empty page, no arbitrary depth ceiling other than the one you set. This is the whole reason the Actor exists, and the RSS comparison table at the top is the evidence.
Every storefront. The country code is a real parameter on all three surfaces: reviews, listings and rankings. French reviews for a French storefront, Japanese rankings for the Japanese store. Nothing is US-only.
No login, no cookies, no browser. Plain HTTP against endpoints that need no token, which is why there is no session to expire, no account to get banned and no headless browser inflating the runtime. It also keeps the platform cost near zero, which is what makes the flat per-app unit sustainable rather than a promotional trick.
It degrades in the open. An entry it cannot parse is skipped with an explanation in the log and the run continues. One app failing on one storefront does not stop the other pairs. If everything fails, the run ends as FAILED, not as a green run with an empty dataset. The log prints the billing plan before spending anything: how many app-storefront units and how many keyword units the run is about to charge.
You are not billed for empty pairs. The app-storefront unit is charged when the first row for that pair is actually pushed. An app that does not exist on a storefront returns nothing and costs nothing.
Pricing
Pay per event, three events, nothing else. Platform compute is included, so the events are the whole bill. Event prices drop with your Apify plan: the Free column is what you pay on the Free plan, Gold and above get the lowest price.
| Event | Free | Bronze | Silver | Gold and above | Charged |
|---|---|---|---|---|---|
| Actor start | $0.02 | $0.02 | $0.02 | $0.02 | Once per run, per GB of memory. The default is 1 GB, so $0.02 a run unless you raise the memory. |
| App on a storefront | $0.01 | $0.0087 | $0.0073 | $0.006 | Per app and per storefront that returns data. Covers the store listing and the complete review sweep, however many reviews that is. |
| Keyword ranking | $0.002 | $0.0017 | $0.0015 | $0.0012 | Per keyword, per storefront and per store searched. Covers up to 200 ranked positions on the App Store, about 30 on Google Play. |
What that works out to per 1,000 reviews
The right column is the same job at a per-review price of $0.10 per 1,000 reviews, which is roughly the going rate on this market. Costs here include the $0.02 run start at the default 1 GB. The per-1,000 column uses the Free plan price.
| Scenario | Reviews collected | Cost here, Free plan | Cost here, Gold | Per 1,000 reviews, Free plan | Same job, metered per review |
|---|---|---|---|---|---|
| 1 app, 1 storefront, small app | 500 | $0.03 | $0.026 | $0.060 | $0.05 |
| 1 app, 1 storefront, mid-size app | 5,000 | $0.03 | $0.026 | $0.006 | $0.50 |
| 1 app, 1 storefront, large app | 50,000 | $0.03 | $0.026 | $0.0006 | $5.00 |
| 1 app, 5 storefronts | 25,000 | $0.07 | $0.05 | $0.0028 | $2.50 |
| 10 competitors, 1 storefront | 20,000 | $0.12 | $0.08 | $0.006 | $2.00 |
| 10 competitors, 5 storefronts | 100,000 | $0.52 | $0.32 | $0.0052 | $10.00 |
| 20 keywords, 3 storefronts, App Store, no apps | 0 | $0.14 | $0.09 | n/a | n/a |
| 20 keywords, 3 storefronts, both stores, no apps | 0 | $0.26 | $0.16 | n/a | n/a |
On the Free plan, the crossover is around 300 reviews per app-storefront pair for a single-app run, and around 100 once the run start is spread over several apps. Below that, metering per review is cheaper and you should use whatever is cheapest. Above it, the flat unit wins, and it wins by more the deeper you go: at 50,000 reviews the ratio is roughly 165 to 1.
A run stops on its own when it reaches the maximum charge you set for it, and says so in the status message. It never silently spends past your cap.
Limits and what this scraper does not do
Read this before you buy. It is shorter than the feature list and more useful.
Google Play rankings stop at about 30 positions. That is what the Play search page serves. The App Store goes to 200. An app missing from the Play rows for a keyword is outside the top 30, not necessarily absent from the store.
Google Play rankings are requested in English, logged out. hl=en, with the storefront as country. The personalized Android app can order results slightly differently.
The default review cap is 500. maxReviewsPerApp defaults to 500, per app and per storefront. That is a sample. A full backlog needs you to raise it, and raising it does not cost more, but nobody gets the full sweep by accident.
Reviews are not filterable by date or rating at the source. Neither store's endpoint accepts those filters here. The Actor sweeps and you filter downstream, or you use mode: new-only to get only what is new since the last run.
Reviews come back newest first on both stores. With a maxReviews cap you get the latest reviews, not an arbitrary slice. Ties within the same day are in store order: if you need strict chronology, sort on posted_at yourself.
Google Play reviews are requested in English. The hl language parameter is fixed to en while the storefront gl follows your countries. You get that country's reviews, in the language they were written, but the request itself is not localized.
Some Google Play listing fields can come back null. The listing is parsed out of the page's embedded data blocks, and Google moves those paths around. The parser returns null for a field whose path moved instead of failing the run. version is the one that goes missing most often. Review collection is unaffected, it uses a different surface.
No in-app purchase list, no ratings histogram, no similar-apps graph, no changelog, no screenshots. The metadata row is the set of fields documented above and nothing more.
No Apple Search Ads placements. The ranking source is Apple's official search endpoint, which returns organic results only and can lag the live UI slightly. It is a good ASO tracker, not a paid-placement monitor.
The delta registry keeps the last 8,000 review ids per app and storefront. In new-only mode, an app that receives more than 8,000 reviews between two runs can re-serve older ones. Daily runs on any normal app are nowhere near that.
No developer replies on the App Store. Apple's review surface used here does not carry them. reply_body and reply_at are populated on Google Play only.
FAQ
How do I export App Store reviews to CSV?
Run the Actor, open the run's Storage tab, pick the Reviews view and export as CSV. From the API, call https://api.apify.com/v2/datasets/<DATASET_ID>/items?view=reviews&format=csv. The view keeps the review columns and drops the metadata and ranking rows, so the file opens cleanly in Excel or Sheets. JSON, XML and Excel are available at the same endpoint by changing format.
Can I get reviews from all countries?
Yes. List the ISO country codes in countries and every app is swept on every storefront you name. Each app-storefront pair is one billable unit, $0.01 on the Free plan and $0.006 on Gold, so twenty countries for one app is twenty cents (twelve on Gold) plus the run start, regardless of how many reviews come back.
How many reviews does the App Store API return?
The public RSS feed caps at about 500 and serves empty pages that make naive scrapers stop at zero. The catalog endpoint this Actor uses paginates 20 at a time and was still returning reviews at offset 5,000 on Spotify US during testing. In practice your ceiling is maxReviewsPerApp, not the store.
Can I scrape both iOS and Android apps in the same run?
Yes, that is the point of the common schema. Mix App Store ids, Google Play package names and store URLs in one apps list. The Actor works out which store each entry belongs to and both come back with identical field names.
Do I need a proxy or an Apify Proxy subscription? No. Both stores are read over plain HTTP with no token and no cookie, and a normal run needs no proxy at all. The proxy option exists for very large sweeps, hundreds of app-storefront pairs in a single run, where Apple's per-IP rate limit starts to bite. Leave it off otherwise, it only slows the run down.
How do I track my app store keyword ranking over time?
Add rankings to include, put your terms in keywords, set a rankingDepth, pick the stores in rankingStores (App Store, Google Play or both), and schedule the Actor daily or weekly. Each run appends ranking rows with keyword, country, position and the app at that position. Point every scheduled run at the same dataset, or join the runs afterwards on keyword and run date, and you have a position history.
Can I get only new reviews instead of the whole backlog every time?
Set mode to new-only. The Actor keeps a persistent registry of review ids per app and storefront and returns only what it has not seen. The first run in this mode still returns everything, since nothing has been seen yet. Combine it with a schedule and a webhookUrl for a live feed of new feedback: the webhook is called only on runs that found something new.
Does the price go up if an app has a million reviews?
No. The unit is the app on a storefront, $0.01 on the Free plan and $0.006 on Gold, and the reviews inside it are not metered. What a large app costs you is run time, not money, which is what maxReviewsPerApp is for.
Can I get the app description, icon and install count?
Yes, they are in the metadata rows: description, icon_url and, on Google Play, installs. Keep metadata in include. It shares the billable unit with reviews, so it is free if you are already collecting reviews.
What happens if one app in my list is wrong? It is skipped, the log says why, and the rest of the list runs. The same applies to an app that does not exist on one of your storefronts: that pair returns nothing, is not charged, and the other pairs continue. A run only ends as FAILED when every single pair failed.
Can I use this scraper as an app metadata API?
Yes. Set include to ["metadata"], list your apps and storefronts, and call the synchronous endpoint run-sync-get-dataset-items. You get the listings back in the response body. It is one billable unit per app and storefront, same as a full sweep.
Is scraping app store reviews legal? The Actor reads public store pages and public endpoints, the same data any visitor sees, and collects no personal data beyond the public display names attached to public reviews. What you may do with that data depends on your jurisdiction and your purpose. If you are processing it at scale in the EU or handling it as personal data, talk to your own counsel first.
Where do I report a problem? The Issues tab on the Actor page. Google occasionally rotates the internal RPC id behind the Play reviews endpoint. When that happens the Actor raises an explicit error saying so, rather than pretending the app has no reviews. Report it and it gets fixed.
Integrations
Webhooks. Set webhookUrl and the Actor calls your endpoint itself at the end of the run, with the dataset id and the count of new reviews per app (see Webhook). Combined with mode: new-only and a daily schedule, this is a review feed: your service gets called only when there is something new, and only the new reviews are in the dataset. Apify's own run webhooks still work too, if you prefer one call per run whatever happened.
Exports. Datasets export as JSON, CSV, Excel, XML and RSS from the Console or the API, with the three named views to split reviews, metadata and rankings cleanly. The API endpoint accepts format and view as query parameters, so the export is a single URL you can put in a cron job.
Anything Apify connects to. Runs and datasets are reachable from Zapier, Make, n8n, Google Sheets, Airbyte and Keboola through the standard Apify integrations, plus the official Python and JavaScript clients shown above for anything custom.
MCP and AI agents. The Actor runs over the Apify MCP server, so an agent can call it as a tool, pass an app id and read the reviews back without any glue code. The common iOS and Android schema helps here more than anywhere else: an agent gets one field layout to reason about instead of two, and the flat per-app price means an agent exploring a category cannot run up a bill proportional to how much it read.