# 🍎 App Store Reviews Scraper (`skootle/app-store-reviews`) Actor

Scrape Apple App Store reviews + app metadata across countries and apps. Rating, title, body, version, helpful votes, author. Watchlist mode emits only new reviews. Joins each review with app metadata. Export, run via API, schedule, or integrate with other tools.

- **URL**: https://apify.com/skootle/app-store-reviews.md
- **Developed by:** [Skootle](https://apify.com/skootle) (community)
- **Categories:** Marketing, AI, Lead generation
- **Stats:** 6 total users, 4 monthly users, 100.0% runs succeeded, NaN bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 app store review records

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/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

![Apple App Store Reviews hero](https://raw.githubusercontent.com/kesjam/skootle-actors-assets/main/heroes/app-store-reviews.png)

### TL;DR

Monitor Apple App Store reviews + app metadata across countries and apps. Returns clean structured JSON: rating, title, body, version reviewed, helpful votes, author, ISO 8601 dates. Each review is joined with a fresh app metadata snapshot (current rating, version, price, primary genre, content rating, release dates). Watchlist mode emits only NEW reviews since the previous run, so a daily 1-2-star feed is a clean negative-review triage queue. Built on Apple's public iTunes RSS + Lookup APIs. Zero authentication, zero anti-bot.

---

<!-- skootle:review-cta -->
> Try it on a small dataset, then let us know what you think in a [review](https://apify.com/skootle/app-store-reviews/reviews).

---

### What does Apple App Store Reviews Monitor do?

Apple App Store Reviews Monitor extracts reviews and app metadata from the Apple App Store. You give it a list of Apple track IDs (the number after `/id` in any App Store URL) and a list of country codes; it returns one record per review with: rating, title, body, reviewed version, helpful-vote sum/count, author name, ISO 8601 submitted timestamp, plus a joined snapshot of current app metadata (current rating, current version, release date, price, primary genre, content rating).

By default the actor also emits one `app_store_app` metadata record per app per country with the current snapshot (rating, version, price, etc.). Disable with `emitAppMetadataRecord: false`.

Watchlist mode (`watchlistMode: true`) makes this scraper schedulable. State persists across runs in the actor's key-value store, so a daily cron only emits reviews NEW since the last run.

### Why scrape Apple App Store reviews?

App reviews are the fastest moat-signal for any mobile app team. One bad release can crater your rating and conversion overnight, and a competitor's rating dip is your wedge. Daily 1-star triage and weekly competitor sentiment-shift reports are the workflows that pay back the most for mobile-marketing and ASO teams.

Track competitor reviews across 5 country App Stores in one query, catch a version regression the day it ships, harvest 5-star praise for marketing copy, and feed clean labeled review text into sentiment classifiers. Replaces the manual ritual of opening App Store country pages tab by tab.

### Who needs this?

- **iOS app developers** monitoring their own apps for new 1-star reviews and version-regression signals
- **ASO consultants** benchmarking competitor reviews + ratings + version-release cadence
- **App studios with portfolios** tracking sentiment across many apps from a single watchlist
- **Localization QA teams** comparing rating distributions across countries (`countryCodes: ["us", "jp", "de", "br", "in"]`)
- **Mobile-marketing teams** measuring sentiment-velocity post-release
- **AI / LLM teams** training customer-feedback classifiers on labeled review data
- **Competitive intelligence** monitoring the top 10 competitor apps in your category
- **AI agents** routing new negative reviews to support channels with auto-classification

### How to use Apple App Store Reviews Monitor

1. Open the **Input** tab on the actor page
2. Add Apple track IDs (e.g., `284882215` for Facebook) to the `appIds` field. Find them in any App Store URL: `apps.apple.com/us/app/<name>/id<TRACKID>`.
3. Add country codes to `countryCodes` (e.g., `["us", "gb", "jp"]`)
4. Optionally choose `sortBy`: `mostrecent` (default) or `mosthelpful`
5. Set `reviewPagesPerApp` (default 2; ~50 reviews per page; max 10 pages)
6. Optionally filter by `minRating` and `maxRating` (e.g., `minRating: 1, maxRating: 2` for negative-only triage)
7. Optionally enable `watchlistMode` for daily diffs
8. Set `maxItems` (default 50)
9. Click **Start**

### How much will scraping App Store reviews cost?

This actor is priced per event:

- **Actor Start**: $0.01 once per run
- **App Store record (review or app metadata)**: tiered, charged per record written

| Apify plan | $/1000 records |
|---|---|
| FREE | $20.00 |
| BRONZE | $17.00 |
| SILVER | $15.00 |
| GOLD | $12.00 |
| PLATINUM | $12.00 |
| DIAMOND | $10.00 |

A typical daily watchlist on 5 apps with 2 review pages per app per country returns ~50 reviews on day 1, then 0-20 per day in watchlist mode. Roughly $0.30-$0.70 per daily run on GOLD.

### Is it legal to scrape App Store reviews?

Yes. The iTunes RSS feed at `itunes.apple.com/<country>/rss/customerreviews/...` and the Lookup API at `itunes.apple.com/lookup` are explicitly published as free public APIs by Apple, with no authentication required and no rate-limit block on commercial use.

Reviews are public, anyone visiting the App Store can read them. The data scraped (rating, title, body, author display name, version reviewed, helpful votes) is published by Apple as part of the App Store user experience. Use freely for research, AI training, ASO analytics, and competitive monitoring.

### Examples

#### Example 1: Daily negative-review triage on your app

```json
{
  "appIds": ["284882215"],
  "countryCodes": ["us"],
  "sortBy": "mostrecent",
  "reviewPagesPerApp": 2,
  "minRating": 1,
  "maxRating": 2,
  "watchlistMode": true,
  "maxItems": 100
}
````

#### Example 2: 5-star praise harvest for marketing

```json
{
  "appIds": ["284882215"],
  "countryCodes": ["us"],
  "sortBy": "mosthelpful",
  "reviewPagesPerApp": 5,
  "minRating": 5,
  "maxRating": 5,
  "maxItems": 250
}
```

#### Example 3: Multi-country localization audit

```json
{
  "appIds": ["284882215"],
  "countryCodes": ["us", "gb", "jp", "de", "br", "in", "fr", "es"],
  "reviewPagesPerApp": 2,
  "watchlistMode": true,
  "maxItems": 800
}
```

Compare rating distributions per country to spot localization issues.

#### Example 4: Competitor watchlist (top 5 in category)

```json
{
  "appIds": ["284882215", "389801252", "835599320", "544007664", "324684580"],
  "countryCodes": ["us"],
  "watchlistMode": true,
  "reviewPagesPerApp": 2,
  "maxItems": 500
}
```

#### Example 5: Version-regression detector

```json
{
  "appIds": ["284882215"],
  "countryCodes": ["us"],
  "sortBy": "mostrecent",
  "reviewPagesPerApp": 5,
  "watchlistMode": true,
  "maxItems": 250
}
```

After a release, filter the output downstream for `reviewedVersion: <new>` and chart rating distribution to detect regressions.

#### Example 6: AI training corpus for sentiment classification

```json
{
  "appIds": ["284882215", "389801252", "835599320", "544007664", "324684580", "447188370", "414478124"],
  "countryCodes": ["us"],
  "reviewPagesPerApp": 10,
  "maxItems": 5000
}
```

#### Example 7: ASO-keyword-mining base feed

```json
{
  "appIds": ["284882215"],
  "countryCodes": ["us"],
  "sortBy": "mosthelpful",
  "reviewPagesPerApp": 10,
  "maxItems": 500
}
```

Pipe through a keyword-frequency analyzer to find what users actually call your app's features (often different from your marketing copy).

#### Example 8: App-metadata-only snapshot

```json
{
  "appIds": ["284882215"],
  "countryCodes": ["us"],
  "reviewPagesPerApp": 0,
  "emitAppMetadataRecord": true,
  "maxItems": 1
}
```

Returns one `app_store_app` record with the current rating, version, price, etc. Zero reviews fetched.

### Input parameters

| Field | Type | Default | Description |
|---|---|---|---|
| `appIds` | string\[] | `["284882215"]` | Apple track IDs (numbers from App Store URLs) |
| `countryCodes` | string\[] | `["us"]` | 2-letter store country codes |
| `sortBy` | enum | `mostrecent` | `mostrecent` or `mosthelpful` |
| `reviewPagesPerApp` | int | `2` | ~50 per page, max 10 pages |
| `minRating` | int | `1` | Filter (set 4 for positive only) |
| `maxRating` | int | `5` | Filter (set 2 for negative-only triage) |
| `emitAppMetadataRecord` | bool | `true` | Also emit `app_store_app` per app per country |
| `watchlistMode` | bool | `false` | Idempotent diff against KV-stored seen review IDs |
| `maxItems` | int | `50` | Hard cap on records |

### App Store output format

The dataset has two record types. Filter by `recordType`.

#### `app_store_review`

| Field | Type | Description |
|---|---|---|
| `outputSchemaVersion`, `recordType`, `recordId` | string | Discriminated identity (`apple:review:<id>`) |
| `reviewId`, `url` | string | Apple review ID + canonical URL |
| `appId`, `countryCodes`, `sortBy`, `page`, `rank` | int / string / enum / int | Source context |
| `rating` | int 1-5 | Star rating |
| `title`, `body` | string | Review headline + body |
| `reviewedVersion` | string | App version the user reviewed |
| `voteSum`, `voteCount` | int | "Found this helpful" votes |
| `authorName`, `authorReviewsUrl` | string | Author display name + their reviews page |
| `createdAt`, `scrapedAt` | ISO 8601 | Submitted + snapshot times |
| `app` | object | Joined app metadata snapshot (see `app_store_app` below) |
| `fieldCompletenessScore`, `agentMarkdown` | int / string | Quality + LLM-ready summary |

#### `app_store_app`

| Field | Type | Description |
|---|---|---|
| `outputSchemaVersion`, `recordType`, `recordId` | string | Discriminated identity (`apple:app:<id>:<country>`) |
| `appId`, `countryCode` | int / string | Store identity |
| `app` | object | `trackId`, `trackName`, `bundleId`, `sellerName`, `primaryGenre`, `contentRating`, `averageUserRating`, `userRatingCount`, `averageUserRatingCurrentVersion`, `userRatingCountCurrentVersion`, `currentVersion`, `currentVersionReleaseDate`, `releaseDate`, `priceUsd`, `fileSizeBytes`, `trackViewUrl`, `artworkUrl` |
| `scrapedAt` | ISO 8601 | Snapshot timestamp |
| `fieldCompletenessScore`, `agentMarkdown` | int / string | Quality + LLM-ready summary |

#### Apple App Store Reviews scraper output example (review)

```json
{
  "outputSchemaVersion": "2026-05-09",
  "recordType": "app_store_review",
  "recordId": "apple:review:1919375575",
  "reviewId": "1919375575",
  "appId": 284882215,
  "countryCode": "us",
  "rating": 1,
  "title": "Issue unresolved",
  "body": "App keeps crashing on launch since v560.1.",
  "reviewedVersion": "560.1",
  "authorName": "JaneFromBrooklyn",
  "voteSum": 12,
  "voteCount": 14,
  "createdAt": "2026-05-07T18:18:45-07:00",
  "app": {
    "trackName": "Facebook",
    "averageUserRating": 4.52,
    "userRatingCount": 26185776,
    "currentVersion": "560.1"
  },
  "fieldCompletenessScore": 100,
  "agentMarkdown": "**⭐ Facebook · \"Issue unresolved\"**\n> App keeps crashing on launch since v560.1.\n- by JaneFromBrooklyn · v560.1 · 📅 2026-05-07 · 🌐 US\n- 👍 12/14 found helpful\n- 🔗 https://itunes.apple.com/us/reviews/id1919375575"
}
```

### During the Actor run

The actor pulls reviews and current app metadata from Apple's public iTunes endpoints with respectful pacing (no API key required, no proxy needed). Alongside the dataset, three artifacts land in the actor's key-value store: **`OUTPUT`** (run summary), **`AGENT_BRIEFING`** (rating mix plus the top 10 negative reviews to triage), and **`WATCHLIST_STATE`** (seen review IDs, when `watchlistMode: true`).

### FAQ

#### Where do I find an Apple track ID?

The number after `/id` in any App Store URL: `apps.apple.com/us/app/facebook/id284882215` → `284882215`.

#### Can I scrape reviews from multiple countries?

Yes. Pass an array to `countryCodes`. The actor queries each country separately and emits records tagged with the country.

#### Can I monitor only new reviews?

Yes. Set `watchlistMode: true`. The first run captures everything; subsequent runs only emit reviews new since the previous run.

#### Can I filter by rating?

Yes. `minRating` and `maxRating`. Common patterns: `1, 2` for negative-only triage; `4, 5` for positive-only marketing harvest.

#### Does this work for Google Play?

No, this actor is iOS App Store only. Google Play uses a different platform and a different API.

#### Can I get the developer's response to reviews?

Apple's iTunes RSS feed does not include developer responses. This is a platform limitation, not an actor feature gap.

#### How fresh is the data?

Apple updates the RSS feed within ~minutes of new reviews. Watchlist mode + a 30-minute schedule gives you near-real-time alerting.

#### Can I use this with the Apify API?

Yes. POST to `https://api.apify.com/v2/acts/skootle~app-store-reviews/runs`.

#### Can I integrate with Make / Zapier / n8n / Slack?

Yes. Click **Integrations** on the actor page.

#### Why does this actor cost more than a free reviews scraper?

This actor ships joined app metadata per review, watchlist diff mode, versioned schema, idempotent IDs, multi-country support in one run, agent-ready markdown summaries, and a daily-digest briefing. If you're feeding it into a triage queue or AI pipeline, the per-record cost pays back in saved engineering time.

#### Your feedback

Hit a bug or want a feature? Open an issue on the [Issues tab](https://apify.com/skootle/app-store-reviews/issues/open) rather than the reviews page, and we'll fix it fast (typically within 48 hours).

### Why choose Apple App Store Reviews Monitor

- **Monitor mode emits only what's new since last run**, so a daily cron is a clean negative-review triage queue instead of yesterday's noise
- **Multi-country in one run**, query US + GB + JP + DE + BR + IN simultaneously instead of opening the App Store country-by-country
- **Every review carries the current app snapshot**, rating, version, price, so downstream analytics and AI sentiment pipelines have both data points in one record
- **Reliability free actors can't deliver**, free scrapers break monthly when Apple shifts a tag. We auto-test daily and ship a fix within 24-48 hours
- **No proxy, no auth, no anti-bot**, built on Apple's official public iTunes APIs, so runs are cheap and predictable
- **Agent-ready markdown** per record drops straight into an LLM context window
- **Re-runs are safe to dedupe by ID**, stable `apple:review:<id>` and `apple:app:<id>:<country>` keys
- **Schema doesn't break your pipeline**, versioned and bumped on breaking change
- **AI agents can self-filter sparse rows** via `fieldCompletenessScore` per record

### Other Skootle actors you might want to check

- **[Shopify App Store Scraper](https://apify.com/skootle/shopify-app-store-scraper)**, Shopify app listings + pricing tiers
- **[Reddit Subreddit Scraper](https://apify.com/skootle/reddit-subreddit-monitor)**, sentiment + brand monitoring
- **[Hacker News Watchlist](https://apify.com/skootle/hackernews-watchlist)**, tech discourse stream
- **[GitHub Trending Repos](https://apify.com/skootle/github-trending)**, daily trending dev repos
- **[SEC EDGAR Filings Monitor](https://apify.com/skootle/sec-edgar-filings)**, public-company filings stream

### Support and contact

File issues on this actor's page, replies within 48 hours. Feature requests welcome, tag with `enhancement`.

# Actor input Schema

## `appIds` (type: `array`):

Numeric track IDs (e.g., 284882215 for Facebook). Find by searching the App Store URL: apps.apple.com/us/app/<name>/id<TRACKID>.

## `countryCodes` (type: `array`):

Two-letter store country codes (us, gb, jp, de, br, ...). Each country queried separately.

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

How to sort reviews from the App Store RSS feed.

## `reviewPagesPerApp` (type: `integer`):

Apple RSS returns ~50 reviews per page, max ~10 pages.

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

Filter (e.g. set to 4 for positive-only digest).

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

Filter (e.g. set to 2 for negative-only triage).

## `emitAppMetadataRecord` (type: `boolean`):

When true, also push a separate metadata record (averageUserRating, version, price, etc.) per app per country.

## `watchlistMode` (type: `boolean`):

When true, only emit reviews NEW since the previous run. State persists in the key-value store.

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

Hard cap on records (apps + reviews). Conservative default for the 5-minute auto-test.

## Actor input object example

```json
{
  "appIds": [
    "284882215"
  ],
  "countryCodes": [
    "us"
  ],
  "sortBy": "mostrecent",
  "reviewPagesPerApp": 2,
  "minRating": 1,
  "maxRating": 5,
  "emitAppMetadataRecord": true,
  "watchlistMode": false,
  "maxItems": 50
}
```

# Actor output Schema

## `datasetItems` (type: `string`):

No description

## `agentBriefing` (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 = {
    "appIds": [
        "284882215"
    ],
    "countryCodes": [
        "us"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("skootle/app-store-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 = {
    "appIds": ["284882215"],
    "countryCodes": ["us"],
}

# Run the Actor and wait for it to finish
run = client.actor("skootle/app-store-reviews").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 '{
  "appIds": [
    "284882215"
  ],
  "countryCodes": [
    "us"
  ]
}' |
apify call skootle/app-store-reviews --silent --output-dataset

```

## MCP server setup

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

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "🍎 App Store Reviews Scraper",
        "description": "Scrape Apple App Store reviews + app metadata across countries and apps. Rating, title, body, version, helpful votes, author. Watchlist mode emits only new reviews. Joins each review with app metadata. Export, run via API, schedule, or integrate with other tools.",
        "version": "0.1",
        "x-build-id": "PCu0OYwVKxDK9X1ex"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/skootle~app-store-reviews/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-skootle-app-store-reviews",
                "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/skootle~app-store-reviews/runs": {
            "post": {
                "operationId": "runs-sync-skootle-app-store-reviews",
                "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/skootle~app-store-reviews/run-sync": {
            "post": {
                "operationId": "run-sync-skootle-app-store-reviews",
                "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",
                "properties": {
                    "appIds": {
                        "title": "Apple App Store track IDs",
                        "type": "array",
                        "description": "Numeric track IDs (e.g., 284882215 for Facebook). Find by searching the App Store URL: apps.apple.com/us/app/<name>/id<TRACKID>.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "countryCodes": {
                        "title": "Country codes",
                        "type": "array",
                        "description": "Two-letter store country codes (us, gb, jp, de, br, ...). Each country queried separately.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "sortBy": {
                        "title": "Review sort order",
                        "enum": [
                            "mostrecent",
                            "mosthelpful"
                        ],
                        "type": "string",
                        "description": "How to sort reviews from the App Store RSS feed.",
                        "default": "mostrecent"
                    },
                    "reviewPagesPerApp": {
                        "title": "Review pages per app (~50 reviews/page)",
                        "minimum": 0,
                        "maximum": 10,
                        "type": "integer",
                        "description": "Apple RSS returns ~50 reviews per page, max ~10 pages.",
                        "default": 2
                    },
                    "minRating": {
                        "title": "Minimum rating to include",
                        "minimum": 1,
                        "maximum": 5,
                        "type": "integer",
                        "description": "Filter (e.g. set to 4 for positive-only digest).",
                        "default": 1
                    },
                    "maxRating": {
                        "title": "Maximum rating to include",
                        "minimum": 1,
                        "maximum": 5,
                        "type": "integer",
                        "description": "Filter (e.g. set to 2 for negative-only triage).",
                        "default": 5
                    },
                    "emitAppMetadataRecord": {
                        "title": "Emit one app_store_app record per app+country",
                        "type": "boolean",
                        "description": "When true, also push a separate metadata record (averageUserRating, version, price, etc.) per app per country.",
                        "default": true
                    },
                    "watchlistMode": {
                        "title": "Watchlist mode (idempotent diff)",
                        "type": "boolean",
                        "description": "When true, only emit reviews NEW since the previous run. State persists in the key-value store.",
                        "default": false
                    },
                    "maxItems": {
                        "title": "Max total records",
                        "minimum": 1,
                        "maximum": 20000,
                        "type": "integer",
                        "description": "Hard cap on records (apps + reviews). Conservative default for the 5-minute auto-test.",
                        "default": 50
                    }
                }
            },
            "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
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
