# App Store Scraper - Apps, Reviews & Charts (`s-r/app-store-scraper`) Actor

Read the Apple App Store: search any storefront, look up apps by link or bundle id, list a developer's catalogue, or pull the top charts. Ratings, prices, versions, screenshots and customer reviews included.

- **URL**: https://apify.com/s-r/app-store-scraper.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:** E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## App Store Scraper

Read the Apple App Store as data. Search any country's storefront, look up
specific apps by link or bundle id, list everything a developer has published,
or pull a top chart. Every row carries the ratings, the price, the version
history, the screenshots and the store description, and customer reviews can be
attached to any of them.

### Four ways to ask

**Search.** Give it terms, one per line, and get up to 200 apps per term with
the full record for each. This is the mode for market research: what exists in
a category, who publishes it, how it is rated and what it costs.

**Specific apps.** Paste App Store links, numeric ids or bundle ids in any
mix. A link copied from any storefront works, tracking parameters and all. Use
this to track a set of competitors over time.

**Developer's apps.** Give it a developer id and get their whole catalogue. The
id is on every app row as `developer_id`, so you can search once, spot a
publisher worth watching, and pull everything they have made.

**Top charts.** Top free, top paid, top grossing and new, per country, with the
rank on every row. Narrow to a category with a genre id, for example 6014 for
Games or 6008 for Photo & Video.

### Storefronts are not interchangeable

Everything Apple returns is per country: which apps exist, what they cost, how
they are rated and what people wrote about them. An app can rank in the US and
not be listed in Germany at all. The **Storefront** input takes a two-letter
country code and every row records which storefront it came from, so a run
across several countries stays sortable rather than silently mixed.

If a lookup returns nothing, the country is the first thing to check. That is
reported as `not_found` with the storefront named, rather than as a bad id.

### Reviews, and the honest limits

Turn on **Include reviews** and each app gets a `reviews` array: the rating,
the title, the text, the author, the app version reviewed and the date.

Two things to know, because they are properties of Apple's feed rather than
choices this actor makes.

**The ceiling is 100 reviews per app per storefront.** Apple serves reviews in
pages of fifty and stops after two. This was measured rather than assumed: the
first two pages return fifty each with no overlap between them, and the third
page was never served across twenty attempts. Asking for more returns the
ceiling. If you need more than 100, run the same app against several
storefronts; the reviews differ per country.

**The feed answers most requests with nothing, so it has to be retried.** The
same reviews URL returns fifty reviews on one attempt and an empty page on the
next, minutes apart, with no error either time. This actor retries until it is
served, which is what the **Review retry budget** input controls. The default
of 10 is comfortable; lower it and you get fewer reviews rather than a failure.

Because of that, every row says which outcome it got in `reviews_status`:

| Value | Meaning |
|---|---|
| `complete` | Both pages served |
| `partial` | One page served, the other was refused |
| `none` | Served, and the app genuinely has no reviews |
| `not_served` | The feed refused every attempt. We do not know whether reviews exist |

That last distinction is the one that matters. Plenty of tools report an empty
reviews feed as "this app has no reviews", which is a confident and wrong
statement about an app with millions of them. This one tells you it could not
find out, and lists the app under `reviews_not_served` in the errors record so
you can rerun just those.

### What a row looks like

- `app_id`, `bundle_id`, `title`, `url` — the identifiers
- `developer`, `developer_id`, `developer_url`, `seller`, `seller_url`
- `price`, `currency`, `is_free`
- `rating`, `rating_count`, `rating_current_version`, `rating_count_current_version`
- `version`, `released_at`, `updated_at`, `release_notes`, `size_bytes`
- `description`, `primary_genre`, `genres`, `genre_ids`, `content_rating`, `advisories`
- `minimum_os`, `supported_devices`, `languages`
- `icon_url`, `screenshots`, `ipad_screenshots`
- `country`, `search_term`, `position`, and `chart_rank` on chart rows
- `reviews`, `review_count_returned`, `reviews_status` when reviews were asked for

Ratings come in two flavours on purpose. `rating` and `rating_count` cover the
app's whole history; `rating_current_version` covers only the release that is
live now. A big gap between them usually means a recent update changed how
people feel about it, which is the signal you actually want.

