# App Reviews Scraper (`juanoox/app-reviews`) Actor

Scrape user reviews from the Apple App Store and Google Play in one run, with a single unified schema. Rating, title, text, author, app version, developer reply and date for any app, in any country.

- **URL**: https://apify.com/juanoox/app-reviews.md
- **Developed by:** [Juan ignacio Veltri](https://apify.com/juanoox) (community)
- **Categories:** Developer tools, SEO tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.30 / 1,000 results

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 Reviews Scraper — App Store & Google Play in one run

Scrape user reviews from the **Apple App Store** and **Google Play** with a single Actor and a
single output schema. Point it at an app — by store URL, by ID, or just by name — and get every
review with rating, text, author, app version, date and the developer's reply.

**Every other reviews scraper on the Store covers one of the two stores.** If you want to compare
how your app is received on iOS versus Android, you currently run two Actors, get two different
JSON shapes, and reconcile them by hand. This one returns both in the same rows.

***

### What you get

Both stores are read through their own public endpoints — Apple's review feed and Google Play's
own web API. No proxies are needed, nothing is blocked, and nothing is guessed from HTML that
changes every redesign.

| | App Store | Google Play |
|---|---|---|
| Rating, text, author, date | ✅ | ✅ |
| App version reviewed | ✅ | ✅ |
| Review title | ✅ | — *(Google Play has no review titles)* |
| Developer reply | — *(Apple does not publish it)* | ✅ |
| "Helpful" votes | — | ✅ |
| Reviews available | 500 per country | Unlimited, paginated |

Fields a store does not publish come back as `null`. That is information — "this store doesn't
have this" — not a gap in the extraction.

***

### Quick start

Paste an app name and go:

```json
{
  "apps": ["duolingo"],
  "stores": "both",
  "countries": ["US"]
}
```

Or be precise, mixing stores and formats freely:

```json
{
  "apps": [
    "https://apps.apple.com/us/app/instagram/id389801252",
    "com.spotify.music",
    "389801252",
    "notion"
  ],
  "stores": "both",
  "countries": ["US", "GB", "BR"],
  "maxItems": 2000
}
```

A reference that already names its store — a URL, an Apple ID, an Android bundle — is always
honoured. Only a plain name is searched in both stores.

#### Find only the complaints

```json
{
  "apps": ["com.instagram.android"],
  "maxRating": 2,
  "postedWithinDays": 30,
  "onlyWithText": true,
  "keywords": ["crash", "battery", "login"]
}
```

#### Watch what the developer answers

```json
{
  "apps": ["com.spotify.music"],
  "stores": "google_play",
  "onlyWithDeveloperReply": true
}
```

***

### Getting more than 500 reviews from the App Store

Apple's feed stops at 500 reviews **per country**. That is a hard limit of the store, not of this
Actor, and no scraper can go past it on a single market.

The way around it is to read several markets, which this Actor does in one run:

```json
{
  "apps": ["389801252"],
  "stores": "app_store",
  "countries": ["US", "GB", "CA", "AU", "DE", "FR", "ES", "BR", "MX", "JP"]
}
```

Every review carries `storeCountry`, so the markets stay separable afterwards. Google Play has no
such cap and paginates until it runs out.

***

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `apps` | array | example app | Store URL, Apple numeric ID, Android bundle, or app name |
| `stores` | select | `both` | `both`, `app_store`, `google_play` |
| `countries` | array | `["US"]` | Markets to read. Reviews are per country, not global |
| `sortBy` | select | `most_recent` | `most_recent` or `most_helpful` |
| `maxItems` | integer | `500` | Total cap for the run. `0` = no cap |
| `maxReviewsPerApp` | integer | `0` | Cap per app **and** market, so one big app can't eat the run |
| `minRating` / `maxRating` | integer | — | Keep only reviews within a star range |
| `keywords` | array | — | Only reviews mentioning any of these, in title or text |
| `postedWithinDays` | integer | — | Only reviews newer than N days |
| `onlyWithText` | boolean | `false` | Drop ratings with nothing written |
| `onlyWithDeveloperReply` | boolean | `false` | Only reviews the developer answered (Google Play only) |
| `includeSeen` | boolean | `true` | Turn off to skip reviews returned by previous runs |
| `maxConcurrency` | integer | `5` | Parallel requests |
| `maxRequestsPerMinute` | integer | `120` | Rate ceiling |
| `maxRequestRetries` | integer | `3` | Retries before dropping a request |

Supported markets: AR, BR, CL, CO, MX, PE, UY, PY, BO, EC, VE, CR, GT, US, CA, GB, IE, ES, PT,
FR, DE, IT, NL, BE, AT, CH, PL, SE, NO, DK, FI, AU, NZ, IN, SG, JP, IL, ZA.

***

### Output

One row per review, identical shape from both stores:

```json
{
  "id": "app_store:14462223652",
  "url": "https://apps.apple.com/us/app/id389801252?review=14462223652",
  "source": "app-reviews",
  "country": "US",
  "scrapedAt": "2026-08-24T13:34:10.442Z",
  "store": "app_store",
  "appId": "389801252",
  "appName": "Instagram",
  "storeCountry": "US",
  "rating": 1,
  "title": "Algorithmic abuse/manipulation",
  "text": "Meta is now using data obtained from research through a job I applied for…",
  "authorName": "👽🛸👾👽",
  "authorUrl": "https://itunes.apple.com/us/reviews/id1184342001",
  "appVersion": "443.1.0",
  "publishedAt": "2026-08-23T11:35:50.000Z",
  "helpfulCount": null,
  "developerReply": null
}
```

The same app on Google Play, same columns:

```json
{
  "id": "google_play:a2c899a2-a9f1-4921-8d2f-1d8ae09acbf7",
  "url": "https://play.google.com/store/apps/details?id=com.instagram.android&gl=US&reviewId=a2c899a2…",
  "store": "google_play",
  "appId": "com.instagram.android",
  "appName": "Instagram",
  "storeCountry": "US",
  "rating": 5,
  "title": null,
  "text": "Nice upload quality on Android. Hope that it will get better and better.",
  "authorName": "Encellys",
  "authorUrl": null,
  "appVersion": "443.0.0.48.82",
  "publishedAt": "2026-08-23T13:17:51.000Z",
  "helpfulCount": 0,
  "developerReply": null
}
```

`id` is stable across runs and prefixed by store, so the two stores never collide and you can
deduplicate or upsert straight into your own database.

***

### What it is good for

- **ASO and release monitoring** — schedule it, turn `includeSeen` off, and each run returns only
  reviews you haven't seen before.
- **Regression hunting** — filter by `appVersion` and `maxRating` to see what a release broke.
- **Competitor research** — pass several apps at once and compare ratings side by side.
- **Support triage** — `onlyWithDeveloperReply` shows what your team already answered, and what it
  hasn't.
- **Localisation** — the same app across `countries` shows where sentiment differs by market.

***

### Notes and limits

- **The App Store caps at 500 reviews per country.** Add markets to `countries` to go wider. This is
  Apple's limit; nobody can lift it.
- **`onlyWithDeveloperReply` only matches Google Play**, because Apple does not publish developer
  replies at all. Combining it with `stores: "app_store"` returns nothing, by construction.
- **`sortBy` offers the two orders both stores actually support.** Apple documents more, but the
  others return HTTP 500, so they are not offered here rather than sold and broken.
- Reviews with no date are dropped by `postedWithinDays`, because claiming they are recent would
  misreport the filter you asked for.

***

### Pricing

Pay per result. You are charged for reviews that actually land in your dataset — filtered out and
duplicate rows cost nothing.

# Actor input Schema

## `apps` (type: `array`):

The apps to read. Accepts a store URL from either store, an Apple numeric ID, an Android bundle (com.example.app), or just the app name, which is searched in the stores you selected.

## `stores` (type: `string`):

Which stores to query. A reference that already names its store (a URL or an ID) is always honoured, even when you pick both.

## `countries` (type: `array`):

Country codes to read reviews from. Reviews are per market, not global: the App Store returns at most 500 per country, so adding markets is the only way past that ceiling.

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

The order in which the store returns reviews.

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

Cap for each app-and-market pair, so one huge app can't eat the whole run. 0 = no cap.

## `minRating` (type: `integer`):

Drop reviews below this rating. Leave empty for no filter.

## `maxRating` (type: `integer`):

Drop reviews above this rating. Useful to keep only the critical ones.

## `keywords` (type: `array`):

Only reviews mentioning any of these, in the title or the body. Ignores case and accents.

## `postedWithinDays` (type: `integer`):

Only reviews newer than this many days. Reviews with no date are dropped.

## `onlyWithText` (type: `boolean`):

Drop ratings with nothing written.

## `onlyWithDeveloperReply` (type: `boolean`):

Only reviews the developer replied to. The App Store does not publish replies, so this filter leaves Google Play results only.

## `maxItems` (type: `integer`):

Total cap for the run, across every app and market. 0 = no cap.

## `includeSeen` (type: `boolean`):

Turn it off to skip reviews returned by previous runs. For monitoring, schedule it so it reuses the same storage.

## `maxConcurrency` (type: `integer`):

Parallel requests.

## `maxRequestsPerMinute` (type: `integer`):

Rate ceiling. Both stores are free and public: limiting the pace is courtesy to whoever leaves the data open.

## `maxRequestRetries` (type: `integer`):

How many times to retry a failed request before dropping it.

## Actor input object example

```json
{
  "apps": [
    "https://apps.apple.com/us/app/instagram/id389801252"
  ],
  "stores": "both",
  "countries": [
    "US"
  ],
  "sortBy": "most_recent",
  "maxReviewsPerApp": 0,
  "onlyWithText": false,
  "onlyWithDeveloperReply": false,
  "maxItems": 500,
  "includeSeen": true,
  "maxConcurrency": 5,
  "maxRequestsPerMinute": 120,
  "maxRequestRetries": 3
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

# 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 = {
    "apps": [
        "https://apps.apple.com/us/app/instagram/id389801252"
    ],
    "countries": [
        "US"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("juanoox/app-reviews").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 = {
    "apps": ["https://apps.apple.com/us/app/instagram/id389801252"],
    "countries": ["US"],
}

# Run the Actor and wait for it to finish
run = client.actor("juanoox/app-reviews").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 '{
  "apps": [
    "https://apps.apple.com/us/app/instagram/id389801252"
  ],
  "countries": [
    "US"
  ]
}' |
apify call juanoox/app-reviews --silent --output-dataset

```

## MCP server setup

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

```

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/R3jKaRlSltqMZEqGe/builds/0oeOPALTrBdmdzel7/openapi.json
