App Store & Google Play Reviews API
Pricing
from $0.12 / 1,000 review returneds
App Store & Google Play Reviews API
Scrape App Store and Google Play reviews as data. One row per review: rating, text, author, app version, review date, developer reply. Plus one app row per app with rating, rating histogram, installs, price and category. Any country, no API key, no login.
Pricing
from $0.12 / 1,000 review returneds
Rating
0.0
(0)
Developer
Insight Solutions
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
13 hours ago
Last modified
Categories
Share
Get any app's reviews from both stores as one flat table. Give this Actor a list of App Store links, Google Play links, Apple app IDs or Android package names — mixed together, in any order — and get back one row per review: the star rating, the text as written, who wrote it, which app version they were running, when they posted, and the developer's public reply. Each app also gets one row of its own store listing: rating, the per-star histogram, install band, price, category and dates.
No API key. No login. No cookies to paste. No App Store Connect or Play Console access. $0.20 per 1,000 reviews, proxy included, apps that could not be read are free, and a run that returns nothing costs nothing at all.
Try it in 30 seconds
{"apps": ["https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580","com.spotify.music"],"country": "us","maxReviewsPerApp": 50,"sort": "newest"}
The same app on both stores, one App Store URL and one bare Android package name. Apple app IDs (324684580) and full Google Play URLs work too.
What comes back
One review row per review:
{"ok": true,"rowType": "review", // "review" | "app" | "diagnostic""store": "google", // "apple" | "google""appId": "com.spotify.music","appUrl": "https://play.google.com/store/apps/details?id=com.spotify.music&hl=en&gl=US","appTitle": "Spotify: Music and Podcasts","reviewId": "f7f4c69a-b9d1-4da0-b881-7ce3acd0fc94","rating": 1, // 1–5 stars"title": null, // the review's headline — App Store only"text": "worst experience, too many ads","author": "pratiksha","version": "9.1.80.2221", // the app version the reviewer was running"reviewedAt": "2026-09-08T14:19:18.000Z","thumbsUp": 0, // readers who marked it helpful"developerReply": "Hi. Thanks for sharing this. We hear you and we'll pass your feedback along to the Ads team.","developerRepliedAt": "2026-09-08T16:17:54.000Z","country": "us","language": "en","position": 1, // 1-based, in the order the store served it"sort": "newest","error": null,"errorType": null,"scrapedAt": "2026-09-09T05:53:05.487Z","source": "play.google.com","sourceUrl": "https://play.google.com/store/apps/details?id=com.spotify.music&hl=en&gl=US"}
And one app row per app, before its reviews, on the same columns:
{"ok": true,"rowType": "app","store": "google","appId": "com.spotify.music","title": "Spotify: Music and Podcasts","developer": "Spotify AB","description": "With the Spotify music and podcast app, you can play millions of songs…","category": "Music & Audio","price": 0,"currency": "USD","isFree": true,"rating": 4.345289707183838,"ratingCount": 36268768,"ratingHistogram": { "1": 3705674, "2": 1066445, "3": 1316488, "4": 3090485, "5": 27089653 },"installsText": "1,000,000,000+","version": null, // Google no longer publishes one for many apps"releaseDate": "2014-05-27T13:12:17.000Z","updatedAt": "2026-09-08T09:26:27.000Z","contentRating": "Teen","iconUrl": "https://play-lh.googleusercontent.com/IzQgYCcnCF…","screenshots": ["https://play-lh.googleusercontent.com/c1jnjL-9CI…"],"minimumOs": null,"sizeBytes": null // App Store rows carry both of these}
Every row — review, app and diagnostic — carries the same keys, so the dataset exports as one rectangular CSV whatever mix of stores and row types a run produced.
Use cases
- Release monitoring.
sort: "newest"after a ship, filtered onversion, tells you within the hour whether the 1-stars are about the thing you just changed. - Competitive research. The same query against a competitor's package name, in the same country, on the same day.
ratingHistogramis the shape of their reputation; the 2- and 3-star reviews are where the real complaints live. - Voice-of-customer and sentiment analysis. One flat array of text with a star rating attached — a labelled dataset, ready to hand to a model, without the labelling.
- Support triage and bug discovery. App store reviews are a bug tracker nobody filed. Filter
rating <= 2and read the last week. - Localisation and market research. Run the same app across
countrycodes: the German storefront's rating, its reviews and its complaints are a different dataset from the US one, not a translation of it. - Developer-response auditing.
developerReplyanddeveloperRepliedAt(Google Play) show which reviews got answered, how fast, and whether the answer was a template. - ASO reporting.
rating,ratingCount,installsTextandupdatedAttracked over time, per country, from a scheduled run.
How it works, and why it keeps working
Neither store has a public reviews API. Apple's App Store Connect API only shows you your own apps; Google's Play Developer API is the same. This Actor reads the two public surfaces the stores serve to anyone.
| Store | What it reads | Shape |
|---|---|---|
| App Store — details | itunes.apple.com/lookup?id=… | JSON, one result object. Accepts a bundle ID too |
| App Store — reviews | The customer-reviews RSS feed, /rss/customerreviews/page=N/id=…/sortby=…/json | JSON, fifty reviews a page, ten pages maximum |
| Google Play — details | The public app page | 1.2 MB of HTML with a JSON-LD block and a set of AF_initDataCallback data blobs in it |
| Google Play — reviews | POST /_/PlayStoreUi/data/batchexecute, RPC UsvDTd | The Play web client's own reviews call. Token-paginated |
Both stores have a quirk that catches naive scrapers, and both are handled here:
Apple answers with content-type: text/javascript. The body is JSON. A client that trusts the header sees a script and gives up.
Google wraps its answer twice. The response opens with )]}' — deliberately invalid JavaScript, so the body cannot be loaded with a <script> tag — then an array whose payload row carries the actual reviews as a string of JSON inside it. Two decodes, not one. The details page needs a bracket-counting scanner rather than a regex, because the data blobs are megabyte-scale nested arrays full of braces and apostrophes inside strings.
Underneath: Apify proxy, one pinned session per parallel worker. When a store refuses an exit IP — HTTP 429, HTTP 403, an empty body, a body that will not decode, or a consent wall in place of the page — that session is retired and the same page is asked for once more from a different address. Retrying on an address that was just refused only deepens the block, so it is never done. If the second address is refused too, the walk stops, keeps every review it already delivered, and files one free blocked row saying where it stopped.
Pages of the same app are spaced 250–600 ms apart. Nothing forces that; it is the difference between reading a store listing and hammering one.
How it compares
- Both stores, one schema. The same columns for an Apple review and a Google one, so a comparison is a
GROUP BY storerather than a reconciliation project. Where a store genuinely does not publish a field — Apple has no developer replies, Google has no review headline — the column is null rather than faked. - The app's own numbers come with the reviews.
rating,ratingCount,ratingHistogramandinstallsTextin the same run, so a sample of 100 reviews can be read against the population it came from. - Honest about the App Store's ceiling. Apple's review feed stops at 500 reviews per country per sort order and there is no way past it. This Actor says so in the input description rather than quietly returning 500 and letting you assume that was all of them.
- Failures are free and legible. A missing app, an app with no reviews in that storefront, or a block produce a diagnostic row with an
errorTypeyou can branch on — and no charge. A run that returns nothing at all finishes FAILED with the reason in its status message, never a green run containing an apology. - A partial walk is kept, not thrown away. Hit
maxRunSecsor your charge ceiling on page 12 and you keep pages 1–11. - No login, ever. No Apple ID, no Google account, no cookie jar, no session token. Public endpoints only, which is also why it runs under Apify's limited permissions.
Input reference
| Field | Type | Default | What it does |
|---|---|---|---|
apps (required) | array of strings | prefilled with two | App Store URLs, Google Play URLs, Apple numeric IDs, or Android package names. A dotted name is read as Android; apple:com.spotify.client forces an iOS bundle lookup. Duplicates are read, and billed, once |
country | string | us | Two-letter storefront code. Both stores are per-country: us and de reviews of the same app are different sets in different languages |
language | string | en | The language Google Play answers in — app title, description, category, install text. Apple's review feed has no language parameter |
maxReviewsPerApp | integer | 100 | Reviews per app. 0 = as many as the store will serve, which is tens of thousands on Google Play and 500 on the App Store |
sort | newest | helpful | newest | Reverse-chronological, or each store's own engagement ranking. The two return substantially different reviews |
ratingFilter | integer 0–5 | 0 | Only reviews with this many stars. Google Play only; Apple ignores it. 0 = every rating |
includeAppDetails | boolean | true | Add one app row per app. One extra request, charged as its own event |
maxConcurrency | integer | 3 | Apps in parallel. Each worker keeps its own proxy session. Pages within one app cannot be parallelised |
maxRunSecs | integer | 240 | Whole-run wall-clock budget. When it runs out the Actor keeps what it has and files a free diagnostic row for each app it never reached |
proxyConfiguration | object | Apify residential | Google Play refuses many datacenter ranges. Scraping only the App Store? Switch to { "useApifyProxy": true } and pay much less in proxy traffic |
Output reference
Every row carries the same keys. ok: true is a review or an app row; ok: false is a free diagnostic row.
| Field | What it is |
|---|---|
rowType | review, app or diagnostic |
store, appId, appUrl, input, appTitle | Which store, the join key, the public listing, the entry you supplied, and the app's name — on every row |
title | The app's name on an app row; the review's headline on a review row (App Store only) |
developer, developerId | App rows: the publisher |
description, category, contentRating | App rows: the listing text, primary category and age rating, in the requested language |
price, currency, isFree | App rows: the download price in the storefront's currency |
rating | The app's average on an app row; the review's 1–5 stars on a review row |
ratingCount, ratingHistogram | App rows: how many ratings, and the per-star breakdown (Google Play only) |
installsText | App rows: Google's install band, e.g. 1,000,000,000+. Apple publishes none |
version | The current version on an app row; the version reviewed on a review row |
releaseDate, updatedAt | App rows: first published, and when the current version shipped |
iconUrl, screenshots, minimumOs, sizeBytes | App rows: the listing's assets and requirements |
reviewId, author, text, reviewedAt, thumbsUp | The review itself. reviewId is stable, so it is what you deduplicate on between runs |
developerReply, developerRepliedAt | The publisher's public answer. Google Play only |
country, language, position, sort | Which storefront, which order, and where this row fell in it |
ok, error, errorType | Whether this row is data, and if not, why not |
scrapedAt, source, sourceUrl | When, and from where |
errorType on a diagnostic row is one of:
| Value | Meaning | Charged? |
|---|---|---|
not-found | No app with that identifier in that storefront | No |
no-reviews | The app exists and the store returned no reviews for it there | No |
blocked | The store refused our requests from two different proxy exits. Reviews already returned for that app are kept | No |
invalid-input | The entry was not a store link or an app identifier | No |
timeout | The run's maxRunSecs budget ran out before this app was reached | No |
unavailable | Something else went wrong; the detail is in error | No |
Pricing
$0.20 per 1,000 reviews. Pay-per-event, with proxy already inside that number — there is no separate proxy line on your bill for this Actor.
| Event | What triggers it | FREE | Starter | Scale | Business |
|---|---|---|---|---|---|
| Review returned (primary) | One review row written to your dataset | $0.0002 | $0.0002 | $0.00016 | $0.00012 |
| App details returned | One app row written to your dataset | $0.002 | $0.002 | $0.002 | $0.002 |
| Run started | Once per run, after the first paid row | $0.001 | $0.001 | $0.001 | $0.001 |
Worked example. 10 apps on both stores (20 entries), 100 reviews each, app details on, one package name misspelled:
- 19 apps × 100 reviews × $0.0002 = $0.38
- 19 app rows × $0.002 = $0.038
- 1 run start = $0.001
- 1 misspelled package = $0.00 (free
not-foundrow) - Total: $0.419
Daily monitoring example. 5 apps, 50 newest reviews each, includeAppDetails: false: 250 × $0.0002 + $0.001 = $0.051 per run, about $1.53 a month on a daily schedule.
What you are never charged for: an app that does not exist, an app with no reviews in that storefront, an entry that was not an app, an app the run never reached before maxRunSecs, or a page the store blocked. If a whole run comes back empty it finishes FAILED and bills nothing at all, start fee included.
Set ACTOR_MAX_TOTAL_CHARGE_USD on a run and the Actor stops fetching once the ceiling is in sight, rather than handing you rows it cannot bill or billing you for rows it cannot hand over. It finishes SUCCEEDED with the ceiling named in its status message, and everything already delivered is yours.
Limits, and the ones that might bite
The App Store caps at 500 reviews per app, per country, per sort order. Ten pages of fifty, and page eleven answers HTTP 400 with "CustomerReviews RSS page depth is limited to 10". Nothing gets past it — not this Actor, not any other tool, because the limit is in Apple's feed. If you need more App Store reviews of one app, run it again against other country codes; each storefront is its own set of 500. Google Play has no comparable limit and will page through tens of thousands.
Reviews are per-country, and so are ratings. The same app had a 4.35 average on the US Play storefront and 4.18 on the German one in our captures, from the same global rating count. A single-country run is a single-country answer.
The App Store publishes no developer replies. Replies exist and are visible on the web page; the RSS feed does not carry them. developerReply is therefore always null for Apple rows. Google Play publishes them and they come through.
Google Play publishes no version for many large apps, and no download size on the web listing at all. version and sizeBytes are null for those, which is what the page says. The App Store publishes both for everything.
thumbsUp is near-zero under newest. A review posted an hour ago has had no time to collect helpful votes. Use sort: "helpful" when that column is what you are after — in our captures the top most-relevant review had 17,330 votes and the busiest of the last hour's had 99.
ratingFilter is Google Play only, and worth verifying against your own app before you rely on it: it is a filter slot in an undocumented RPC, not a supported parameter.
Reviews you can only see signed in — flagged, removed, or held for moderation — are not here. This Actor does not log in, does not accept cookies and does not take a session token, and it never will; that is a deliberate line, not a missing feature.
The upstream format may change. Both stores are read through surfaces they change without notice — that is true of every tool that reads app store reviews, including the ones that do not say so. When a shape changes, rows stop arriving and you get free blocked or not-found diagnostic rows rather than quietly wrong data, and a run that returns nothing bills nothing.
Rate and reliability. Requests go out through proxy sessions pinned per worker, one rotation per block, and a 250–600 ms pause between pages of the same app. Three apps in parallel is the default because it is where throughput and block rate balance; raising maxConcurrency speeds a long list up and makes blocks more likely.
Use it from an AI agent, or from code
One JSON object in, one flat array out — the shape agent runtimes want. The Actor runs with limited permissions, uses pay-per-event pricing and never enters Standby, so it works over the Apify MCP server and with x402 agentic payments. The Integrations tab pushes results to Slack, a webhook, Zapier, Make, Google Sheets, Snowflake or BigQuery.
curl -X POST "https://api.apify.com/v2/acts/insight.solutions~app-reviews-api/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"apps":["com.spotify.music"],"country":"us","maxReviewsPerApp":50,"sort":"newest"}'
# pip install apify-clientfrom apify_client import ApifyClientclient = ApifyClient("<APIFY_TOKEN>")run = client.actor("insight.solutions/app-reviews-api").call(run_input={"apps": ["https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580","com.spotify.music",],"country": "us","maxReviewsPerApp": 200,"sort": "newest","includeAppDetails": True,})for row in client.dataset(run["defaultDatasetId"]).iterate_items():if not row.get("ok"):print("skipped:", row["input"], row["errorType"])elif row["rowType"] == "app":print(f'{row["store"]:6} {row["title"]}: {row["rating"]} from {row["ratingCount"]} ratings')else:print(f' {row["rating"]}★ {row["author"]}: {row["text"][:80]}')
Set includeAppDetails: false for a pure review feed — the payload shrinks and the app-details event is not billed.
FAQ
Do I need an Apple or Google developer account? No. Both stores' review data here is public. App Store Connect and the Play Developer API only show you apps you own; this shows you any app, including your competitors'.
Can I get more than 500 App Store reviews for one app?
Not from one country. Apple's feed stops at ten pages of fifty, per country per sort order. Run the same app against us, gb, ca, au and so on and you get 500 from each. Google Play has no such limit.
Are Apple and Google reviews the same people?
No — they are entirely separate populations writing about separate builds, and the ratings usually differ. The store column is there so you never accidentally average them together.
What does position mean?
Where the review fell in the order the store served it, 1-based, per app. With sort: "newest" position 1 is the most recent review at the moment of the run.
How do I deduplicate between runs?
On reviewId. Both stores' IDs are stable — Apple's is a numeric string, Google's a UUID — so an incremental pipeline is an upsert keyed on (store, appId, reviewId).
Why is developerReply always empty for my iOS app?
Because Apple's review feed does not carry replies, even though they show on the web page. It is a limitation of the source, not of this Actor.
Can I filter by date?
Not at the source — neither store takes a date parameter. Ask for sort: "newest" and stop reading when reviewedAt passes your cutoff, which for a daily monitoring run means a small maxReviewsPerApp.
Does the language input change which reviews come back?
On Google Play it changes the app text, not the review set — reviews follow the country storefront. On the App Store the language input does nothing at all: the feed answers in whatever the storefront speaks.
What happens if one app fails?
The others still run. The failed one produces a free diagnostic row and the run finishes SUCCEEDED. If every app fails, the run finishes FAILED and you are billed nothing at all.
Is the data fresh? Live. Every run reads both stores at that moment; nothing is cached.
Legal and data-protection notes
- Public listings only. Every source is a public store page or its public feed. The Actor never logs in, never accepts cookies or session tokens, never takes an API key belonging to anyone else, and never touches a developer console.
- Reviews are personal data in most jurisdictions. A review carries a display name and, on Google Play, an account identifier, and under the GDPR and similar laws that is personal data about an identifiable person. You are the controller of whatever you collect: have a lawful basis, keep only what you need, honour deletion requests, and remember that a review deleted on the store stays in your dataset until you remove it.
- Reviews are their authors' words. Republishing them, or training on them, is your call and your responsibility, subject to each store's terms and to the law where you operate. Aggregation, sentiment analysis and quotation are the ordinary uses and are what this is built for.
- Not affiliated with Apple Inc., Google LLC, or with any developer or reviewer whose content you retrieve. All product names and trademarks belong to their respective owners and are used only to describe which public endpoints this Actor reads.
Our other Actors
Every Insight Solutions Actor is pay-per-result with no browser, no login and no API key, and every one of them returns free diagnostic rows instead of billing for failures. Prices are per 1,000 results.
Video, audio & social
- YouTube Transcript API — captions as timed segments, text, SRT or VTT, with language fallback and translation.
- YouTube Comments API — comments and replies with likes, pinned and hearted flags, newest or top sort.
- YouTube Channel API — a channel's videos, Shorts and live streams, plus YouTube search.
- Podcast Search, Episodes & Charts API — Apple Podcasts search, charts and full episode feeds.
- Bluesky Scraper — profiles, posts, followers and follows from the public AT Protocol API.
- Telegram Channel Scraper — posts, views and channel stats from public Telegram channels.
- Substack Scraper — posts with full free text, comments and publication profiles.
News, documents & the web
- Google News Search, Topics & Real Article URLs — news search and topic feeds with the publisher's real URL decoded.
- Website to Markdown — Content Extractor for LLMs & RAG — any site as clean Markdown, text and heading-aware chunks.
- Internet Archive API — archive.org search, item metadata, files and reviews.
- Wayback Machine Toolkit — archived URL inventories, snapshots and text diffs between dates.
- Website Technology Detector — the tech stack behind any site, with the evidence for each detection.
- Domain Intelligence API — DNS, RDAP registration, TLS certificate and HTTP facts in one row per domain.
- SEO Page Audit — sitemap crawl with on-page checks, structured data and broken-link reports.
- Keyword Suggestions API — Google, YouTube, Bing, Amazon and eBay autocomplete with alphabet and question expansions.
- Website Contact Extractor — emails, phone numbers and social profiles from any list of websites.
Business, finance & jobs
- Congress & Insider Trades API — STOCK Act periodic transaction reports and SEC Form 4 insider trades in one schema.
- SEC EDGAR API — filings, XBRL financials and full-text search by ticker or CIK.
- Y Combinator Companies, Batches & Founders — the YC directory with founders and social links, filterable by batch, industry and hiring status.
- Career Site Jobs API — jobs straight from Greenhouse, Lever, Ashby, Workable and 10+ other ATS career sites.
- New Job Postings Monitor — new, closed and changed postings on the career sites you watch.
- Shopify Products API — any Shopify store's catalogue, variants, prices and stock signals.
Apps & games
- App Store Top Charts & App Search API — Apple top charts by country and genre, plus app search and details.
- Steam Reviews API — Steam reviews with playtime, helpfulness and game details.
- Steam Game Data API — prices, tags, review scores, live player counts and top charts.