App Store & Google Play Reviews Scraper | $0.01/App, No Cap avatar

App Store & Google Play Reviews Scraper | $0.01/App, No Cap

Pricing

from $6.00 / 1,000 apps

Go to Apify Store
App Store & Google Play Reviews Scraper | $0.01/App, No Cap

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

Alexis Morain

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

1

Monthly active users

10 days ago

Last modified

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:

JobBilled hereHere, Free planHere, GoldAt $0.10 / 1K reviews
Spotify, US App Store, 5,000 reviews1 app & storefront$0.01$0.006$0.50
Spotify, Google Play US, 20,000 reviews1 app & storefront$0.01$0.006$2.00
10 competitor apps, 1 storefront, 2,000 reviews each10 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:

AppStorefrontPublic RSS feedThis scraper
SpotifyUS0 reviews across all 10 pages1,200+, still paginating at offset 5,000
WhatsAppUS100, or 0 if it stops on the first empty page600+
DoctolibFR150deep 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

  1. 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.
  2. 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.
  3. 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.
  4. Leave Reviews and App metadata both ticked. They are covered by the same billable unit, so taking both costs exactly what taking one costs.
  5. 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.
  6. 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 inLeave alone
Read what users say about one appapps, one entry in countrieseverything else
Compare an app across marketsapps, several countrieseverything else
Get the full review backlog, not a samplemaxReviewsPerApp at a high valueeverything else
Monitor new feedback dailymode: new-only, plus a scheduleeverything else
Get called when new reviews arrivemode: new-only, webhookUrl, plus a scheduleeverything else
Track App Store search positionsadd rankings to include, fill keywordsapps can stay empty
Track Google Play search positions toosame, plus rankingStores: ["apple", "google"]apps can stay empty
Run a competitor app analysisevery competitor in one apps listeverything else
Cut a run short for a quick lookmaxReviewsPerApp at 100everything 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

FieldTypeWhat it doesExample
appsarray of stringsThe 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"]
countriesarray of stringsISO 3166-1 codes of the storefronts to sweep. Case-insensitive. Every app is swept on every storefront listed. Empty means us.["us", "fr", "gb"]
includearray of stringsAny combination of reviews, metadata and rankings. Reviews and metadata share one billable unit. Rankings are billed separately, per keyword, storefront and store.["reviews", "metadata"]
modestringall 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"
webhookUrlstringOptional. 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/..."
maxReviewsPerAppintegerUpper bound on reviews per app and per storefront. Does not affect the price. Default 500.5000
keywordsarray of stringsSearch terms to track. Used only when rankings is in include. Works with an empty apps list.["music streaming"]
rankingDepthintegerHow 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
rankingStoresarray of stringsWhich 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"]
proxyConfigurationobjectOff 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 rankingDepth above 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_count is 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-only mode, the call happens only when at least one new review came in. In all mode, it happens whenever the run returned data.
  • Any 2xx answer counts as delivered, whatever the body, so Slack's ok and 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)

FieldTypeDescriptionExample
typestringAlways review for these rows."review"
storestringapple or google."apple"
app_idstringApp Store numeric id, or Android package name. Always a string, both stores."310633997"
countrystringStorefront this review was collected from, lowercase."us"
review_idstringStore-side review id. Stable, and what the delta mode deduplicates on."13657831289"
ratinginteger1 to 5. Clamped to that range on both stores.5
titlestring or nullReview title. App Store only, Google Play does not have review titles, so it is null there."Potential improvements"
bodystring or nullReview text, as posted, in the storefront's language."I love the app and it's my main..."
authorstring or nullDisplay name of the reviewer."Ed Bradway"
versionstring or nullApp version the review was left on. Google Play only, null on App Store rows."9.1.64.1676"
posted_atstring or nullISO 8601 UTC, second precision, Z suffix. Normalized on both stores, never an epoch, never a localized string."2026-01-21T04:48:38Z"
is_editedboolean or nullWhether the review was edited after posting. App Store only, null on Google Play rows.false
reply_bodystring or nullThe developer's public reply. Google Play only."Thanks for the feedback..."
reply_atstring or nullWhen 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)

FieldTypeDescriptionExample
typestringAlways metadata for these rows."metadata"
storestringapple or google."google"
app_idstringNumeric id or package name."com.spotify.music"
countrystringStorefront the listing was read from."us"
namestring or nullApp name as shown on that storefront."Spotify: Music and Podcasts"
developerstring or nullPublisher name."Spotify AB"
rating_averagenumber or nullAverage star rating, unrounded.4.3448367
rating_countinteger or nullNumber of ratings.36244267
review_countinteger or nullNumber of written reviews. On the App Store this equals rating_count, Apple does not split the two.1850955
versionstring or nullCurrent version on that storefront. Reliable on the App Store, sometimes null on Google Play."26.34.74"
pricenumber or nullListing price in the storefront currency. 0.0 for a free app.0.0
currencystring or nullCurrency of price, as the storefront shows it (USD, EUR, GBP...), on both stores."USD"
genrestring or nullPrimary category."Music & Audio"
installsstring or nullInstall bucket as Google publishes it. Google Play only, null on App Store rows."1,000,000,000+"
descriptionstring or nullFull store description text."With the Spotify music and podcast app..."
icon_urlstring or nullDirect URL to the app icon. 512px on the App Store."https://play-lh.googleusercontent.com/..."
urlstring or nullCanonical 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.

FieldTypeDescriptionExample
typestringAlways ranking for these rows."ranking"
storestringapple or google, the store whose search the position comes from."apple"
countrystringStorefront the search was run in."us"
keywordstringThe search term, exactly as you submitted it."music streaming"
positionintegerRank in the result list, starting at 1.2
app_idstringNumeric App Store id, or Google Play package name, of the ranked app."324684580"
namestring or nullName of the ranked app."Spotify: Music and Podcasts"
developerstring or nullIts publisher."Spotify"
rating_averagenumber or nullIts average rating on that storefront.4.77453
rating_countinteger or nullIts 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 ApifyClient
client = 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 pd
df = 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.

EventFreeBronzeSilverGold and aboveCharged
Actor start$0.02$0.02$0.02$0.02Once 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.006Per 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.0012Per 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.

ScenarioReviews collectedCost here, Free planCost here, GoldPer 1,000 reviews, Free planSame job, metered per review
1 app, 1 storefront, small app500$0.03$0.026$0.060$0.05
1 app, 1 storefront, mid-size app5,000$0.03$0.026$0.006$0.50
1 app, 1 storefront, large app50,000$0.03$0.026$0.0006$5.00
1 app, 5 storefronts25,000$0.07$0.05$0.0028$2.50
10 competitors, 1 storefront20,000$0.12$0.08$0.006$2.00
10 competitors, 5 storefronts100,000$0.52$0.32$0.0052$10.00
20 keywords, 3 storefronts, App Store, no apps0$0.14$0.09n/an/a
20 keywords, 3 storefronts, both stores, no apps0$0.26$0.16n/an/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.