### Errors

An input that fails becomes an entry in the `errors` record rather than a
missing row:

| Code | Meaning |
|---|---|
| `bad_input` | Not an App Store link, id or bundle id |
| `not_found` | No app with that id in the storefront you chose |
| `no_results` | The search term matched nothing |
| `fetch_failed` | Apple could not be reached after several attempts |
| `reviews_not_served` | The reviews feed refused every attempt for that app |

### Run sizes

The catalogue side is fast: search, lookup, developer and chart calls come back
in well under a second each, and a run of a hundred apps finishes in seconds.

Reviews are the slow half, because of the retrying described above. Budget
roughly a few seconds per app and prefer smaller batches when you want reviews.
If you are collecting catalogue data only, leave **Include reviews** off and
runs stay fast and cheap.

The default memory of 512 MB is comfortable for any run size, since nothing is
held but JSON.

### Related actors

For the Google Play side of the same question, use **Google Play Scraper**. For
what people search for in an app store rather than what is in it, look at the
keyword actors. For reviews on the open web rather than in the store, use
**Trustpilot Reviews** or **G2 Reviews**.

# Actor input Schema

## `mode` (type: `string`):

Search a storefront, look up specific apps, list a developer's catalogue, or read a top chart.

## `search` (type: `array`):

One term per line. Used in Search mode.

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

App Store links, numeric ids or bundle ids, one per line. Used in Specific apps mode.

## `developerIds` (type: `array`):

Numeric developer ids, one per line. A developer id is on every app row as developer\_id. Used in Developer's apps mode.

## `chartFeed` (type: `string`):

Which top chart to read. Used in Top charts mode.

## `genreId` (type: `string`):

Optional App Store genre id to narrow a chart, for example 6014 for Games or 6008 for Photo & Video.

## `country` (type: `string`):

Two-letter country code. Apps, prices, ratings and reviews are all per storefront.

## `entity` (type: `string`):

iPhone, iPad or Mac apps.

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

Per search term, developer or chart. Apple caps a response at 200.

## `includeReviews` (type: `boolean`):

Attach customer reviews to every app. Slower and metered separately, because the reviews feed has to be retried before it serves.

## `maxReviews` (type: `integer`):

Apple serves at most 100 per app per storefront, in two pages of fifty. Asking for more returns the ceiling.

## `reviewAttempts` (type: `integer`):

How many times to retry one page of reviews before giving up. The feed answers most requests with an empty page, so a low budget returns fewer reviews rather than failing.

## Actor input object example

```json
{
  "mode": "search",
  "search": [
    "photo editor",
    "budget tracker"
  ],
  "appIds": [
    "389801252",
    "com.burbn.instagram"
  ],
  "developerIds": [
    "389801255"
  ],
  "chartFeed": "topfreeapplications",
  "country": "us",
  "entity": "software",
  "maxItems": 50,
  "includeReviews": false,
  "maxReviews": 100,
  "reviewAttempts": 10
}
```

# Actor output Schema

## `apps` (type: `string`):

One row per app.

## `summary` (type: `string`):

Apps returned, reviews returned, and how many apps the reviews feed refused to serve.

## `errors` (type: `string`):

Inputs that could not be read, with the reason.

# 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 = {
    "search": [
        "photo editor"
    ],
    "appIds": [
        "https://apps.apple.com/us/app/instagram/id389801252"
    ],
    "developerIds": [
        "389801255"
    ],
    "country": "us"
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/app-store-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 = {
    "search": ["photo editor"],
    "appIds": ["https://apps.apple.com/us/app/instagram/id389801252"],
    "developerIds": ["389801255"],
    "country": "us",
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/app-store-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 '{
  "search": [
    "photo editor"
  ],
  "appIds": [
    "https://apps.apple.com/us/app/instagram/id389801252"
  ],
  "developerIds": [
    "389801255"
  ],
  "country": "us"
}' |
apify call s-r/app-store-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/app-store-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/C0a1IbxHCBDvONGQH/builds/DZYL77TiSElKur5Wq/openapi.json
