# Apple App Store Reviews Scraper: iOS App Reviews, No Login (`hyperbach/app-store-reviews-scraper`) Actor

Scrape Apple App Store reviews by app URL or id and country: rating, title, text, app version, reviewer nickname, date, helpful votes and review id, newest first or most helpful. Several apps and countries per run, a cap per app, a free app row with its rating and a completeness verdict.

- **URL**: https://apify.com/hyperbach/app-store-reviews-scraper.md
- **Developed by:** [Hyperbach](https://apify.com/hyperbach) (community)
- **Categories:** Lead generation, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.08 / 1,000 reviews

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

## Apple App Store Reviews Scraper: iOS App Reviews, No Login

**Apple App Store reviews by app and country, logged out: App Store URLs or app ids in, one row per review out — the star rating, title, text, the app version the reviewer used, the reviewer's nickname and profile link, the date, the helpful votes and the review id — newest first or most helpful first, several apps and countries in one run, a cap per app.** One free `app` row per app and country carries the app's name, developer, average rating and rating count, current version and store link, and a completeness verdict: COMPLETE (the feed's own end), TRUNCATED (Apple's 500-review limit per app, country and order), CAPPED (your limit) or PARTIAL. An unknown app, a country with no storefront, an app with no written review: free typed rows. No login, no Apple account, no API key, proxies handled for you.

### Why this App Store reviews scraper

- **Several apps and countries in one run.** Give App Store URLs or ids and a list of countries: every app is read in every storefront (reviews are per storefront on the App Store), each pair with its own cap (`maxReviewsPerApp`), and the run's cap on top (`maxItems`). A URL alone brings its own country.
- **Newest first or most helpful.** The two orders Apple's public reviews feed serves: `mostRecent` and `mostHelpful` (the first pages of the two shared no review on our test). Each row carries its `rank` in the order read.
- **The feed's own record, every field.** The star rating, title and text, the app version the review was written on, the reviewer's nickname and profile link, the date (UTC), the helpful votes (`vote_sum`, `vote_count`) and Apple's review id; every row carries the app id, the country and the input it answers.
- **A verdict per app and country.** Apple's public feed serves up to 500 reviews per app, country and order (10 pages of 50; page 11 answers "page depth is limited to 10", measured 2026-10-22). The free `app` row says how many reviews were delivered from how many pages and why the walk stopped: the feed's own end (COMPLETE), Apple's 500 limit (TRUNCATED), your cap (CAPPED) or a refusal (PARTIAL). For more reviews of a big app, read it in more countries or in both orders.
- **The app's record, free.** The `app` row carries the app's name, developer, bundle id, genre, price, average rating and rating count in that storefront, current version, store link and icon, from Apple's lookup.
- **Honest runs.** An id Apple's storefront does not list is a free `not_found` row (checked twice), a country code with no storefront a free `invalid` row, an app with no written review a free `empty` row, a repeated app a free `duplicate` row. Every input gets a row; a review is charged once a run.
- **Fast and light.** Plain HTTP on Apple's public JSON: 50 reviews a call, about 8 KB on the wire for the newest 50. A run of 2 apps × 60 reviews took 3 seconds of reading on our test.

### Who it's for

- **App developers and product teams** — the newest reviews of your app in every market you sell in, with the app version per review to see what a release changed.
- **ASO and market researchers** — the reviews and ratings of a list of competitor apps across countries in one run, with the app's rating count per storefront on the free app row.
- **Data and analytics teams** — typed rows keyed by `app_id`, `country` and `review_id`, a free row per app and country with the status and verdict, a run summary to check a pipeline against.

### Quick start

**The newest reviews of two apps (the Instagram URL and the TikTok id)**

```json
{
  "appIds": [
    "https://apps.apple.com/us/app/instagram/id389801252",
    "835599320"
  ],
  "maxReviewsPerApp": 50
}
```

**One app in five countries, up to 500 reviews each**

```json
{
  "appIds": [
    "389801252"
  ],
  "countries": [
    "us",
    "gb",
    "ca",
    "au",
    "de"
  ]
}
```

**The most helpful reviews of an app**

```json
{
  "appIds": [
    "https://apps.apple.com/us/app/tiktok/id835599320"
  ],
  "sort": "mostHelpful",
  "maxReviewsPerApp": 100
}
```

### Output

One row per review (and one free app row per app and country):

| field | meaning |
|---|---|
| `row_type` | `review` (one review, charged) or `app` (the app's record in one storefront with the walk's verdict; free). |
| `input_index` | The 1-based position in `appIds` of the input this row answers. |
| `input_value` | The input as you gave it (the URL or id). |
| `app_id` | Apple's numeric app id. |
| `country` | The storefront's two-letter country code (`us`, `gb` …). |
| `sort` | The order read: `mostRecent` or `mostHelpful`. |
| `status` | An app row: `ok`, `empty`, `not_found`, `invalid`, `duplicate`, `failed` or `not_read` (see the FAQ). A review row: `ok`. |
| `status_detail` | Why, in words, when the status is not `ok`. |
| `scraped_at` | When the row was read (ISO 8601, UTC). |
| `review_id` | Apple's review id. |
| `rating` | The star rating, 1 to 5. |
| `title` | The review's title. |
| `text` | The review's text. |
| `app_version` | The app version the review was written on. |
| `reviewer_name` | The reviewer's App Store nickname, as Apple shows it. |
| `reviewer_url` | The reviewer's public App Store profile link. |
| `published_at` | When the review was written or last edited (ISO 8601, UTC). |
| `vote_sum` | Helpful votes the review got (Apple's vote sum). |
| `vote_count` | Votes cast on the review, helpful or not. |
| `url` | Apple's review link for the app in that storefront. |
| `rank` | The review's position in the order read (1 to 500). |
| `app_name` | The app's name in that storefront (on review rows too). |
| `developer` | The developer as the App Store names it (app row). |
| `bundle_id` | The app's bundle id (app row). |
| `genre` | The app's primary genre (app row). |
| `price` | The app's price as the storefront shows it (`Free`, `$2.99`; app row). |
| `average_rating` | The app's average star rating in that storefront (app row). |
| `rating_count` | How many ratings the app has in that storefront, with or without a written review (app row). |
| `current_version` | The app's current version (app row). |
| `app_url` | The app's App Store link in that storefront (app row). |
| `icon_url` | The app's icon (app row). |
| `reviews_delivered` | Review rows delivered for this app and country (app row). |
| `pages_read` | Feed pages read for this app and country (app row). |
| `verdict` | COMPLETE (the feed's own end), TRUNCATED (Apple's 500 limit), CAPPED (your limit) or PARTIAL (a refusal or the time limit) (app row). |
| `verdict_detail` | The verdict in words, with the numbers (app row). |

Example record:

```json
{
  "row_type": "review",
  "input_index": 1,
  "input_value": "https://apps.apple.com/gb/app/instagram/id389801252",
  "app_id": "389801252",
  "country": "gb",
  "sort": "mostRecent",
  "status": "ok",
  "status_detail": null,
  "scraped_at": "2026-10-22T13:45:36Z",
  "review_id": "14691542951",
  "rating": 1,
  "title": "Unfair bans/ no human support",
  "text": "Appalling, and there Ai system is going out of order and banning so many accounts including mine, with no human support that the only way is to go through legal action.",
  "app_version": "452.0.0",
  "reviewer_name": "nickname-redacted",
  "reviewer_url": "https://itunes.apple.com/gb/reviews/id900000001",
  "published_at": "2026-10-21T02:00:01Z",
  "vote_sum": 0,
  "vote_count": 0,
  "url": "https://itunes.apple.com/gb/review?id=389801252&type=Purple%20Software",
  "rank": 3,
  "app_name": "Instagram",
  "developer": null,
  "bundle_id": null,
  "genre": null,
  "price": null,
  "average_rating": null,
  "rating_count": null,
  "current_version": null,
  "app_url": null,
  "icon_url": null,
  "reviews_delivered": null,
  "pages_read": null,
  "verdict": null,
  "verdict_detail": null
}
```

### Pricing

**Pay for the reviews a run delivers**, plus one run start; no platform usage is billed to you. The review price falls with your Apify plan.

| event | unit | Free | Starter | Scale | Business |
|---|---|---|---|---|---|
| Review (`review`) | per 1,000 | $0.10 | $0.085 | $0.0767 | $0.0767 |
| Run start (`start`) | per run | $0.0011 | $0.0011 | $0.0011 | $0.0011 |

A review is **$0.10 per 1,000** on the Free plan ($0.0001 a review). Each run that delivers a charged review also pays the **run start, $0.0011 once**; a run that delivers no charged review pays nothing at all. So the 500 newest reviews of one app in one country (all Apple's feed serves) cost $0.0511 on the Free plan and $0.0436 on Starter, and 1,000 reviews $0.1011 / $0.0861; several apps and countries in one run share one start. The free `app` row of every app and country, an app Apple does not list in a storefront (`not_found`), an app with no written review (`empty`), a value that is no App Store app or country (`invalid`), a repeated app (`duplicate`), an app left at the cap or the time limit (`not_read`) and a refusal the run could not pass (`failed`) are never charged. A review is charged once a run. A run that stops early (a timeout, an abort) is charged for the reviews it delivered. `maxItems` counts the charged rows; before the first request the log names the price of each row at your plan and the most the run can cost. Enterprise plans have their own rates: the Actor's Pricing tab shows the price for your plan.

### Usage patterns

- **Apps and countries** — `appIds` takes App Store URLs (`https://apps.apple.com/gb/app/instagram/id389801252`, `itunes.apple.com` links too) or numeric ids (`389801252`, `id389801252`). `countries` takes two-letter storefront codes (`us`, `gb`, `de` …; `uk` is read as `gb`): every app is read in every country given. With no `countries`, each app is read in the country of its URL, or `us` for a bare id.
- **What a row is** — `row_type` = `review`: one review with the feed's record; `rank` is its position in the order read (1 to 500); `sort` the order. `row_type` = `app`: the free record of one app in one storefront — the lookup's name, developer, rating and version — and the walk: `reviews_delivered`, `pages_read`, the `verdict` with its `verdict_detail`, and the `status` with its reason.
- **How far an app goes** — `maxReviewsPerApp` = 0 (the default) reads every review Apple's public feed serves for the app, country and order: up to 500 (10 pages of 50). `maxReviewsPerApp` = 100 is the first 100 in the order asked. `maxItems` caps the charged rows of the whole run. The app row's `verdict` says where the walk stopped and why.

### Input configuration

| field | type | default | what it does |
|---|---|---|---|
| `appIds` | `array` |  | The apps to read: App Store URLs (`https://apps.apple.com/us/app/instagram/id389801252`) or numeric app ids (`389801252`, `id389801252`). A URL brings its own country unless `countries` is set. Up to 1,000 app and country pairs a run; a value that is no App Store app gets a free `invalid` row, an app given twice for the same country a free `duplicate` row. |
| `countries` | `array` |  | Two-letter country codes of the App Store storefronts to read (`us`, `gb`, `de`, `jp` …; `uk` is read as `gb`). Every app is read in every country given; reviews are per storefront. Empty: the country in the app's URL, else `us`. A code Apple has no storefront for gets a free `invalid` row. |
| `sort` | `mostRecent` / `mostHelpful` | `"mostRecent"` | `mostRecent`: the newest reviews first. `mostHelpful`: the reviews other users voted most helpful first. Apple's public feed serves up to 500 reviews per app, country and order. |
| `maxReviewsPerApp` | `integer` | `0` | Stop each app and country after this many review rows (0 = all Apple's feed serves: up to 500 per app, country and order). |
| `maxItems` | `integer` | `0` | Stop the run after this many review rows across every app and country (0 = no limit). App rows are free and not counted; an app left unread at the cap gets a free `not_read` row. |

### Errors

**A run does not fail because of your input.** When an input cannot be used — a date that is not a date, an id in the wrong form, two settings that contradict each other — the run ends **Succeeded**, charges nothing, and says what to change:

- the status message starts with `INPUT REJECTED`;
- the dataset holds one row, `{"error": true, "code": "…", "message": "…"}`, and no results;
- the key-value store record `ERROR` holds the same object.

From code, check `error` on the first row before reading results.

An app with no written review in a storefront is not an error: it gets one free `app` row with `status` empty, and the run goes on with the others. A run that ends **Failed** is a fault on our side, never your input; it charges nothing, and we are alerted.

### FAQ

**Why at most 500 reviews per app and country?**

Apple's public reviews feed serves 10 pages of 50 per app, storefront and order, and answers page 11 with "CustomerReviews RSS page depth is limited to 10" (measured 2026-10-22). Every scraper that reads it logged out stops there. For more reviews of a big app, read it in more countries (each storefront has its own reviews) and in both orders (the newest and the most helpful are different reviews). The app row says TRUNCATED when the limit, not the app's last review, ended the walk.

**Which status is which?**

An app row's `status` is `ok` (reviews served), `empty` (no written review in that storefront), `not_found` (Apple's storefront does not list the id, checked twice), `invalid` (a value that is no App Store app, or a country code with no storefront), `duplicate` (the same app and country twice: read once), `failed` (a refusal or an error; free) or `not_read` (left at `maxItems`, the charge limit or the run's time limit). A review row's `status` is `ok`.

**Does it read the developer's replies?**

No. Apple's public reviews feed does not carry the developer's response, so it is not on the row.

**Is the reviewer's nickname on the row?**

The nickname and the profile link are on every review row as Apple shows them publicly. They are personal data: process them under your own lawful basis.

**Does it need an Apple account or API key?**

No. Everything is read from Apple's public reviews feed and lookup, logged out.

### Integration

#### JavaScript

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('hyperbach/app-store-reviews-scraper').call({"appIds": ["https://apps.apple.com/us/app/instagram/id389801252", "835599320"], "maxReviewsPerApp": 50});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

#### Python

```python
from apify_client import ApifyClient
client = ApifyClient('YOUR_TOKEN')
run = client.actor('hyperbach/app-store-reviews-scraper').call(run_input={'appIds': ['https://apps.apple.com/us/app/instagram/id389801252', '835599320'], 'maxReviewsPerApp': 50})
items = client.dataset(run['defaultDatasetId']).list_items().items
```

#### CLI

```bash
apify call hyperbach/app-store-reviews-scraper --input '{"appIds": ["https://apps.apple.com/us/app/instagram/id389801252", "835599320"], "maxReviewsPerApp": 50}'
```

#### REST

```bash
curl -X POST "https://api.apify.com/v2/acts/hyperbach~app-store-reviews-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H 'Content-Type: application/json' -d '{"appIds": ["https://apps.apple.com/us/app/instagram/id389801252", "835599320"], "maxReviewsPerApp": 50}'
```

### Support

apify@hyperbach.com

*This page is generated from the Actor's schemas and a live sample — it cannot describe a field the Actor does not have.*

# Actor input Schema

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

The apps to read: App Store URLs (`https://apps.apple.com/us/app/instagram/id389801252`) or numeric app ids (`389801252`, `id389801252`). A URL brings its own country unless `countries` is set. Up to 1,000 app and country pairs a run; a value that is no App Store app gets a free `invalid` row, an app given twice for the same country a free `duplicate` row.

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

Two-letter country codes of the App Store storefronts to read (`us`, `gb`, `de`, `jp` …; `uk` is read as `gb`). Every app is read in every country given; reviews are per storefront. Empty: the country in the app's URL, else `us`. A code Apple has no storefront for gets a free `invalid` row.

## `sort` (type: `string`):

`mostRecent`: the newest reviews first. `mostHelpful`: the reviews other users voted most helpful first. Apple's public feed serves up to 500 reviews per app, country and order.

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

Stop each app and country after this many review rows (0 = all Apple's feed serves: up to 500 per app, country and order).

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

Stop the run after this many review rows across every app and country (0 = no limit). App rows are free and not counted; an app left unread at the cap gets a free `not_read` row.

## Actor input object example

```json
{
  "appIds": [
    "https://apps.apple.com/us/app/instagram/id389801252",
    "835599320"
  ],
  "sort": "mostRecent",
  "maxReviewsPerApp": 50,
  "maxItems": 100
}
```

# Actor output Schema

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

All scraped records in the default dataset. One row per review (and one free app row per app and country):

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

How the run went: review rows charged, free rows, app/country pairs by status, the walk per pair (reviews delivered, pages read, repeated ids skipped, the verdict), the source counters (calls, served, refused, exits, rotations, bytes) and the memory watch.

# 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": [
        "https://apps.apple.com/us/app/instagram/id389801252",
        "835599320"
    ],
    "maxReviewsPerApp": 50,
    "maxItems": 100
};

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

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

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

```

## Python example

```python
from apify_client import ApifyClient

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

# Prepare the Actor input
run_input = {
    "appIds": [
        "https://apps.apple.com/us/app/instagram/id389801252",
        "835599320",
    ],
    "maxReviewsPerApp": 50,
    "maxItems": 100,
}

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

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

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

```

## CLI example

```bash
echo '{
  "appIds": [
    "https://apps.apple.com/us/app/instagram/id389801252",
    "835599320"
  ],
  "maxReviewsPerApp": 50,
  "maxItems": 100
}' |
apify call hyperbach/app-store-reviews-scraper --silent --output-dataset

```

## MCP server setup

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

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

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/e9a7BDt0s64B1hMoI/builds/9XVsu9lcwqjNdZhSe/openapi.json
