# App Store & Google Play Reviews Scraper API | $0.10/1K Reviews (`opaled_hurricane/unified-app-store-reviews-scraper`) Actor

Scrape Apple App Store and Google Play reviews in one run, with one identical output schema for both stores. $0.10 per 1,000 reviews, and you only pay for reviews you actually receive. Ratings, review text, dates, app versions and developer replies. Export to JSON, CSV or Excel. No API key.

- **URL**: https://apify.com/opaled\_hurricane/unified-app-store-reviews-scraper.md
- **Developed by:** [Dustin Hill](https://apify.com/opaled_hurricane) (community)
- **Categories:** Developer tools, Automation, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.10 / 1,000 review scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## App Store & Google Play Reviews Scraper

**Scrape user reviews from the Apple App Store and Google Play in a single run,
and get them back in one identical output format.**

Give it Apple app IDs, Google Play package names, or both at once. Every review
comes back as a flat record with the same 16 fields regardless of which store it
came from — so you can sort, filter and analyse iOS and Android feedback
together without writing any glue code.

No account, no API key, no proxy setup.

***

### Both stores. One run. One schema.

Most review scrapers do one store. If you need iOS **and** Android you end up
running two actors, getting two different field layouts back, and writing a
script to reconcile them — different date formats, different rating fields,
different names for the same thing.

This actor removes that step. One run, one dataset, one schema:

```
store          appId               appName        rating  reviewDate
apple          310633997           WhatsApp…      5       2026-09-12T03:36:34Z
apple          310633997           WhatsApp…      1       2026-09-12T01:14:07Z
google_play    com.whatsapp        WhatsApp…      4       2026-09-12T02:51:19Z
google_play    com.whatsapp        WhatsApp…      2       2026-09-11T22:08:44Z
```

Same columns. Same date format. Same rating scale. Sort the whole thing by date
and you have a single cross-platform feedback stream.

***

### What people use it for

- **App store reviews scraper** — pull reviews for your own app or a competitor's
- **Google Play reviews API** — a clean JSON feed of Android reviews
- **App reviews export** — dump reviews to JSON, CSV or Excel for a spreadsheet
- **ASO review data** — mine review text and ratings for keyword and sentiment work
- **Competitor monitoring** — track what users say about rival apps over time
- **Release regression tracking** — filter by `appVersion` to see which build
  ratings fell off a cliff
- **Support triage** — find reviews with no `developerReply` and answer them

***

### Input

Provide at least one of `appleAppIds` or `googlePlayPackages`. You can provide
both — that is the point.

```json
{
  "appleAppIds": ["310633997", "389801252"],
  "googlePlayPackages": ["com.whatsapp", "com.instagram.android"],
  "country": "us",
  "language": "en",
  "maxReviewsPerApp": 100,
  "sortBy": "newest"
}
```

| field | type | default | notes |
|---|---|---|---|
| `appleAppIds` | array of strings | — | Numeric App Store IDs |
| `googlePlayPackages` | array of strings | — | Android package names |
| `country` | string | `us` | Two-letter store front. **Drives which reviews Apple returns.** |
| `language` | string | `en` | Two-letter language code. **Drives which reviews Google Play returns.** |
| `maxReviewsPerApp` | integer | `100` | Per app, max 5000 |
| `sortBy` | string | `newest` | `newest`, `rating` or `helpfulness` |

**Where do I find the IDs?**

- Apple — it is in the App Store URL:
  `apps.apple.com/us/app/whatsapp-messenger/id`**`310633997`**
- Google Play — it is the `id` parameter:
  `play.google.com/store/apps/details?id=`**`com.whatsapp`**

***

### Output

One record per review. **Every key is always present**, so you never have to
guard against a missing field. Where a store genuinely does not provide
something, the value is `null` — never a guess, never an omission.

An Apple review:

```json
{
  "store": "apple",
  "appId": "310633997",
  "appName": "WhatsApp Messenger",
  "reviewId": "13657831289",
  "userName": "dad_warrior",
  "rating": 1,
  "title": "Invasive program",
  "text": "Why is this program insisting on full access to my contacts? There is no reason it needs all of them to work.",
  "reviewDate": "2026-09-12T03:36:34Z",
  "appVersion": "26.1.74",
  "thumbsUp": 2,
  "developerReply": null,
  "developerReplyDate": null,
  "country": "us",
  "language": "en",
  "scrapedAt": "2026-09-13T11:37:22Z"
}
```

A Google Play review, from the same run:

```json
{
  "store": "google_play",
  "appId": "com.spotify.music",
  "appName": "Spotify: Music and Podcasts",
  "reviewId": "ad1d9be2-0959-4e98-8229-ee3ee4d9df0e",
  "userName": "A. Mead",
  "rating": 3,
  "title": null,
  "text": "I love music, it's the only thing that keeps me focused, but the ads between every other song are getting hard to sit through.",
  "reviewDate": "2026-04-10T00:04:24Z",
  "appVersion": "9.1.36.1948",
  "thumbsUp": 50,
  "developerReply": "Hi. We'll pass this on to the ads team. Furthermore, have you tried a quick reinstall?",
  "developerReplyDate": "2026-04-10T11:17:33Z",
  "country": "us",
  "language": "en",
  "scrapedAt": "2026-09-13T11:37:23Z"
}
```

#### Fields

| field | type | notes |
|---|---|---|
| `store` | string | `"apple"` or `"google_play"` |
| `appId` | string | Numeric ID (Apple) or package name (Google Play) |
| `appName` | string|null | Resolved app name |
| `reviewId` | string | Unique per review |
| `userName` | string|null | Review author's display name |
| `rating` | integer | 1–5, same scale on both stores |
| `title` | string|null | `null` for all Google Play reviews — they have no titles |
| `text` | string | Review body |
| `reviewDate` | string | ISO 8601 UTC, identical format for both stores |
| `appVersion` | string|null | App version reviewed |
| `thumbsUp` | integer|null | Helpful votes (see note) |
| `developerReply` | string|null | `null` for all Apple reviews — the feed has no replies |
| `developerReplyDate` | string|null | ISO 8601 UTC |
| `country` | string | The store front requested |
| `language` | string | The language requested |
| `scrapedAt` | string | ISO 8601 UTC |

Export the dataset as **JSON, CSV, Excel, XML or RSS** from the Apify console or
the API.

> **`thumbsUp` note:** on Google Play this is the thumbs-up count; on Apple it is
> the net helpful-vote total. Both answer "how many people found this helpful",
> but they are not computed identically.

***

### Why not just run two separate actors?

You can. This exists because the join is the annoying part, not the scraping.

- **One schema, not two.** Two actors give you two field layouts. Apple calls it
  one thing, Google another, dates come back in different formats, and one gives
  you a review title the other does not have. You write reconciliation code and
  maintain it.
- **One run, one dataset.** No merging two exports, no lining up two runs, no
  second schedule to keep in sync.
- **Consistent nulls.** Every record has all 16 keys. Code that reads a field
  never needs a store-specific branch or a `.get()` fallback.
- **One integration.** One actor to call, one input shape, one output contract —
  which matters if an agent or a script is consuming this rather than a human.
- **Honest about store differences.** Where the stores genuinely differ, you get
  `null` and a documented reason, not an invented value papering over the gap.

If you only ever need one store, a single-store scraper is a perfectly good
choice. This is for when you need both.

***

### Limits worth knowing before you run it

These come from the stores themselves, not from this actor:

- **Apple returns at most 500 reviews per app, per country.** The App Store feed
  is 10 pages of 50 and then it stops. Asking for more returns the 500 that exist
  and logs a note. Google Play has no such cap — set `maxReviewsPerApp` as high
  as you need.
- **`sortBy: "rating"` does not exist on Apple.** The App Store feed offers newest
  and most-helpful only. Apple apps fall back to newest and say so in the log;
  Google Play honours it. Mixed runs still work.
- **Apple reviews carry no developer replies.** That field is always `null` for
  Apple. Google Play replies come through in full.
- **The two stores segment reviews differently — this trips people up.**
  Apple splits reviews by **country**: `us` and `gb` return entirely different
  reviews, and `language` does not change them. Google Play splits by
  **language**: `en` and `de` return entirely different reviews, while changing
  `country` alone returns the same ones. So to widen Apple coverage, run more
  countries; to widen Google Play coverage, run more languages. (Verified
  2026-09-13: across `gb`/`de`/`jp`, Apple returned 0 overlapping reviews,
  Google Play returned 100% the same reviews until the language changed.)

### Reliability

- A bad app ID never kills a run. It is logged and skipped; every other app in
  the batch still returns data.
- An app that exists but has no reviews yet returns zero reviews as a success —
  it is not reported as an error.
- Transient network failures retry with exponential backoff.

### Pricing

**$0.10 per 1,000 reviews.** No subscription, no API key, no proxy bills, no
minimum spend.

| you scrape | you pay |
|---|---|
| 100 reviews | **$0.01** |
| 1,000 reviews | **$0.10** |
| 10,000 reviews | **$1.00** |
| 100,000 reviews | **$10.00** |

#### You only pay for reviews you actually receive

Nothing is charged when:

- a run fails validation
- an app ID does not exist
- an app returns no reviews

And if one app in your batch fails while the others succeed, you pay for the
reviews you got and nothing for the app that failed.

This is enforced in the code, not just promised: the charge is issued by the
same operation that writes the review into your dataset. There is no path where
you are billed for a review you did not receive.

***

### How this gets its data, and what that means

This actor reads the same public review endpoints that the App Store and Google
Play websites use to render reviews in your browser. **Both of those endpoints
are disallowed by their host's `robots.txt`:**

| host | rule | what it covers |
|---|---|---|
| `itunes.apple.com` | `Disallow: /*/rss/*` | the App Store review feed |
| `play.google.com` | `Disallow: /_` | the Google Play review endpoint |

The officially sanctioned APIs — Apple's App Store Connect API and Google's Play
Developer API — only return reviews for apps you already own, so neither can
scrape a competitor's reviews.

This is the norm for review scrapers on this platform, and every actor in this
category works the same way. It is stated here so you know what you are running
rather than having to find out. If your use case needs a licensed data source,
look at a commercial review-data provider instead.

### FAQ

**Can I scrape both iOS and Android in one run?** Yes — that is what this is for.
Put IDs in `appleAppIds` and packages in `googlePlayPackages` in the same input.

**Do I need an API key or a developer account?** No.

**Do I need proxies?** No. These endpoints have no anti-bot measures and the
actor runs without proxy configuration.

**Can I export to CSV or Excel?** Yes — any Apify dataset exports to JSON, CSV,
Excel, XML or RSS.

**Can I get more than 500 Apple reviews?** Not for one app in one country; that
is Apple's limit. Running additional countries is how you widen Apple coverage —
each country is a separate set of 500.

**Can I scrape reviews in other languages or countries?** Yes, but note which
knob does the work: set `country` for Apple and `language` for Google Play. To
collect broadly on Apple, run several countries; on Google Play, run several
languages.

**Does it get developer replies?** On Google Play, yes, with timestamps. On
Apple, no — the feed does not include them.

**Can I schedule it?** Yes, use Apify Schedules to run it daily or weekly and
append to the same dataset.

# Actor input Schema

## `appleAppIds` (type: `array`):

Numeric App Store IDs. Find one in any App Store URL: apps.apple.com/us/app/whatsapp-messenger/id310633997 -> 310633997

## `googlePlayPackages` (type: `array`):

Android package names. Find one in any Play Store URL: play.google.com/store/apps/details?id=com.whatsapp -> com.whatsapp

## `country` (type: `string`):

Two-letter country code for the store front, e.g. us, gb, de, jp. This is what determines WHICH reviews the Apple App Store returns - each country has its own separate set. It does not change which Google Play reviews you get (use language for that).

## `language` (type: `string`):

Two-letter language code, e.g. en, de, ja, es. This is what determines WHICH reviews Google Play returns - each language has its own separate set. It does not change which Apple reviews you get (use country for that).

## `maxReviewsPerApp` (type: `integer`):

Upper bound per app. Note: the Apple App Store feed caps at 500 reviews per app per country, so Apple apps return at most 500 regardless of this value. Google Play has no such cap.

## `sortBy` (type: `string`):

Review ordering. Note: the Apple feed does not support sorting by rating; Apple apps fall back to newest (Google Play honours it).

## Actor input object example

```json
{
  "appleAppIds": [
    "310633997",
    "389801252"
  ],
  "googlePlayPackages": [
    "com.whatsapp",
    "com.spotify.music"
  ],
  "country": "us",
  "language": "en",
  "maxReviewsPerApp": 100,
  "sortBy": "newest"
}
```

# Actor output Schema

## `reviews` (type: `string`):

Every review collected in this run, from both the Apple App Store and Google Play, in one normalized schema. Export as JSON, CSV, Excel, XML or RSS.

## `runInConsole` (type: `string`):

Open this run and its collected reviews in the Apify Console.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "appleAppIds": [
        "310633997"
    ],
    "googlePlayPackages": [
        "com.whatsapp"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("opaled_hurricane/unified-app-store-reviews-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "appleAppIds": ["310633997"],
    "googlePlayPackages": ["com.whatsapp"],
}

# Run the Actor and wait for it to finish
run = client.actor("opaled_hurricane/unified-app-store-reviews-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "appleAppIds": [
    "310633997"
  ],
  "googlePlayPackages": [
    "com.whatsapp"
  ]
}' |
apify call opaled_hurricane/unified-app-store-reviews-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,opaled_hurricane/unified-app-store-reviews-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/eP4Nhy2k6pY0g28O9/builds/cwNelHRA5X37tB0Fn/openapi.json
