# App Store Scraper (from $0.10/1,000 apps) - Apple iOS Data (`epctex/appstore-scraper`) Actor

Search the App Store by keyword, or look up apps by App Store ID, bundle ID, or developer ID. First 50 free, then $0.10/1,000 (search) or $0.40/1,000 for direct lookups, 45 fields per app. Reviews from $0.10/1,000, similar apps from $0.01/1,000, as add-ons. Fits ASO teams, publishers, researchers.

- **URL**: https://apify.com/epctex/appstore-scraper.md
- **Developed by:** [epctex](https://apify.com/epctex) (community)
- **Categories:** E-commerce
- **Features:** Limited permissions
- **Stats:** 1,308 total users, 106 monthly users, 99.3% runs succeeded, 20 bookmarks
- **User rating**: 4.98 out of 5 stars

## Pricing

from $0.10 / 1,000 dataset items

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

## App Store Scraper

App Store Scraper is an Apify actor that extracts App Store app metadata, reviews and similar apps by App Store ID, bundle ID, developer ID or App Store URL. App lookups cost **$0.0003 each, or $0.40 per 1,000 apps**.

The App Store Scraper runs 6 query types, accepts 19 input fields, returns 45 structured fields per app, and bills through 6 charge events. Every price below is a per-event charge with no subscription and no monthly minimum.

### What Does the App Store Scraper Extract?

The App Store Scraper runs 6 query types. Four query types resolve apps, and two query types attach extra data to an app that is already resolved.

| Query Type | Description |
|---|---|
| App Lookup (by ID or URL) | Fetches full app detail metadata for a single app identified by its numeric App Store ID or an App Store app URL. |
| App Lookup (by Bundle ID) | Fetches full app detail metadata for a single app identified by its iOS bundle identifier. |
| Developer Apps | Lists every app published by a given developer ID or App Store developer page URL. |
| Keyword Search | Searches the App Store for a term, with page-by-page pagination. |
| Similar Apps | Fetches similar/related apps for a resolved app. Optional add-on enabled per query. |
| Reviews | Fetches paginated user reviews for a resolved app. Optional add-on enabled per query. |

Similar Apps and Reviews are add-ons rather than standalone queries. Both attach their results to the app's dataset item instead of producing separate top-level items.

### Which App Store Identifiers Does the App Store Scraper Accept?

The App Store Scraper accepts 5 input identifiers: `term`, `startUrls`, `appIds`, `bundleIds` and `developerIds`. Each identifier maps to a different way of naming an app on the App Store.

- **`appIds`** takes numeric App Store IDs, such as `389801252`, and looks apps up directly without a full URL.
- **`bundleIds`** takes iOS bundle identifiers, such as `com.crunchyroll.sfduel`.
- **`developerIds`** takes numeric developer IDs and returns every application published by each developer.
- **`startUrls`** takes App Store or iTunes detail URLs, covering app detail pages and developer pages.
- **`term`** takes a keyword and searches the App Store for matching apps.

App Store Scraper needs at least one of these five inputs to build a query.

### Who Uses the App Store Scraper?

Listed below are the 8 roles that run App Store Scraper against App Store data.

- **ASO managers** track keyword search results per storefront country, then read `trackName`, `primaryGenreName` and `averageUserRating` for every app ranking on a term.
- **App publishers** monitor their own `version`, `currentVersionReleaseDate` and `releaseNotes` alongside the same fields for competing apps.
- **Competitive intelligence analysts** pull a rival's full catalog through `developerIds` and compare `price`, `formattedPrice` and `contentAdvisoryRating` across the portfolio.
- **Product managers** collect `reviews` for a resolved app and read `rating`, `title` and `review` to locate defects and feature requests by app version.
- **Market researchers** size a category by paginating Keyword Search across a term and aggregating `userRatingCount` and `averageUserRating`.
- **Machine learning teams** assemble app metadata and review corpora from `trackName`, `releaseNotes` and the `reviews` array for classification and sentiment models.
- **Investment analysts** track `averageUserRatingForCurrentVersion` and `currentVersionScore` over repeated runs to observe reception of each release.
- **Agencies** run App Store Scraper across client portfolios by supplying multiple `appIds` or `bundleIds` in one run.

### What Does the App Store Scraper Cost?

The App Store Scraper charges 6 event types. App lookups and developer queries cost **$0.0003**, reviews cost **$0.005 per page**, keyword search costs **$0.005 per query regardless of page count**, similar apps cost **$0.00015**, and every returned item adds **$0.0001**.

| Query Type | Charge Event | Price |
|---|---|---|
| App Lookup (by ID or URL) | `app-query` | $0.0003 |
| App Lookup (by Bundle ID) | `app-query` | $0.0003 |
| Developer Apps | `developer-query` | $0.0003 |
| Keyword Search (per query) | `search-query` | $0.005 |
| Similar Apps | `similar-query` | $0.00015 |
| Reviews (per page) | `review-query` | $0.005 |

Every returned item is also charged `dataset-item`: $0.0001. A paying account's items from a keyword search's first results page are exempt from the `dataset-item` charge.

#### App Store Scraper Cost Examples

The table below prices 7 scenarios against the rate card above. Each row multiplies the charge events triggered by the run.

| Scenario | Charge Events | Calculation | Total Cost |
|---|---|---|---|
| 1 app lookup by App Store ID | 1 app-query + 1 dataset-item | $0.0003 + $0.0001 | $0.0004 |
| 1 app lookup by bundle ID, with similar apps | 1 app-query + 1 similar-query + 1 dataset-item | $0.0003 + $0.00015 + $0.0001 | $0.00055 |
| 1,000 app lookups by App Store ID | 1,000 app-query + 1,000 dataset-item | (1,000 × $0.0003) + (1,000 × $0.0001) | $0.40 |
| A 3-page keyword search returning 150 apps (50 per page), free account | 1 search-query + 150 dataset-item | $0.005 + (150 × $0.0001) | $0.020 |
| The same 3-page keyword search, paying account (page 1's 50 apps exempt) | 1 search-query + 100 dataset-item | $0.005 + (100 × $0.0001) | $0.015 |
| 1 app with 5 pages of reviews | 1 app-query + 5 review-query + 1 dataset-item | $0.0003 + (5 × $0.005) + $0.0001 | $0.0254 |
| 1 developer query returning 12 apps | 1 developer-query + 12 dataset-item | $0.0003 + (12 × $0.0001) | $0.0015 |

#### App Store Scraper Unit Economics

The 8 figures below are derived from the rate card above. Volume figures exclude `dataset-item` charges except where the row states otherwise.

| Metric | Value |
|---|---|
| Cost per app lookup, including its dataset item | $0.0004 |
| App lookups per $1, including dataset items | 2,500 |
| Cost per 1,000 app lookups, including dataset items | $0.40 |
| Cost per keyword search query, any number of pages | $0.005 |
| Keyword searches per $1, any number of pages | 200 |
| Cost of the similar-apps add-on, per app | $0.00015 |
| Ratio between the cheapest and most expensive charge event | 1 : 50 |
| Share of a single app lookup's cost taken by `dataset-item` | 25% |

Reviews are the only per-page charge, on `review-query` at $0.005 per page, which makes `endPage` the field that controls review cost. Keyword search charges `search-query` once per run no matter how many pages `endPage` allows, so pagination depth no longer multiplies that charge. `endPage` still governs how many search-result items are fetched and therefore how many `dataset-item` charges a search run accrues, except that a paying account's first page of search results is exempt from `dataset-item`. A review page costs 16.7 times an app lookup, so a run with deep review pagination is priced by its page count rather than its app count.

### How Is the App Store Scraper Configured?

The App Store Scraper exposes 19 input fields covering targets, localization, pagination, add-ons, output mapping and delivery to other services.

| Name | Type | Description |
|---|---|---|
| term | String | Keyword to search for on the App Store. |
| startUrls | Array | App Store or iTunes detail URLs. |
| appIds | Array | Numeric App Store IDs to look up directly, without needing a full URL. |
| bundleIds | Array | Bundle identifiers to look up directly (e.g. com.crunchyroll.sfduel). |
| developerIds | Array | Numeric developer IDs. Returns every application published by each developer. |
| mode | String | Optional override of the execution path. |
| country | String | App Store storefront country. |
| lang | String | Response language. Defaults to the storefront country's language if omitted. |
| page | Integer | First page number to request from the search endpoint. |
| endPage | Integer | Last page number the actor should request, for both search-result pagination and, when includeReviews is enabled, per-app review pagination. Default: unbounded (the actor keeps paging until the provider returns an empty page or maxItems is reached). |
| includeReviews | Boolean | When true, fetches reviews for every resolved app and attaches them to the app's dataset item. |
| reviewsSort | String | Sort order used when fetching reviews. Only applies when includeReviews is true. |
| includeSimilarApps | Boolean | When true, fetches similar/related apps for every resolved app and attaches them to the app's dataset item. |
| maxItems | Integer | Maximum number of top-level application items the run will output. Default is all. |
| customMapFunction | String | JavaScript function to transform each app before it is pushed to the dataset. Ignored with a warning if it is not a usable function; the original item is pushed unchanged. |
| outputConnector | String | MCP connector to deliver the results to — pick one (see [How Do You Send App Store Scraper Results to Notion, Todoist and More?](#how-do-you-send-app-store-scraper-results-to-notion-todoist-and-more)). |
| deliverTarget | String | Where the connector writes — paste the URL or ID (Notion example: `https://www.notion.so/My-Posts-24f8a1b2c3d44e5f8a9b0c1d2e3f4a5b`); some services need none. |
| deliverMode | String | `perPost` (one record per item) or `summary` (one roll-up per run). |
| deliverMaxItems | Integer | Optional cap on how many records `perPost` delivers (ignored in `summary`). |

`mode` accepts `search` and `lookup`. Omitting `mode` lets the actor select the execution path from the identifiers supplied.

### Which Input Rules Does the App Store Scraper Enforce?

The App Store Scraper enforces 6 usage constraints covering URL shape, mode requirements and custom mapping.

| Rule | Why |
|---|---|
| `startUrls` entries must be `http`/`https` URLs on an `apple` hostname. | Only Apple's own App Store domains are supported as lookup sources; anything else is skipped with a warning. |
| A `startUrls` entry must be an app detail page (path contains `/id` and `/app/`) or a developer page (path contains `/id` and `/developer/`). | Only these two page shapes can be parsed into a resolvable query; other App Store page types are skipped with a warning. |
| When `mode` is `search`, `term` must be non-empty. | Search mode has no other way to know what to search for. |
| When `mode` is `lookup`, at least one of `appIds`, `startUrls`, `bundleIds`, or `developerIds` must be provided. | Lookup mode needs at least one target identifier to resolve. |
| When `mode` is omitted, at least one of `appIds`, `startUrls`, `bundleIds`, `developerIds`, or `term` must be provided. | The actor needs at least one usable input to build any query from. |
| `customMapFunction` must be a plain synchronous function that returns a non-null object (not async, not a generator, not a Promise) and finishes within about 1 second per item. It runs in an isolated sandbox with plain JavaScript only (no network requests, `require` or `console`). | Guarantees deterministic per-item mapping without being able to break the run; on rejection the run continues with unmodified items. |

A rejected `customMapFunction` does not stop the run. The run continues and pushes items unmodified.

### How Do You Run an App Store Scraper Keyword Search?

A keyword search needs 4 settings: `term`, `country`, `page` and `endPage`. The App Store Scraper requests the search endpoint from the `page` number and stops at `endPage`, at `maxItems`, or when the provider returns an empty page.

```json
{
  "term": "photo editor",
  "mode": "search",
  "country": "us",
  "page": 1,
  "endPage": 5,
  "maxItems": 200
}
```

### How Do You Look Up Apps with the App Store Scraper by App Store ID or Bundle ID?

Lookup mode resolves apps from identifiers instead of a keyword. Supply `appIds`, `bundleIds`, `developerIds` or `startUrls`, and combine them in a single run.

```json
{
  "mode": "lookup",
  "appIds": ["389801252"],
  "bundleIds": ["com.crunchyroll.sfduel"],
  "developerIds": ["284882218"],
  "startUrls": [
    "https://apps.apple.com/us/app/sample-photo-app/id389801252"
  ],
  "country": "us",
  "lang": "en"
}
```

### How Does the App Store Scraper Collect App Store Reviews?

The App Store Scraper collects reviews when `includeReviews` is set to `true`. Reviews are fetched for every resolved app, paginated per app, and attached to that app's dataset item in the `reviews` array. `reviewsSort` sets the review sort order and applies only while `includeReviews` is true. `endPage` caps per-app review pagination in the same run where it caps search-result pagination.

```json
{
  "mode": "lookup",
  "appIds": ["389801252"],
  "includeReviews": true,
  "reviewsSort": "mostRecent",
  "endPage": 3
}
```

### How Does the App Store Scraper Collect Similar Apps?

The App Store Scraper collects similar apps when `includeSimilarApps` is set to `true`. Related apps are fetched for every resolved app and attached to that app's dataset item in the `similarApps` array. A failure in the similar-apps step leaves the main app item intact, because that step is skipped independently of the app query.

### How Do You Send App Store Scraper Results to Notion, Todoist and More?

Skip the manual exports — the App Store Scraper can deliver its results directly into the tools you already work in, through [Apify MCP Connectors](https://apify.com/store/collections/mcp-connectors):

1. Authorize a connector (Notion, Todoist, or any service whose MCP server offers non-destructive write tools) once in [Console → Settings → API & Integrations → MCP connectors](https://console.apify.com/settings/integrations).
2. Pick it from the **Send results to Notion, Todoist, …** dropdown.
3. Fill in where and how to write:
   - **Destination target** — paste the URL or ID (Notion example: `https://www.notion.so/My-Posts-24f8a1b2c3d44e5f8a9b0c1d2e3f4a5b`); share it with your connector first via Notion's **•••  → Connections** menu. For structured columns use a **page** URL (the Actor builds the database for you) or a database that **already has columns** — an empty database only receives titles. (Some services need no target.)
   - **Write mode** — `perPost` writes one record per app; `summary` writes a single roll-up for the run.
   - **Max records to deliver** *(optional)* — cap on how many records `perPost` delivers.

**Notion, structured like a spreadsheet:** for a table of typed columns, point the target at a Notion **page** — the Actor auto-creates a database from the data, one matching, correctly-typed column per field (URL, number, date, text). Pointing at an **existing database** fills the columns it already has (matched by name); an **empty database** receives only the title, and the rest of each record goes to the row body — the connector can't add columns to an existing database without destructive access it never requests. Nested fields are flattened into columns too — a list like `genres` becomes a comma-joined column — so everything is sortable and filterable in the grid. Fields that don't fit a column go to the row's page body, so nothing is lost.

Your credentials stay yours: the Actor talks to your services exclusively through your own authorized connector and only ever uses non-destructive write tools — it cannot delete or overwrite anything you already have. Delivery is best-effort and runs after the scrape: if a connector is misconfigured or a delivery fails, your run still finishes normally with all results intact — the failure is reported in the run log, never by failing the run. If the run is about to hit its timeout, delivery stops early so the run still succeeds, and the log tells you how many records were delivered.

### Which Fields Does the App Store Scraper Output?

The App Store Scraper outputs 45 fields per app, covering identifiers, naming, artwork, genre, technical requirements, release history, pricing, developer attribution, ratings, screenshots and the two add-on arrays.

#### App Store Scraper App Object

| Name | Type |
|---|---|
| inputSource | Object |
| wrapperType | String |
| kind | String |
| trackId | String |
| collectionId | String |
| bundleId | String |
| trackName | String |
| collectionName | String |
| trackCensoredName | String |
| collectionCensoredName | String |
| trackViewUrl | String |
| collectionViewUrl | String |
| artworkUrl60 | String |
| artworkUrl100 | String |
| artworkUrl512 | String |
| artworkUrl600 | String |
| primaryGenreName | String |
| primaryGenreId | String |
| genres | Array |
| genreIds | Array |
| contentAdvisoryRating | String |
| languageCodesISO2A | Array |
| fileSizeBytes | String |
| minimumOsVersion | String |
| releaseDate | String |
| currentVersionReleaseDate | String |
| releaseNotes | String |
| version | String |
| price | Number |
| formattedPrice | String |
| currency | String |
| artistId | String |
| artistName | String |
| artistViewUrl | String |
| sellerUrl | String |
| averageUserRating | Number |
| userRatingCount | Number |
| averageUserRatingForCurrentVersion | Number |
| currentVersionScore | Number |
| screenshotUrls | Array |
| ipadScreenshotUrls | Array |
| appletvScreenshotUrls | Array |
| supportedDevices | Array |
| similarApps | Array |
| reviews | Array |

`inputSource` records which flow produced the item and which identifier it came from, which keeps items traceable when one run mixes lookup and search queries.

#### App Store Scraper Output Example

```json
{
    "inputSource": {
        "flow": "lookup-app",
        "sourceId": "389801252"
    },
    "wrapperType": "track",
    "kind": "software",
    "trackId": "389801252",
    "collectionId": "389801252",
    "bundleId": "com.example.photoapp",
    "trackName": "Sample Photo App",
    "collectionName": "Sample Photo App",
    "trackCensoredName": "Sample Photo App",
    "collectionCensoredName": "Sample Photo App",
    "trackViewUrl": "https://apps.apple.com/us/app/sample-photo-app/id389801252",
    "collectionViewUrl": "https://apps.apple.com/us/app/sample-photo-app/id389801252",
    "artworkUrl60": "https://is1-ssl.mzstatic.com/image/thumb/example/60x60.png",
    "artworkUrl100": "https://is1-ssl.mzstatic.com/image/thumb/example/100x100.png",
    "artworkUrl512": "https://is1-ssl.mzstatic.com/image/thumb/example/512x512.png",
    "artworkUrl600": "https://is1-ssl.mzstatic.com/image/thumb/example/600x600.png",
    "primaryGenreName": "Photo & Video",
    "primaryGenreId": "6008",
    "genres": ["Photo & Video", "Social Networking"],
    "genreIds": ["6008", "6005"],
    "contentAdvisoryRating": "12+",
    "languageCodesISO2A": ["EN", "ES"],
    "fileSizeBytes": "215678976",
    "minimumOsVersion": "15.0",
    "releaseDate": "2018-04-02T07:00:00Z",
    "currentVersionReleaseDate": "2026-08-20T07:00:00Z",
    "releaseNotes": "Bug fixes and performance improvements.",
    "version": "9.4.1",
    "price": 0,
    "currency": "USD",
    "artistId": "284882218",
    "artistName": "Sample Inc.",
    "artistViewUrl": "https://apps.apple.com/us/developer/sample-inc/id284882218",
    "sellerUrl": "https://www.example.com",
    "averageUserRating": 4.5,
    "userRatingCount": 128340,
    "averageUserRatingForCurrentVersion": 4.6,
    "currentVersionScore": 892,
    "screenshotUrls": [
        "https://is1-ssl.mzstatic.com/image/thumb/example/screenshot1.png",
        "https://is1-ssl.mzstatic.com/image/thumb/example/screenshot2.png"
    ],
    "ipadScreenshotUrls": [],
    "appletvScreenshotUrls": [],
    "supportedDevices": ["iPhone13,1", "iPhone13,2"],
    "similarApps": [
        {
            "trackId": "123456789",
            "bundleId": "com.example.anotherphotoapp",
            "trackName": "Another Photo App",
            "trackViewUrl": "https://apps.apple.com/us/app/another-photo-app/id123456789",
            "price": 0,
            "currency": "USD"
        }
    ],
    "reviews": [
        {
            "id": "10234567890",
            "userName": "jdoe",
            "title": "Great app!",
            "review": "Really enjoy using this every day.",
            "rating": 5,
            "version": "9.4.0",
            "updated": "2026-08-15T10:32:00Z"
        }
    ]
}
```

### Which Errors Does the App Store Scraper Report?

The App Store Scraper reports 23 error codes. **20 of the 23 codes are non-terminal**, covering per-query failures that skip one app, one developer query or one page and leave the rest of the run alive. 3 codes terminate the run: C098, C099 and C100.

The 20 non-terminal codes split into 6 recovery behaviors:

- **6 codes** (C001, C002, C003, C008, C009, C010) skip one app and push no dataset item for it.
- **4 codes** (C004, C005, C011, C012) skip only the similar-apps step and leave the main app item intact.
- **4 codes** (C006, C007, C013, C014) stop review pagination for one app and keep every review already collected.
- **3 codes** (C015, C016, C017) skip one developer query and push no items for it.
- **2 codes** (C018, C020) skip one keyword search page.
- **1 code** (C019) pushes an error item and retries the request after a short delay, up to the configured retry limit.

| Error Code | Cause | Behavior |
|---|---|---|
| C001 | Request for an app (by App ID) failed. | The app is skipped; no dataset item is pushed for it. |
| C002 | Request for an app (by App ID) returned an empty response. | The app is skipped; no dataset item is pushed for it. |
| C003 | No app was found for the given App ID. | The app is skipped; no dataset item is pushed for it. |
| C004 | Request for an app's similar apps failed. | The similar-apps step for that app is skipped; the main app item is unaffected. |
| C005 | Request for an app's similar apps returned an empty response. | The similar-apps step for that app is skipped; the main app item is unaffected. |
| C006 | Request for a page of an app's reviews failed. | Review pagination for that app stops; reviews collected so far are kept. |
| C007 | Request for a page of an app's reviews returned an empty response. | Review pagination for that app stops; reviews collected so far are kept. |
| C008 | Request for an app (by Bundle ID) failed. | The app is skipped; no dataset item is pushed for it. |
| C009 | Request for an app (by Bundle ID) returned an empty response. | The app is skipped; no dataset item is pushed for it. |
| C010 | No app was found for the given Bundle ID. | The app is skipped; no dataset item is pushed for it. |
| C011 | Request for a bundle's similar apps failed. | The similar-apps step for that app is skipped; the main app item is unaffected. |
| C012 | Request for a bundle's similar apps returned an empty response. | The similar-apps step for that app is skipped; the main app item is unaffected. |
| C013 | Request for a page of a bundle's reviews failed. | Review pagination for that app stops; reviews collected so far are kept. |
| C014 | Request for a page of a bundle's reviews returned an empty response. | Review pagination for that app stops; reviews collected so far are kept. |
| C015 | Request for a developer's apps failed. | The developer query is skipped; no items are pushed for it. |
| C016 | Request for a developer's apps returned an empty response. | The developer query is skipped; no items are pushed for it. |
| C017 | No apps were found for the given Developer ID. | The developer query is skipped; no items are pushed for it. |
| C018 | Request for a keyword search page failed. | That search page is skipped. |
| C019 | The backend rate limit was exceeded (HTTP 429). | An error item is pushed to the dataset, the request is retried after a short delay, up to the configured retry limit. |
| C020 | A keyword search page returned an empty response. | That search page is skipped. |
| C098 | An unhandled promise rejection occurred anywhere in the run. | The error is logged and tracked, and the actor exits immediately. |
| C099 | An uncaught exception occurred anywhere in the run. | The error is logged and tracked, and the actor exits immediately. |
| C100 | The run failed to initialize. | The actor exits immediately. |

### Which App Store Scraper Questions Come Up Most Often?

#### Does the App Store Scraper need an App Store URL?

No. The App Store Scraper resolves apps from `appIds`, `bundleIds` or `developerIds` without a full URL. `startUrls` is one of five accepted identifiers rather than a requirement.

#### Which App Store URLs does the App Store Scraper accept?

The App Store Scraper accepts `http` and `https` URLs on an `apple` hostname, in two page shapes: an app detail page whose path contains `/id` and `/app/`, and a developer page whose path contains `/id` and `/developer/`. Other App Store page types are skipped with a warning.

#### Can the App Store Scraper search the App Store by keyword?

Yes. Set `term` to the keyword and `mode` to `search`. The search endpoint is requested page by page, starting at `page` and ending at `endPage`.

#### Does the App Store Scraper charge search-query for every page of a keyword search?

No. `search-query` is charged once per keyword search run, no matter how many pages `endPage` allows. `dataset-item` still applies to the apps returned from every page, except that a paying account's first results page is exempt from that charge.

#### Which App Store storefront does the App Store Scraper use?

`country` sets the App Store storefront country for the run.

#### Which language does the App Store Scraper return?

`lang` sets the response language. Omitting `lang` returns the storefront country's language.

#### How many apps does the App Store Scraper return?

`maxItems` sets the maximum number of top-level application items a run outputs. The default is all.

#### How far does the App Store Scraper paginate?

`endPage` sets the last page requested. Pagination is unbounded by default, and the actor keeps paging until the provider returns an empty page or `maxItems` is reached.

#### Why did the App Store Scraper reject my customMapFunction?

A `customMapFunction` is rejected when it is not a plain synchronous function returning a non-null object, when it throws, or when it takes longer than about 1 second on one item. Async functions, generators and Promises are rejected. The function runs in an isolated sandbox, so only plain JavaScript is available (no network requests, `require` or `console`). The run continues with unmodified items, and the run log explains the rejection in a warning that starts with `Your "customMapFunction" input was rejected:`.

#### What happens when the App Store Scraper finds no matching app?

The app is skipped and no dataset item is pushed for it. The App Store Scraper reports C003 for an App Store ID with no match and C010 for a bundle ID with no match.

#### What happens when the App Store Scraper hits a rate limit?

The App Store Scraper reports C019 on HTTP 429, pushes an error item to the dataset, and retries the request after a short delay, up to the configured retry limit.

#### Does the App Store Scraper discard reviews when a review page fails?

No. Review pagination for that app stops and the reviews collected so far are kept. This applies to C006, C007, C013 and C014.

#### Can the App Store Scraper return similar apps and reviews on the same item?

Yes. `includeSimilarApps` and `includeReviews` both attach their results to the app's dataset item, in the `similarApps` and `reviews` arrays.

#### Which App Store Scraper failures stop the whole run?

Three error codes stop the run: C098 for an unhandled promise rejection, C099 for an uncaught exception, and C100 for a failed initialization. Every other code skips one query and continues.

### Where Do You Get App Store Scraper Support?

The App Store Scraper is published by epctex. Visit epctex.com to see all available products. For custom integrations, reach out through the chat box on epctex.com. For support, contact business@epctex.com.

# Actor input Schema

## `term` (type: `string`):

Keyword to search for on the App Store.

## `startUrls` (type: `array`):

App Store or iTunes detail URLs.

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

Numeric App Store IDs to look up directly, without needing a full URL.

## `bundleIds` (type: `array`):

Bundle identifiers to look up directly (e.g. com.crunchyroll.sfduel).

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

Numeric developer IDs. Returns every application published by each developer.

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

Optional override of the execution path.

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

App Store storefront country.

## `lang` (type: `string`):

Response language. Defaults to the storefront country's language if omitted.

## `page` (type: `integer`):

First page number to request from the search endpoint.

## `endPage` (type: `integer`):

Last page number the actor should request, for both search-result pagination and, when includeReviews is enabled, per-app review pagination. Default: unbounded (the actor keeps paging until the provider returns an empty page or maxItems is reached).

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

When true, fetches reviews for every resolved app and attaches them to the app's dataset item.

## `reviewsSort` (type: `string`):

Sort order used when fetching reviews. Only applies when includeReviews is true.

## `includeSimilarApps` (type: `boolean`):

When true, fetches similar/related apps for every resolved app and attaches them to the app's dataset item.

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

Maximum number of top-level application items the run will output. Default is all.

## `customMapFunction` (type: `string`):

JavaScript function to transform each output app. Use to add computed fields, modify values, or remove unnecessary data. NOT intended for filtering - filtering may result in automatic bans. Must be a single synchronous function, e.g. (object) => { return {...object} }. It runs in a sandbox with plain JavaScript only (no network or require) and about 1 second per item. Leave empty if you do not need to transform the output - an unusable function is ignored and the original items are saved instead.

## `outputConnector` (type: `string`):

Pick an MCP connector you authorized in Console → Settings → API & Integrations → MCP connectors (e.g. Notion, Todoist — any service whose MCP server offers non-destructive write tools works best-effort). The scraped results are written through your own connector — the Actor never sees your third-party credentials. Only non-destructive write tools are used; it can never delete or overwrite your existing data.

## `deliverTarget` (type: `string`):

Where to write into the connector — paste the URL or ID. Notion example: https://www.notion.so/My-Posts-24f8a1b2c3d44e5f8a9b0c1d2e3f4a5b. Leave empty for services that need no target (e.g. Todoist Inbox, Slack). For Notion, share the page or database with your connector first (Notion's ••• → Connections menu), and point at a PAGE for structured columns (the Actor builds the database for you) — an empty database receives only the title (the rest of each record goes to the row's body).

## `deliverMode` (type: `string`):

perPost writes one record per scraped item; summary writes a single roll-up for the run.

## `deliverMaxItems` (type: `integer`):

Cap on how many records perPost mode writes to the connector; ignored in summary mode. Leave empty to deliver everything scraped.

## Actor input object example

```json
{
  "term": "game",
  "startUrls": [
    "https://apps.apple.com/us/app/street-fighter-duel/id6444880223",
    "https://apps.apple.com/us/developer/crunchyroll-games-llc/id1447891276"
  ],
  "appIds": [
    "6444880223"
  ],
  "bundleIds": [
    "com.crunchyroll.sfduel"
  ],
  "developerIds": [
    "1447891276"
  ],
  "country": "us",
  "lang": "en-us",
  "page": 1,
  "includeReviews": false,
  "reviewsSort": "recent",
  "includeSimilarApps": false,
  "maxItems": 50,
  "customMapFunction": "(object) => { return {...object} }",
  "deliverMode": "perPost"
}
```

# 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 = {
    "term": "game",
    "startUrls": [
        "https://apps.apple.com/us/app/street-fighter-duel/id6444880223",
        "https://apps.apple.com/us/developer/crunchyroll-games-llc/id1447891276"
    ],
    "appIds": [
        "6444880223"
    ],
    "bundleIds": [
        "com.crunchyroll.sfduel"
    ],
    "developerIds": [
        "1447891276"
    ],
    "country": "us",
    "lang": "en-us",
    "page": 1,
    "maxItems": 50,
    "customMapFunction": (object) => { return {...object} }
};

// Run the Actor and wait for it to finish
const run = await client.actor("epctex/appstore-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 = {
    "term": "game",
    "startUrls": [
        "https://apps.apple.com/us/app/street-fighter-duel/id6444880223",
        "https://apps.apple.com/us/developer/crunchyroll-games-llc/id1447891276",
    ],
    "appIds": ["6444880223"],
    "bundleIds": ["com.crunchyroll.sfduel"],
    "developerIds": ["1447891276"],
    "country": "us",
    "lang": "en-us",
    "page": 1,
    "maxItems": 50,
    "customMapFunction": "(object) => { return {...object} }",
}

# Run the Actor and wait for it to finish
run = client.actor("epctex/appstore-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 '{
  "term": "game",
  "startUrls": [
    "https://apps.apple.com/us/app/street-fighter-duel/id6444880223",
    "https://apps.apple.com/us/developer/crunchyroll-games-llc/id1447891276"
  ],
  "appIds": [
    "6444880223"
  ],
  "bundleIds": [
    "com.crunchyroll.sfduel"
  ],
  "developerIds": [
    "1447891276"
  ],
  "country": "us",
  "lang": "en-us",
  "page": 1,
  "maxItems": 50,
  "customMapFunction": "(object) => { return {...object} }"
}' |
apify call epctex/appstore-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,epctex/appstore-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/1juBl6QedWJ3bwbgZ/builds/7gln2yq2jqLUa31WC/openapi.json
