# App & Extension Review Intelligence (`scraping_scrap/app-extension-review-intelligence`) Actor

Monitor public reviews from Google Play, Apple App Store, and Chrome Web Store. Extract normalized review records, detect new reviews, classify sentiment/topics, create app summaries, and send webhook alerts.

- **URL**: https://apify.com/scraping\_scrap/app-extension-review-intelligence.md
- **Developed by:** [Вадим Захаров](https://apify.com/scraping_scrap) (community)
- **Categories:** Developer tools, Automation, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, NaN bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 review data records

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use 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 & Extension Review Intelligence

Store V1 production-ready public review monitor for Google Play reviews, Apple App Store reviews, and Chrome Web Store reviews.

This unofficial Actor is a Google Play reviews scraper, Apple App Store reviews scraper, and Chrome Web Store reviews scraper for app reputation monitoring, competitor review analysis, product feedback, extension review monitor workflows, and negative review alerts.

Use it as an app review monitor when you need one normalized workflow across public mobile app and browser extension review sources.

### What this Actor does

App & Extension Review Intelligence collects publicly visible app and extension reviews, normalizes them into one dataset schema, detects new reviews with stateful deduplication, creates `app_summary` records, classifies rule-based sentiment and topic classification locally, writes a user-facing `OUTPUT` summary to the Key-Value Store, and can send webhook alerts with new negative reviews.

Use cases:

- App reputation monitoring across stores, countries, and languages.
- Competitor review analysis for public app feedback.
- Product feedback mining from complaint themes such as `bug_crash`, `login`, `performance`, `billing`, and `feature_request`.
- Support automation through negative review alerts to Slack, Make, Zapier, CRM, or helpdesk workflows.
- Scheduled onlyNewSinceLastRun monitors that output only reviews not already seen for the same `stateKey`.

### Supported sources

The Actor reads public review surfaces exposed by each store. Use the store-specific app or extension ID listed below.

### Supported stores

| Store | Input `id` | Public source | Language behavior | Developer replies |
| --- | --- | --- | --- | --- |
| Google Play reviews | Android package name, for example `com.spotify.music` | Public Google Play review surface | Uses requested `country` and `language` as public endpoint context where supported | Included when exposed by the public payload |
| Apple App Store reviews | Numeric app ID, for example `324684580` | Public iTunes customer reviews RSS/JSON feed | Storefront-specific; requested language is target context, not a guaranteed source filter | Not exposed by this public RSS source |
| Chrome Web Store reviews | Extension ID, for example `aapbdbdomjkkjkaonfhkkikfgjllcleb` | Public Chrome Web Store review surface | Uses requested country/language context where supported | Included when exposed by the public payload |

### What it does not do

- No login.
- No cookies.
- No private APIs.
- No private developer dashboards.
- No CAPTCHA bypass.
- No account impersonation.
- No guaranteed full historical depth.
- No AI calls or external enrichment APIs.

This Actor collects public data only. Public reviewer display names can appear only when they are part of public review content. It is not affiliated with Google, Apple, Chrome, or any app/extension owner.

### Input schema

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `apps` | `{ store, id, country?, language? }[]` | required | Apps/extensions to scrape. Store must be `google_play`, `app_store`, or `chrome_web_store`. |
| `maxReviewsPerApp` | integer | `100` | Maximum raw reviews per concrete target, from `1` to `5000`. A concrete target is `store + appId + country + language`. |
| `maxReviewsPerTarget` | integer | empty | Alias for `maxReviewsPerApp`. If both are provided, they must match. |
| `ratings` | integer array | `[1, 2, 3, 4, 5]` | Rating filter applied after normalization. Duplicate ratings are removed. |
| `languages` | string array | `["en"]` | Global languages used when a target has no app-level `language`. |
| `countries` | string array | `["US"]` | Global countries/storefronts used when a target has no app-level `country`. |
| `onlyNewSinceLastRun` | boolean | `false` | When `true`, only unseen review records for the same `stateKey` are pushed to the dataset. |
| `stateKey` | string | `default` | Namespace for incremental state in the Actor's internal named Key-Value Store. |
| `includeTopicClassification` | boolean | `true` | Adds rule-based topics for negative reviews. |
| `includeAppSummary` | boolean | `true` | Pushes one `app_summary` record per successful target. |
| `webhookUrl` | HTTP(S) URL | empty | Sends one final JSON POST with summaries and new negative reviews. |
| `proxyConfiguration` | Apify proxy object | `{ "useApifyProxy": true }` | Standard Apify proxy configuration used for store HTTP requests when a proxy URL is available. |
| `maxConcurrency` | integer | `3` | Maximum store targets fetched in parallel. |
| `requestTimeoutSecs` | integer | `30` | Timeout for public HTTP requests. |
| `maxRequestRetries` | integer | `3` | Retry budget for transient store request failures. |
| `maxTargets` | integer | `100` | Safety limit for expanded concrete targets. |
| `maxTotalReviews` | integer | `10000` | Safety limit for `expandedTargets.length * maxReviewsPerApp`. |

App-level `country` and `language` override global arrays. Without overrides, the Actor expands every app into global `countries x languages` combinations. `maxReviewsPerApp` is applied after expansion, per concrete target.

### Input examples

#### 1. Google Play review monitor

```json
{
  "apps": [
    { "store": "google_play", "id": "com.spotify.music", "country": "US", "language": "en" }
  ],
  "maxReviewsPerApp": 100,
  "ratings": [1, 2, 3, 4, 5],
  "includeAppSummary": true,
  "stateKey": "google-play-monitor"
}
````

### Google Play example

The example above is the standard Google Play example for a single app monitor.

#### 2. Apple App Store review monitor

```json
{
  "apps": [
    { "store": "app_store", "id": "324684580", "country": "US", "language": "en" }
  ],
  "maxReviewsPerApp": 100,
  "includeAppSummary": true,
  "stateKey": "apple-app-store-monitor"
}
```

### Apple App Store example

The example above monitors a public Apple App Store RSS review feed by numeric app ID.

#### 3. Chrome Web Store review monitor

```json
{
  "apps": [
    { "store": "chrome_web_store", "id": "aapbdbdomjkkjkaonfhkkikfgjllcleb", "country": "US", "language": "en" }
  ],
  "maxReviewsPerApp": 50,
  "includeAppSummary": true,
  "stateKey": "chrome-extension-monitor"
}
```

### Chrome Web Store example

The example above is a Chrome extension monitor for public extension reviews.

#### 4. Multi-store competitor review analysis

```json
{
  "apps": [
    { "store": "google_play", "id": "com.spotify.music", "country": "US", "language": "en" },
    { "store": "google_play", "id": "com.duolingo", "country": "US", "language": "en" },
    { "store": "app_store", "id": "324684580", "country": "US", "language": "en" },
    { "store": "chrome_web_store", "id": "aapbdbdomjkkjkaonfhkkikfgjllcleb", "country": "US", "language": "en" }
  ],
  "maxReviewsPerTarget": 100,
  "includeAppSummary": true,
  "includeTopicClassification": true,
  "stateKey": "competitor-review-analysis"
}
```

#### 5. onlyNewSinceLastRun scheduled monitor

```json
{
  "apps": [
    { "store": "google_play", "id": "com.spotify.music", "country": "US", "language": "en" },
    { "store": "app_store", "id": "324684580", "country": "US", "language": "en" }
  ],
  "maxReviewsPerApp": 50,
  "onlyNewSinceLastRun": true,
  "includeAppSummary": true,
  "stateKey": "daily-negative-review-monitor"
}
```

#### 6. negative review webhook alert

```json
{
  "apps": [
    { "store": "google_play", "id": "com.spotify.music", "country": "US", "language": "en" },
    { "store": "app_store", "id": "324684580", "country": "US", "language": "en" }
  ],
  "ratings": [1, 2],
  "onlyNewSinceLastRun": true,
  "webhookUrl": "https://example.com/review-alerts",
  "includeAppSummary": true,
  "stateKey": "daily negative review monitor"
}
```

### Output examples

### Output schema

Review records use `"recordType": "review"`:

```json
{
  "recordType": "review",
  "store": "google_play",
  "appId": "com.example.app",
  "appName": null,
  "appUrl": null,
  "developerName": null,
  "country": "US",
  "language": "en",
  "scrapedAt": "2026-05-29T09:00:00.000Z",
  "reviewId": "review-123",
  "fingerprint": "google_play:com.example.app:review-123",
  "reviewUrl": null,
  "rating": 1,
  "title": "Crashes after update",
  "text": "The app crashes every time I open it after the latest update.",
  "reviewDate": "2026-05-28T00:00:00.000Z",
  "reviewCreatedAt": "2026-05-28T00:00:00.000Z",
  "reviewUpdatedAt": null,
  "authorName": "Public reviewer name",
  "authorId": null,
  "authorUrl": null,
  "appVersion": "4.2.1",
  "developerReply": null,
  "developerReplyText": null,
  "developerReplyDate": null,
  "hasDeveloperReply": false,
  "sentiment": "negative",
  "sentimentMethod": "rating_plus_rules",
  "topic": "bug_crash",
  "topicMethod": "rule_based",
  "primaryTopic": "bug_crash",
  "topics": ["bug_crash"],
  "topicScores": { "bug_crash": 2 },
  "isNew": true,
  "sourceUrl": "https://play.google.com/store/apps/details?id=com.example.app&hl=en&gl=US",
  "sourceLanguageFilterApplied": true,
  "languageFilterReliability": "best_effort",
  "storeCategory": null,
  "storeRating": null,
  "storeReviewCount": null,
  "warnings": [],
  "storeData": {}
}
```

If a source does not expose an optional field, the Actor writes `null` or `[]` instead of breaking the schema.

Summary records use `"recordType": "app_summary"`:

```json
{
  "recordType": "app_summary",
  "store": "google_play",
  "appId": "com.example.app",
  "country": "US",
  "language": "en",
  "scrapedAt": "2026-05-29T09:00:00.000Z",
  "stateKey": "weekly-monitoring",
  "reviewsScraped": 100,
  "reviewsOutput": 17,
  "newReviews": 17,
  "newNegativeReviews": 8,
  "reviewCount": 100,
  "averageRating": 3.42,
  "negativeRate": 0.32,
  "sentimentCounts": { "negative": 32, "neutral": 18, "positive": 50 },
  "topTopics": [
    { "topic": "bug_crash", "count": 12, "rate": 0.375 }
  ],
  "developerReplyRate": 0.12,
  "developerReplyCoverage": "available",
  "sourceLanguageFilterApplied": true,
  "languageFilterReliability": "best_effort",
  "sentimentMethod": "rating_plus_rules",
  "topicMethod": "rule_based",
  "appName": null,
  "appUrl": null,
  "developerName": null,
  "storeCategory": null,
  "storeRating": null,
  "storeReviewCount": null,
  "warnings": []
}
```

### User-facing Key-Value Store output

Each run writes a compact `OUTPUT` record in the default Key-Value Store. This is separate from internal incremental state and is safe to use as the run-level summary for integrations.

`OUTPUT` contains `actorName`, `runId`, `status`, `scrapedAt`, `stateKey`, `onlyNewSinceLastRun`, `datasetId`, `keyValueStoreId`, summary counters, `byStore`, `byTarget`, `appSummaries`, `warnings`, and `errors`. Status is `succeeded`, `partial_success`, or `failed`.

If all targets fail, the Actor still writes `OUTPUT` with `status="failed"` and then throws so the Apify run is marked failed.

### Webhook usage

When `webhookUrl` is set, the Actor sends one final JSON POST after dataset records and state are written. The webhook payload includes run metadata, summary counters, app summaries, warnings, errors, and up to 100 new negative reviews with review text truncated to avoid huge payloads.

### Webhook payload example

```json
{
  "actorName": "App & Extension Review Intelligence",
  "runId": "apify-run-id",
  "scrapedAt": "2026-05-29T09:00:00.000Z",
  "stateKey": "weekly-monitoring",
  "onlyNewSinceLastRun": true,
  "summary": {
    "targetsProcessed": 3,
    "targetsSucceeded": 3,
    "targetsFailed": 0,
    "reviewsScraped": 250,
    "reviewsOutput": 42,
    "newReviews": 42,
    "newNegativeReviews": 19
  },
  "appSummaries": [],
  "newNegativeReviews": [],
  "warnings": [],
  "errors": [],
  "datasetId": "default-dataset-id",
  "keyValueStoreId": "default-key-value-store-id"
}
```

Webhook failures are warnings, not scraping failures. If delivery fails after retries, the Actor writes `LAST_WEBHOOK_FAILURE` with `failedAt`, `attempts`, `errorCode`, `errorMessage`, webhook host only, `datasetId`, summary counters, and `newNegativeReviewsCount`. It does not store full webhook payloads, raw HTML, cookies, tokens, or secrets.

### Incremental state / stateKey

`onlyNewSinceLastRun` controls dataset output, not scraping. The Actor still fetches the current public review page, normalizes records, deduplicates within the run, and updates state for successful targets.

State is stored in the Actor's internal named Apify Key-Value Store, separate from the per-run default store that holds `INPUT`, `OUTPUT`, and webhook diagnostics. Internal state entries use:

```text
APP_EXTENSION_REVIEW_INTELLIGENCE_STATE_<safeStateKey>
```

Use different `stateKey` values for independent monitors, for example `weekly-monitoring`, `competitors-us`, or `release-7-2`. Fingerprints are namespaced by `store + appId + country + language`, so the same app can be monitored separately across locales.

### Rule-based sentiment and topic classification

The Actor uses deterministic local rules and does not call external AI, sentiment, enrichment, or topic APIs.

The rule-based sentiment method is `rating_plus_rules`. Ratings `1` and `2` are `negative`, rating `3` is `neutral`, and ratings `4` and `5` are `positive`, with limited keyword rules for borderline cases.

Topic classification runs for negative reviews when `includeTopicClassification=true`. It returns `topic`, `primaryTopic`, `topics`, and `topicScores`. Supported topics are `billing`, `bug_crash`, `login`, `performance`, `support`, `pricing`, `UX`, `feature_request`, `spam`, and `other`.

### Understanding warnings

| Warning code | Meaning |
| --- | --- |
| `APP_STORE_LANGUAGE_FILTER_NOT_GUARANTEED` | Apple App Store public RSS is storefront-based and does not guarantee filtering by requested language. |
| `TOPIC_CLASSIFICATION_ENGLISH_BIASED` | Topic rules use an English keyword dictionary; interpret non-English topic labels cautiously. |
| `NO_PUBLIC_REVIEWS_FOUND` | The public source returned no reviews for the target/filter combination. |
| `PUBLIC_ACCESS_BLOCKED` | The public source returned a login, CAPTCHA, or access-blocked response. |
| `GOOGLE_PLAY_PARSE_FAILED` | Google Play public payload changed or could not be parsed. |
| `APP_STORE_RSS_UNAVAILABLE` | Apple RSS feed failed or returned malformed data. |
| `CHROME_WEB_STORE_LIMITED_REVIEWS` | Chrome reports more public reviews than this run could retrieve from the public endpoint. |
| `CHROME_WEB_STORE_PARSE_FAILED` | Chrome Web Store public payload changed or could not be parsed. |
| `LOW_CONFIDENCE_INCREMENTAL_DEDUPE` | A review lacked stable identifiers, so incremental dedupe used a lower-confidence fallback. |
| `WEBHOOK_DELIVERY_FAILED` | The webhook endpoint timed out or returned an error. |

### Limitations

#### Apple language limitation

Apple App Store public RSS is storefront-based. Requested `language` is preserved in output and target keys, but source-side language filtering is not guaranteed. Apple targets include `sourceLanguageFilterApplied=false`, `languageFilterReliability="not_applied"`, and `APP_STORE_LANGUAGE_FILTER_NOT_GUARANTEED`.

#### Apple developer reply limitation

Apple App Store public RSS does not expose developer replies. Apple summaries use `developerReplyCoverage="not_exposed_by_source"`.

#### Google/Chrome public endpoint limitation

Google Play and Chrome Web Store data comes from public review surfaces. Availability, ordering, pagination depth, historical coverage, developer replies, and metadata can change by store, country, language, app, and time. Public endpoint limitations are reported as warnings when possible.

#### Store limitations and troubleshooting

| Symptom | Likely reason | What to check |
| --- | --- | --- |
| No records for a target | No public reviews were returned for that country/language/filter | Try another country/language or broader ratings. |
| Partial success | One target failed while others succeeded | Check `OUTPUT.byTarget` and warnings/errors. |
| All-target failure | Every target failed validation or fetching | Check run logs and failed `OUTPUT`. |
| Apple language warning | Apple RSS language filtering is not guaranteed | Treat Apple language as target context. |
| Webhook warning | Endpoint rejected or timed out | Verify the endpoint accepts JSON POST and returns HTTP 2xx. |

### Pricing note

For Store V1, keep pricing simple. A good draft is a small Actor start event plus a low per-result price based on default dataset items. Because this Actor writes both `review` and `app_summary` records to the default dataset, default dataset item pricing charges both record types unless configured otherwise in Apify Console. Do not apply pricing automatically without final owner approval.

### Public data only

This Actor works with public review sources only. It does not request credentials, does not read private dashboards, does not use cookies, and does not bypass CAPTCHA or access controls. Use it in a way that respects source terms and local laws.

### Development

- `npm run build` compiles TypeScript to `dist/`.
- `npm start` runs the compiled Actor entrypoint.
- `npm run dev` runs the TypeScript entrypoint with `tsx`.
- `npm run smoke:live` runs a small real-source smoke against one target per supported store and writes `storage/smoke-live-report.json`.
- `npm test` runs the Vitest suite once.
- `npm run test:watch` runs Vitest in watch mode.

# Actor input Schema

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

Apps or browser extensions to scrape from public review surfaces. Each item needs a store and store-specific ID.

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

Maximum public reviews to collect per concrete app-country-language target. Defaults to 100 when omitted.

## `maxReviewsPerTarget` (type: `integer`):

Alias for maxReviewsPerApp. Use this when thinking in concrete targets: store + appId + country + language.

## `ratings` (type: `array`):

Ratings to keep after normalization. Use this to focus output on low-rated or high-rated reviews.

## `languages` (type: `array`):

Default language codes used when a target does not specify a language.

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

Default country or storefront codes used when a target does not specify a country.

## `onlyNewSinceLastRun` (type: `boolean`):

Push only review records whose fingerprints are not already present in the persisted state for this stateKey.

## `stateKey` (type: `string`):

Namespace for incremental state in the Actor's internal named Key-Value Store. Use different keys for independent monitoring streams.

## `includeTopicClassification` (type: `boolean`):

Classify negative reviews into local rule-based topics such as bug\_crash, billing, login, and performance.

## `includeAppSummary` (type: `boolean`):

Push app\_summary records with rating, sentiment, topic, new-review, and developer-reply metrics.

## `webhookUrl` (type: `string`):

Optional HTTP(S) endpoint that receives one final JSON payload with summaries and new negative reviews.

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

Standard Apify proxy configuration used for store HTTP requests when a proxy URL is available.

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

Maximum number of store targets fetched in parallel.

## `requestTimeoutSecs` (type: `integer`):

Timeout for public HTTP store requests.

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

Maximum retry attempts for transient store request failures.

## `maxTargets` (type: `integer`):

Safety limit for expanded concrete targets after applying countries and languages.

## `maxTotalReviews` (type: `integer`):

Safety limit for expandedTargets.length multiplied by the resolved per-target review limit.

## Actor input object example

```json
{
  "apps": [
    {
      "store": "google_play",
      "id": "com.spotify.music",
      "country": "US",
      "language": "en"
    }
  ],
  "ratings": [
    1,
    2,
    3,
    4,
    5
  ],
  "languages": [
    "en"
  ],
  "countries": [
    "US"
  ],
  "onlyNewSinceLastRun": false,
  "stateKey": "default",
  "includeTopicClassification": true,
  "includeAppSummary": true,
  "webhookUrl": "",
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 3,
  "requestTimeoutSecs": 30,
  "maxRequestRetries": 3,
  "maxTargets": 100,
  "maxTotalReviews": 10000
}
```

# 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": "google_play",
            "id": "com.spotify.music",
            "country": "US",
            "language": "en"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scraping_scrap/app-extension-review-intelligence").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": "google_play",
            "id": "com.spotify.music",
            "country": "US",
            "language": "en",
        }] }

# Run the Actor and wait for it to finish
run = client.actor("scraping_scrap/app-extension-review-intelligence").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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": "google_play",
      "id": "com.spotify.music",
      "country": "US",
      "language": "en"
    }
  ]
}' |
apify call scraping_scrap/app-extension-review-intelligence --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=scraping_scrap/app-extension-review-intelligence",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "App & Extension Review Intelligence",
        "description": "Monitor public reviews from Google Play, Apple App Store, and Chrome Web Store. Extract normalized review records, detect new reviews, classify sentiment/topics, create app summaries, and send webhook alerts.",
        "version": "0.1",
        "x-build-id": "p1ZKZFBAbs14SjRDm"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/scraping_scrap~app-extension-review-intelligence/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-scraping_scrap-app-extension-review-intelligence",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/scraping_scrap~app-extension-review-intelligence/runs": {
            "post": {
                "operationId": "runs-sync-scraping_scrap-app-extension-review-intelligence",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/scraping_scrap~app-extension-review-intelligence/run-sync": {
            "post": {
                "operationId": "run-sync-scraping_scrap-app-extension-review-intelligence",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "apps"
                ],
                "properties": {
                    "apps": {
                        "title": "Apps and extensions",
                        "minItems": 1,
                        "type": "array",
                        "description": "Apps or browser extensions to scrape from public review surfaces. Each item needs a store and store-specific ID.",
                        "items": {
                            "type": "object",
                            "required": [
                                "store",
                                "id"
                            ],
                            "properties": {
                                "store": {
                                    "title": "Store",
                                    "type": "string",
                                    "description": "Review source for this target.",
                                    "enum": [
                                        "google_play",
                                        "app_store",
                                        "chrome_web_store"
                                    ]
                                },
                                "id": {
                                    "title": "App or extension ID",
                                    "type": "string",
                                    "description": "Google Play package name, Apple numeric app ID, or Chrome extension ID.",
                                    "minLength": 1,
                                    "maxLength": 256
                                },
                                "country": {
                                    "title": "Country",
                                    "type": "string",
                                    "description": "Optional per-target country or storefront override, for example US or GB."
                                },
                                "language": {
                                    "title": "Language",
                                    "type": "string",
                                    "description": "Optional per-target language override, for example en or pt-BR. If omitted, global languages are used."
                                }
                            }
                        }
                    },
                    "maxReviewsPerApp": {
                        "title": "Maximum reviews per app",
                        "minimum": 1,
                        "maximum": 5000,
                        "type": "integer",
                        "description": "Maximum public reviews to collect per concrete app-country-language target. Defaults to 100 when omitted."
                    },
                    "maxReviewsPerTarget": {
                        "title": "Maximum reviews per target",
                        "minimum": 1,
                        "maximum": 5000,
                        "type": "integer",
                        "description": "Alias for maxReviewsPerApp. Use this when thinking in concrete targets: store + appId + country + language."
                    },
                    "ratings": {
                        "title": "Ratings",
                        "type": "array",
                        "description": "Ratings to keep after normalization. Use this to focus output on low-rated or high-rated reviews.",
                        "items": {
                            "type": "integer"
                        },
                        "default": [
                            1,
                            2,
                            3,
                            4,
                            5
                        ]
                    },
                    "languages": {
                        "title": "Languages",
                        "minItems": 1,
                        "maxItems": 50,
                        "type": "array",
                        "description": "Default language codes used when a target does not specify a language.",
                        "items": {
                            "type": "string"
                        },
                        "default": [
                            "en"
                        ]
                    },
                    "countries": {
                        "title": "Countries",
                        "minItems": 1,
                        "maxItems": 50,
                        "type": "array",
                        "description": "Default country or storefront codes used when a target does not specify a country.",
                        "items": {
                            "type": "string"
                        },
                        "default": [
                            "US"
                        ]
                    },
                    "onlyNewSinceLastRun": {
                        "title": "Only new since last run",
                        "type": "boolean",
                        "description": "Push only review records whose fingerprints are not already present in the persisted state for this stateKey.",
                        "default": false
                    },
                    "stateKey": {
                        "title": "State key",
                        "pattern": ".*[A-Za-z0-9_-].*",
                        "minLength": 1,
                        "maxLength": 128,
                        "type": "string",
                        "description": "Namespace for incremental state in the Actor's internal named Key-Value Store. Use different keys for independent monitoring streams.",
                        "default": "default"
                    },
                    "includeTopicClassification": {
                        "title": "Include topic classification",
                        "type": "boolean",
                        "description": "Classify negative reviews into local rule-based topics such as bug_crash, billing, login, and performance.",
                        "default": true
                    },
                    "includeAppSummary": {
                        "title": "Include app summary",
                        "type": "boolean",
                        "description": "Push app_summary records with rating, sentiment, topic, new-review, and developer-reply metrics.",
                        "default": true
                    },
                    "webhookUrl": {
                        "title": "Webhook URL",
                        "pattern": "^(|https?://[^\\s]+)$",
                        "type": "string",
                        "description": "Optional HTTP(S) endpoint that receives one final JSON payload with summaries and new negative reviews.",
                        "default": ""
                    },
                    "proxyConfiguration": {
                        "title": "Proxy configuration",
                        "type": "object",
                        "description": "Standard Apify proxy configuration used for store HTTP requests when a proxy URL is available.",
                        "default": {
                            "useApifyProxy": true
                        }
                    },
                    "maxConcurrency": {
                        "title": "Maximum concurrency",
                        "minimum": 1,
                        "maximum": 25,
                        "type": "integer",
                        "description": "Maximum number of store targets fetched in parallel.",
                        "default": 3
                    },
                    "requestTimeoutSecs": {
                        "title": "Request timeout seconds",
                        "minimum": 1,
                        "maximum": 300,
                        "type": "integer",
                        "description": "Timeout for public HTTP store requests.",
                        "default": 30
                    },
                    "maxRequestRetries": {
                        "title": "Maximum request retries",
                        "minimum": 0,
                        "maximum": 10,
                        "type": "integer",
                        "description": "Maximum retry attempts for transient store request failures.",
                        "default": 3
                    },
                    "maxTargets": {
                        "title": "Maximum expanded targets",
                        "minimum": 1,
                        "maximum": 10000,
                        "type": "integer",
                        "description": "Safety limit for expanded concrete targets after applying countries and languages.",
                        "default": 100
                    },
                    "maxTotalReviews": {
                        "title": "Maximum total review budget",
                        "minimum": 1,
                        "maximum": 1000000,
                        "type": "integer",
                        "description": "Safety limit for expandedTargets.length multiplied by the resolved per-target review limit.",
                        "default": 10000
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
