# App Store & Google Play Reviews Scraper with AI Sentiment (`craftedincode/app-reviews-ai-insights`) Actor

App Store & Google Play Reviews Scraper with AI Sentiment & Feature Requests. Both stores, many countries, app version on every review, optional AI tags and insights. From $0.15 / 1,000 reviews.

- **URL**: https://apify.com/craftedincode/app-reviews-ai-insights.md
- **Developed by:** [Crafted in Code](https://apify.com/craftedincode) (community)
- **Categories:** Developer tools, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.15 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## App Store & Google Play Reviews Scraper with AI Sentiment

Scrape **App Store** and **Google Play** reviews in one run. Every review includes the **app version**, country, rating, and developer reply when the store publishes one. Optional **AI sentiment** tags and a per-app **insights report** turn that feed into bugs, feature requests, and versions that got worse.

Built for product managers, indie developers, mobile agencies, and anyone comparing an app with competitors without a monthly Appbot or AppFollow seat.

### What data can you extract?

| Field | What it is |
| --- | --- |
| `store`, `appId`, `appName`, `country` | Which storefront the review came from |
| `reviewId`, `rating`, `title`, `text` | The review. Google Play has no separate title |
| `appVersion` | Version the reviewer was using |
| `date`, `developerReply`, `replyDate`, `thumbsUp`, `reviewUrl` | Timing, reply, and link |
| `userName` | Only if you turn on **Include reviewer names**. Off by default |
| AI tags | `sentiment` (−1 to 1), `categories`, `featureRequested`, `bugDescription`, `severity`, `churnRisk`, `language`, `isSpam` |
| Insights row | Top themes with counts and quotes, version regressions, feature requests, monthly sentiment trend, reply-priority list |
| Compare row | The same themes across 2–5 apps |

Categories are: bug, crash, performance, UX, pricing, ads, login, feature-request, support, praise.

### How to scrape reviews

1. Add each app. Apple ids are numeric (`310633997` is WhatsApp). Google ids are package names (`com.whatsapp`). A store URL works too.
2. Add country codes (`us`, `gb`, …). Each country is a separate storefront on the same output schema.
3. Set **Max reviews per app**. Leave AI off for a cheap raw export.
4. Run. Reviews land in the dataset. Reports also land in the dataset (`itemType` is `insights-report` or `compare-report`) and in the key-value store under `INSIGHTS-…` and `COMPARE`.

The prefilled input is WhatsApp on both stores, United States, 50 reviews, AI off. That is the run the daily Store test uses. It should finish in well under five minutes with a non-empty dataset.

### How much does it cost to scrape app reviews?

Pay per event. You are not billed for empty or duplicate rows, and there is no per-dataset-item charge on top of these events.

| Event | Free plan | Paid plans (Bronze and up) |
| --- | --- | --- |
| `review-scraped` (primary) | $0.0003 | $0.00015 |
| `review-ai-tagged` | $0.003 | $0.001 |
| `app-insights-report` | $0.30 | $0.10 |

Worked examples on a **paid** plan:

- 1,000 reviews, no AI: **$0.15**
- 1,000 reviews with AI tags: **$1.15**
- Those 1,000 reviews, AI tags, and 2 app reports: **$1.35**

**Free plan:** 100 reviews, 20 AI tags, and 1 report per run. Across a month, a free user is also limited to 100 AI tags and 4 reports when the usage store is writable. The run stops at the cap and keeps the rows already saved. A review is billed only after it is saved. An AI tag is billed only after that same row is saved. Caps are checked first, in batches of about 50, so a review that cannot be billed is not sent to the model.

`maxAiReviews` is split across the apps in the run. Each app gets an equal share, and any remainder goes to the earlier apps. If an app has fewer reviews than its share, those unused tags are given to the apps that still have reviews. The free-plan limit of 20 is a cap on the whole run. That total is what gets split, so the App Store cannot use all 20 and leave Google Play on keywords.

Set a max charge on the run (`ACTOR_MAX_TOTAL_CHARGE_USD`) or pass `maxChargeUsd`. The Actor stops when that ceiling, `maxReviewsPerApp`, or `maxAiReviews` is hit.

### Limits you should know

**Apple only returns about the latest 500 reviews per country.** The public customer-review RSS feed is 10 pages of 50. Page 11 answers HTTP 400. Checked on 7 Oct 2026 for WhatsApp (id `310633997`): page 10 still had 50 reviews, and the feed's last-page link was page 10. That is Apple's cap. This Actor does not pretend to go deeper.

**Google Play** is read from the public `batchexecute` reviews endpoint and paginated with the continuation token. A repeated token or a page of duplicate ids stops the crawl, so a stuck first page cannot bill you for the same reviews over and over. Google changes that endpoint from time to time. If a store returns no reviews on the first page, or the request fails, the Actor retries up to 3 times with backoff. When a proxy is on, each try uses a new session. If it is still empty, the run logs a warning naming the store, app id, and country, and records it on `RUN-STATS` (`emptyApps` and `warnings`). The other stores still finish. An insights report is skipped for an app that saved no reviews, and that skip is not charged. A run that saves nothing still **fails** instead of succeeding with zero rows. Turn on Apify Proxy only if a store blocks the datacenter IP. The default is no proxy.

Reviewer names are personal data. They are dropped unless you opt in.

A run that saves no reviews fails on purpose. A brand-new app with an empty store page will fail the same way.

### Input example

```json
{
  "apps": [
    { "store": "apple", "id": "310633997" },
    { "store": "google", "id": "com.whatsapp" }
  ],
  "countries": ["us", "gb"],
  "maxReviewsPerApp": 500,
  "sinceDate": "2026-07-01",
  "aiTagging": true,
  "insightsReport": true,
  "compareApps": true,
  "maxAiReviews": 2000
}
```

`compareApps` needs 2 to 5 apps. On the free plan the comparison is the one report that gets produced.

### Output example

```json
{
  "itemType": "review",
  "store": "apple",
  "appId": "310633997",
  "appName": "WhatsApp Messenger",
  "country": "us",
  "reviewId": "14632329699",
  "rating": 1,
  "title": "Ads",
  "text": "Way too many long ads.",
  "appVersion": "26.39.73",
  "date": "2026-10-05T23:16:19.000Z",
  "developerReply": null,
  "replyDate": null,
  "thumbsUp": 0,
  "reviewUrl": "https://itunes.apple.com/us/review?id=310633997",
  "sentiment": -0.7,
  "categories": ["ads"],
  "featureRequested": null,
  "bugDescription": null,
  "severity": "med",
  "churnRisk": true,
  "language": "en",
  "isSpam": false
}
```

AI fields are omitted when tagging is off. `userName` is omitted unless you opt in.

### FAQ

**Is this legal?** The Actor reads public review pages and feeds. It does not log in. You are responsible for how you store and use the export, including reviewer names if you turn them on.

**Why both stores?** Most scrapers cover one store, and several Google Play Actors on the Store have been reported to stop after page 1 or to drop the app version. This Actor always requests the next token, stops on duplicates, and puts `appVersion` on every row.

**What is sentiment?** A model score from −1 (very negative) to 1 (very positive), plus a category list. The insights report counts those tags in code. The model writes the short summary. It does not get to invent the counts. Spam is excluded from the counts but still saved in the dataset.

**Version regressions** are themes whose share of reviews jumps by 15 percentage points or more versus the previous version, with at least 3 mentions. Sentiment trend is grouped by calendar month (UTC).

**Integrations.** Use the Apify API, schedules, webhooks, Make, n8n, Zapier, or MCP. The dataset is normal JSON. Download it or pass the dataset id into the next step. Reports are also in the run's key-value store (`RUN-STATS`, `COMPARE`, `INSIGHTS-<store>-<appId>`).

**Proxy.** Leave it off. Public endpoints usually answer from a datacenter IP. If a run fails because the store blocked that IP, enable Apify Proxy and run again. Proxy session ids are alphanumeric.

**AI keys.** Tagging and reports use the publisher's Gemini key (`GEMINI_API_KEY`, model `gemini-3.5-flash-lite` unless `GEMINI_MODEL` is set). An OpenAI key is an optional fallback. You do not paste a key into the input. Leave AI off and no key is required.

### Honest comparison

| | This Actor | Typical single-store scraper |
| --- | --- | --- |
| Stores | App Store and Google Play together | One store |
| App version | On every review | Often missing |
| Pagination | Token plus duplicate-page stop | Some Actors stop after page 1 |
| Apple depth | Documented 10 × 50 cap | Often undocumented |
| AI | Optional, separate events, free-plan caps | Rare, or a flat expensive report |
| Reviewer names | Off by default | Often included |

# Actor input Schema

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

Apps to scrape. Apple ids are numeric (WhatsApp is 310633997). Google ids are package names (com.whatsapp). You can also paste an App Store or Play Store URL in id.

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

Two-letter storefront country codes. Each country is scraped separately and written with a country field.

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

Language passed to Google Play as hl (for example en or es). Apple's public RSS feed is localized by country, not by this field.

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

Cap per app, across all countries combined. The free plan is also capped at 100 reviews for the whole run. Apple's public feed only returns about 10 pages × 50 reviews per country.

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

newest stops early when sinceDate is set. Apple has no rating sort; rating falls back to newest on the App Store.

## `sinceDate` (type: `string`):

Keep reviews on or after this date (YYYY-MM-DD). Leave empty for no date filter.

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

Drop reviews below this star rating. Leave empty to keep every rating.

## `includeReviewerNames` (type: `boolean`):

Off by default. Reviewer names are personal data. Turn this on only if you have a reason to store them.

## `aiTagging` (type: `boolean`):

Sentiment, categories, feature request, bug, severity, churn risk, language, and spam. Charged as review-ai-tagged after the review is saved. Free plan: 20 tagged reviews per run.

## `insightsReport` (type: `boolean`):

One report per app: top themes, version regressions, feature requests, sentiment trend, and a reply-priority list. Charged as app-insights-report.

## `compareApps` (type: `boolean`):

Compare complaint themes across 2–5 apps. Charged as one extra app-insights-report. On the free plan only one report is produced per run, and the comparison comes first.

## `maxAiReviews` (type: `integer`):

Hard cap on review-ai-tagged charges for this run. The cap is split evenly across every app (remainder goes to the earlier apps). If an app has fewer reviews than its share, the unused tags go to the other apps. The free-plan cap of 20 still applies to the total, not to each app.

## `maxChargeUsd` (type: `number`):

Optional extra ceiling for this run. The run also stops at ACTOR_MAX_TOTAL_CHARGE_USD when that is set in the Apify console. The lower of the two wins.

## `proxyConfiguration` (type: `object`):

Leave the proxy off unless a run fails because a store blocked the datacenter IP. Public review endpoints usually answer without one.

## Actor input object example

```json
{
  "apps": [
    {
      "store": "apple",
      "id": "310633997"
    },
    {
      "store": "google",
      "id": "com.whatsapp"
    }
  ],
  "countries": [
    "us"
  ],
  "language": "en",
  "maxReviewsPerApp": 50,
  "sortBy": "newest",
  "includeReviewerNames": false,
  "aiTagging": false,
  "insightsReport": false,
  "compareApps": false,
  "maxAiReviews": 2000,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

One row per review (itemType 'review'), plus one row per app insights report (itemType 'insights-report') and per empty or failed app (itemType 'error').

## `runStats` (type: `string`):

RUN-STATS record: reviews saved, AI tags, reports, empty apps, warnings, estimated charge.

## `files` (type: `string`):

INSIGHTS-<store>-<appId> records with the full per-app reports.

# 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": [
        {
            "store": "apple",
            "id": "310633997"
        },
        {
            "store": "google",
            "id": "com.whatsapp"
        }
    ],
    "countries": [
        "us"
    ],
    "language": "en",
    "maxReviewsPerApp": 50,
    "sortBy": "newest",
    "sinceDate": "",
    "includeReviewerNames": false,
    "aiTagging": false,
    "insightsReport": false,
    "compareApps": false,
    "maxAiReviews": 2000,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("craftedincode/app-reviews-ai-insights").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": [
        {
            "store": "apple",
            "id": "310633997",
        },
        {
            "store": "google",
            "id": "com.whatsapp",
        },
    ],
    "countries": ["us"],
    "language": "en",
    "maxReviewsPerApp": 50,
    "sortBy": "newest",
    "sinceDate": "",
    "includeReviewerNames": False,
    "aiTagging": False,
    "insightsReport": False,
    "compareApps": False,
    "maxAiReviews": 2000,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("craftedincode/app-reviews-ai-insights").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": [
    {
      "store": "apple",
      "id": "310633997"
    },
    {
      "store": "google",
      "id": "com.whatsapp"
    }
  ],
  "countries": [
    "us"
  ],
  "language": "en",
  "maxReviewsPerApp": 50,
  "sortBy": "newest",
  "sinceDate": "",
  "includeReviewerNames": false,
  "aiTagging": false,
  "insightsReport": false,
  "compareApps": false,
  "maxAiReviews": 2000,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call craftedincode/app-reviews-ai-insights --silent --output-dataset

```

## MCP server setup

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

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/UnYxa093V1xoDagIU/builds/pyyqDxKlrUoKNr31Y/openapi.json
