# Apple App Store Scraper (`datasiphon/apple-app-store-scraper`) Actor

Scrape Apple App Store category charts, full app details, reviews, keyword search-rank, and similar-apps. Built for app-market research: competitor density, positioning, review sentiment, and keyword landscape before committing engineering time to a new app.

- **URL**: https://apify.com/datasiphon/apple-app-store-scraper.md
- **Developed by:** [Kashif Ali](https://apify.com/datasiphon) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 apps

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?

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 Scraper

Scrape **Apple App Store category charts**, **full app details**, **reviews**, **keyword search-rank**, and **similar apps** — built for **app-market research** and **idea validation**: competitor density, positioning, review sentiment, and keyword landscape before committing engineering time to a new app.

Wraps the maintained [`app-store-scraper`](https://www.npmjs.com/package/app-store-scraper) npm package (v0.18.0, ISC — same author, facundoolano, as [`google-play-scraper`](https://www.npmjs.com/package/google-play-scraper), which powers the sibling `google-play-store-scraper` actor) — public, unauthenticated iTunes Search API / App Store RSS endpoints, no login, no anti-bot wall.

### What you get per run

Five dataset views, all in one dataset:

| View | Rows | Use case |
|---|---|---|
| **charts** | One per listed app | Market sizing, competitor density, category trends |
| **details** | One per app | Deep competitor analysis, monetization, rating distribution |
| **reviews** | One per review | Sentiment, feature requests, crash reports, version-specific feedback |
| **search** | One per ranked result per term | Keyword-rank tracking — the ASO data AppTweak sells |
| **similar** | One per related app | Competitor/adjacent-app mapping ("customers also bought") |

### Modes (single actor, one input)

| Mode | What it does |
|---|---|
| `charts` | Category/collection listings only |
| `details` | Full app details for explicit `appIds` |
| `reviews` | Reviews for explicit `appIds` |
| `search` | Keyword search-rank for `searchTerms` |
| `similar` | Related apps for explicit `appIds` |
| `all` (default) | Charts → auto-feeds discovered apps to details → reviews for everything |

`search` and `similar` are opt-in — they take explicit keyword/appId lists rather than falling out of chart discovery, so they aren't part of `all`.

#### The `all` mode workflow

```json
{
  "mode": "all",
  "categories": ["BUSINESS", "PRODUCTIVITY"],
  "collections": ["TOP_FREE_IOS", "TOP_GROSSING_IOS"],
  "countries": ["us"],
  "numPerChart": 50,
  "fetchDetailsForListedApps": true,
  "maxReviewsPerApp": 50
}
```

1. Scrapes 2 categories × 2 collections × 1 country × 50 apps = **200 chart rows**
2. Deduplicates the 200 apps → unique bundle IDs
3. Fetches **full details** for every unique app
4. Fetches **up to 50 reviews** per app
5. Single run, single dataset, complete market picture

### Input

#### Quick start (zero config)

Hit **Run** with defaults → gets top 50 free/paid/grossing apps in Business, Productivity, Health & Fitness (US), fetches full details + 50 reviews each.

#### Full input reference

```json
{
  "mode": "all",
  "categories": ["BUSINESS", "PRODUCTIVITY", "HEALTH_AND_FITNESS"],
  "collections": ["TOP_FREE_IOS", "TOP_PAID_IOS", "TOP_GROSSING_IOS"],
  "countries": ["us"],
  "numPerChart": 50,
  "appIds": [],
  "fetchDetailsForListedApps": true,
  "fetchRatingsHistogram": true,
  "maxReviewsPerApp": 50,
  "searchTerms": [],
  "searchNumPerTerm": 20,
  "maxItems": 5000,
  "maxConcurrency": 3,
  "retryAttempts": 3,
  "retryDelayMs": 1500
}
```

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | enum | `"all"` | `charts` | `details` | `reviews` | `search` | `similar` | `all` |
| `categories` | string\[] | `["BUSINESS","PRODUCTIVITY","HEALTH_AND_FITNESS"]` | 70 App Store genres. Use `["ALL"]` for all 70. |
| `collections` | string\[] | `["TOP_FREE_IOS","TOP_PAID_IOS","TOP_GROSSING_IOS"]` | iPhone/iPad/Mac × top/new charts (13 total) |
| `countries` | string\[] | `["us"]` | 2-letter codes (ISO 3166-1 alpha-2) |
| `numPerChart` | int | `50` | Apps per category/collection/country (max 200, App Store RSS limit) |
| `appIds` | string\[] | `[]` | Bundle IDs, numeric App Store IDs, or full App Store URLs — for `details`/`reviews`/`similar` |
| `fetchDetailsForListedApps` | bool | `true` | In `all` mode, auto-fetch details for every app found in charts |
| `fetchRatingsHistogram` | bool | `true` | Fetch the 1-5 star breakdown per app (one extra request per app) |
| `maxReviewsPerApp` | int | `50` | 0 = skip reviews. The App Store review feed hard-caps around 500 (10 pages) per app regardless. |
| `searchTerms` | string\[] | `[]` | Keywords for `search` mode |
| `searchNumPerTerm` | int | `20` | Ranked results to capture per term |
| `maxItems` | int | `5000` | Hard cap on total dataset rows |
| `maxConcurrency` | int | `3` | Parallel requests. Apple rate-limits harder than Google Play. |
| `retryAttempts` | int | `3` | Retries for transient errors |
| `retryDelayMs` | int | `1500` | Base delay for exponential backoff |

### Output

#### Chart row

```json
{
  "type": "chart",
  "category": "BUSINESS",
  "collection": "TOP_GROSSING_IOS",
  "country": "us",
  "rank": 1,
  "id": "288429040",
  "appId": "com.linkedin.LinkedIn",
  "title": "LinkedIn: Community & Network",
  "developer": "LinkedIn Corporation",
  "price": 0,
  "free": true,
  "currency": "USD",
  "genre": "Business",
  "genreId": "6000",
  "icon": "https://is1-ssl.mzstatic.com/...",
  "url": "https://apps.apple.com/us/app/linkedin-community-network/id288429040",
  "scrapedAt": "2026-09-13T22:14:49.555Z"
}
```

#### Detail row

```json
{
  "type": "detail",
  "id": 294934058,
  "appId": "com.hotschedules.hotschedules",
  "title": "HotSchedules",
  "primaryGenre": "Business",
  "score": 4.60016,
  "reviews": 67269,
  "ratings": 67,
  "histogram": { "1": 3364, "2": 973, "3": 2315, "4": 5892, "5": 54725 },
  "price": 2.99,
  "free": false,
  "currency": "USD",
  "requiredOsVersion": "17.1",
  "released": "2008-10-31T05:30:52Z",
  "updated": "2026-09-12T06:49:02Z",
  "version": "6.0.1",
  "description": "...",
  "screenshots": ["https://...", "..."],
  "supportedDevices": ["iPhone16-iPhone16", "..."],
  "developer": "HotSchedules",
  "developerWebsite": "http://www.hotschedules.com",
  "url": "https://apps.apple.com/us/app/hotschedules/id294934058",
  "country": "us",
  "scrapedAt": "2026-09-13T22:14:55.796Z"
}
```

Note App Store addresses apps by both a numeric `id` (trackId) and a string `appId` (bundle ID) — unlike Play's single package-name scheme. Both are always present on detail/chart rows; `appIds` input accepts either form plus full URLs.

Key fields for **idea validation**:

- `score` + `ratings`/`histogram` — rating distribution, not just average (histogram is a separate request per app — see `fetchRatingsHistogram`)
- `reviews` / `currentVersionReviews` — engagement and version-specific reception
- `screenshots` / `description` / `releaseNotes` — positioning and update cadence
- `contentRating`, `languages`, `supportedDevices` — market reach signals
- `developerWebsite` — contact for partnerships

#### Review row

```json
{
  "type": "review",
  "reviewId": "14540170480",
  "appId": "com.linkedin.LinkedIn",
  "userName": "TweetyRuns7",
  "version": "9.1.570",
  "score": 1,
  "title": "Can't login",
  "text": "I am not sure what's going on...",
  "url": "https://itunes.apple.com/us/review?id=288429040&type=Purple%20Software",
  "updated": "2026-09-12T01:34:22-07:00",
  "country": "us",
  "scrapedAt": "2026-09-13T22:20:52.940Z"
}
```

The App Store review RSS feed caps at ~500 reviews (10 pages) per app — this is Apple's limit, not ours.

#### Search row (keyword-rank)

```json
{
  "type": "search",
  "term": "budgeting app",
  "rank": 1,
  "id": 1621020173,
  "appId": "com.fleurbudget",
  "title": "Fleur - Budget Planner App",
  "developer": "Akash Jain",
  "primaryGenre": "Finance",
  "score": 4.80138,
  "url": "https://apps.apple.com/us/app/fleur-budget-planner-app/id1621020173",
  "country": "us",
  "scrapedAt": "2026-09-13T22:30:07.974Z"
}
```

This is the keyword-rank data AppTweak/ASO tools sell — automated App Store search, capturing which apps rank where for each term.

#### Similar row

```json
{
  "type": "similar",
  "sourceAppId": "310633997",
  "relatedId": 454638411,
  "relatedAppId": "com.facebook.Messenger",
  "relatedTitle": "Messenger",
  "relatedDeveloper": "Meta Platforms, Inc.",
  "relatedScore": 4.6613,
  "relatedPrimaryGenre": "Social Networking",
  "relatedUrl": "https://apps.apple.com/us/app/messenger/id454638411",
  "country": "us",
  "scrapedAt": "2026-09-13T22:30:25.503Z"
}
```

Apple's own "customers also bought" data per app listing — competitor/adjacent-app mapping.

### What this doesn't do

This actor covers everything that's actually **scrapable** — public store metadata, charts, reviews, keyword search-rank, and similar-apps. Two things 42matters/AppTweak/Sensor Tower sell that this deliberately does **not** attempt:

- **Install/revenue estimates.** These come from proprietary SDK panels (analytics SDKs embedded in thousands of apps, giving device-level usage samples extrapolated statistically) and calibration deals with Google Play's disclosed install bands. Not scrapable — no public data source exists. If this is ever genuinely needed, buy API access from one of those vendors rather than trying to rebuild the model from rank/rating signals; that's a data-science project of its own with a poor accuracy ceiling.
- **iOS SDK/tech-stack detection.** App Store binaries are FairPlay-DRM-encrypted per purchase — getting a clean binary to inspect for embedded SDKs requires circumventing that DRM, a DMCA anti-circumvention issue, not a difficulty issue. (Android SDK detection via public APK teardown is technically feasible but was explicitly scoped out of this build — separate concern, ask if it's needed later.)

### Reliability

- **Concurrency control** — `p-limit` semaphore, default 3 (lower than the Play actor's default 5 — Apple's iTunes endpoints rate-limit harder, returning 403 rather than 429)
- **Retry with backoff** — exponential backoff + jitter on 403/429/5xx and network errors (`ETIMEDOUT`/`ECONNRESET`/`ECONNREFUSED`/`ENETUNREACH`/`ENOTFOUND`/`EAI_AGAIN`)
- **Graceful degradation** — one failed appId/category/term doesn't kill the run; a missing ratings histogram doesn't lose the rest of a detail row
- **Fail loud** — a run that pushes **0 rows fails with reason** — not a green run with an empty dataset
- **Deduplication** — apps deduplicated across chart discoveries and explicit `appIds` (by resolved bundle ID or numeric ID)
- **Global `maxItems` cap** — stops cleanly at limit

### Categories & Collections (from the package, not hardcoded)

```js
import appStore from 'app-store-scraper';
// category: 70 genres (BOOKS, BUSINESS, GAMES, ... WEATHER)
// collection: 13 charts (TOP_FREE_IOS, TOP_PAID_IPAD, TOP_GROSSING_MAC, NEW_IOS, ...)
```

`ALL_CATEGORIES`/`ALL_COLLECTIONS` in `src/utils.ts` read these directly from the library at runtime — use `["ALL"]` in `categories` to scrape all 70.

### Local development

```bash
cd apple-app-store-scraper
npm install
npm test                # runs unit tests (no network)
npm start                # runs locally, reads storage/key_value_stores/default/INPUT.json
```

Local storage directory is controlled by `CRAWLEE_STORAGE_DIR` (not `APIFY_LOCAL_STORAGE_DIR`), and its default-store input file must live at `<dir>/key_value_stores/default/INPUT.json` — running via the `apify` CLI (`apify run`) sets this up automatically; running `node` directly does not.

#### Test input (save as `storage/key_value_stores/default/INPUT.json`)

```json
{
  "mode": "all",
  "categories": ["BUSINESS"],
  "collections": ["TOP_FREE_IOS"],
  "countries": ["us"],
  "numPerChart": 5,
  "fetchDetailsForListedApps": true,
  "fetchRatingsHistogram": false,
  "maxReviewsPerApp": 5,
  "maxItems": 100,
  "maxConcurrency": 3
}
```

### Deployment

```bash
apify push
```

### Is scraping the App Store legal?

Scraping publicly available app listings, ratings, and reviews is generally legal. This actor collects only public, non-personal data — app metadata, aggregate ratings, and public reviews — via the same endpoints the App Store app itself uses. No user accounts, no private data, no DRM circumvention, no authentication bypass.

You are responsible for how you use the output, including under GDPR/CCPA where relevant. Apple's ToS prohibits automated access; use at your own risk. When in doubt, take legal advice.

### Known dependency vulnerabilities

`app-store-scraper` depends on the deprecated `request` HTTP library, which transitively pulls in `form-data`/`qs`/`tough-cookie`/`uuid` versions with known CVEs (`npm audit` flags 2 critical, 7 moderate). All of them are exploitable only via attacker-controlled multipart form bodies or cookie jars — this actor only issues GET requests to fixed, hardcoded Apple endpoints and never sends multipart data or persists cookies, so none of these are reachable in practice. No non-breaking fix exists upstream (the fix downgrades to `app-store-scraper@0.4.0`, a materially older and less capable version); documenting the ceiling here rather than downgrading.

### Changelog

- **1.0** — Initial release: charts, details, reviews, keyword search-rank, similar-apps, 70 categories, 13 collections, retry/backoff, fail-loud, deduplication, dataset views.

### License

Apache-2.0

***

### Pricing & cost estimator

Pay-per-event: **$1.00 per 1,000 app rows**. You are charged only for rows that contain data; empty or failed targets are free. A 100-item run typically finishes in well under a few minutes and costs a fraction of a cent in platform usage. Use `maxItems` to control spend.

### Example output (real run)

```json
{
  "type": "chart",
  "category": "BUSINESS",
  "collection": "TOP_FREE_IOS",
  "country": "us",
  "rank": 1,
  "id": "1113153706",
  "appId": "com.microsoft.skype.teams",
  "title": "Microsoft Teams",
  "developer": "Microsoft Corporation",
  "developerUrl": "https://apps.apple.com/us/developer/microsoft-corporation/id298856275?uo=2",
  "price": 0,
  "free": true,
  "currency": "USD",
  "genre": "Business",
  "genreId": "6000",
  "icon": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/5b/d9/40/5bd940d9-d71d-17b0-7515-8fff4d9e7aa4/AppIcon-0-…",
  "url": "https://apps.apple.com/us/app/microsoft-teams/id1113153706?uo=2",
  "scrapedAt": "2026-10-01T19:39:02.027Z"
}
```

### Limitations

Chart positions and ratings reflect the moment of the run and the chosen country. Review texts are not collected unless maxReviewsPerApp is set.

### No login, no cookies

This actor only reads public Apple App Store listings and charts. It never asks for a cookie, password or account and does not access private data. Use the data in line with the source site's terms and applicable law (GDPR/CCPA for personal data).

### Need a custom version?

Need a field this actor doesn't return, a different site, a schedule, or delivery to Google Sheets / your CRM?
We build and maintain custom scrapers, public data only. Open an **Issue** on this actor titled "Custom request", or email kashif@dialnorth.com.

# Actor input Schema

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

What to scrape. 'charts' = category/collection listings; 'details' = full app details for given appIds; 'reviews' = reviews for given appIds; 'search' = keyword search-rank; 'similar' = related apps. 'all' runs charts → details → reviews in one run.

## `categories` (type: `array`):

App Store genres. Use 'ALL' to scrape all 70 categories. Defaults to a few common ones for a quick sample.

## `collections` (type: `array`):

Which charts to scrape per category. Mix of iPhone/iPad/Mac and top/new charts.

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

Countries to scrape charts for. Defaults to US.

## `numPerChart` (type: `integer`):

How many apps to fetch per category/collection/country combination. Capped at 200 by the App Store RSS feed.

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

Explicit apps for 'details', 'reviews', and 'similar' modes. Accepts bundle IDs (com.example.app), numeric App Store IDs, or full App Store URLs.

## `fetchDetailsForListedApps` (type: `boolean`):

When mode includes charts, also fetch full detail for every unique app discovered. Enables a single run to go from category charts to full app data.

## `fetchRatingsHistogram` (type: `boolean`):

Fetch the 1-5 star rating breakdown per app (one extra request per app). Turn off for faster/cheaper runs when only the average score is needed.

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

Maximum reviews to fetch per app. Set to 0 to skip reviews entirely. The App Store review RSS feed caps out around 500 reviews (10 pages) per app regardless of this setting.

## `searchTerms` (type: `array`):

Keywords to search the App Store for, in 'search' mode. Captures which apps rank where for each term — the keyword-rank data ASO tools like AppTweak sell.

## `searchNumPerTerm` (type: `integer`):

How many ranked results to capture per search term.

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

Hard cap on total dataset rows pushed across all modes. Stops cleanly when hit.

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

Parallel requests to the App Store. Apple's iTunes endpoints rate-limit harder than Google Play's, so this defaults lower.

## `retryAttempts` (type: `integer`):

Retries for transient errors (rate limits, network) with exponential backoff.

## `retryDelayMs` (type: `integer`):

Base delay for exponential backoff (ms). Delay = base \* 2^attempt + jitter.

## Actor input object example

```json
{
  "mode": "all",
  "categories": [
    "BUSINESS",
    "PRODUCTIVITY",
    "HEALTH_AND_FITNESS"
  ],
  "collections": [
    "TOP_FREE_IOS",
    "TOP_PAID_IOS",
    "TOP_GROSSING_IOS"
  ],
  "countries": [
    "us",
    "gb",
    "de"
  ],
  "numPerChart": 50,
  "appIds": [
    "com.midasplayer.apps.candycrushsaga",
    "https://apps.apple.com/us/app/whatsapp-messenger/id310633997"
  ],
  "fetchDetailsForListedApps": true,
  "fetchRatingsHistogram": true,
  "maxReviewsPerApp": 50,
  "searchTerms": [
    "habit tracker",
    "budgeting app",
    "meal planner"
  ],
  "searchNumPerTerm": 20,
  "maxItems": 5000,
  "maxConcurrency": 3,
  "retryAttempts": 3,
  "retryDelayMs": 1500
}
```

# Actor output Schema

## `dataset` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("datasiphon/apple-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("datasiphon/apple-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 '{}' |
apify call datasiphon/apple-app-store-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datasiphon/apple-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/rXH9mGzCnX3ZfuQ0K/builds/CYS8d1VWya78d0YkQ/openapi.json
