# App Details Scraper (`juanoox/app-details`) Actor

Scrape app metadata, keyword search rankings and top charts from the Apple App Store and Google Play in one run, with a single unified schema. Rating, installs, price, developer, category and rank position.

- **URL**: https://apify.com/juanoox/app-details.md
- **Developed by:** [Juan ignacio Veltri](https://apify.com/juanoox) (community)
- **Categories:** Developer tools, SEO tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 results

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 Details Scraper — App Store & Google Play metadata, keyword rankings and charts

Get app metadata, **keyword search positions** and **top charts** from the Apple App Store and
Google Play, with one Actor and one output schema.

The metadata is the easy part — plenty of Actors return it. What this one adds is the **context**:
every row carries where the app was found. `rank`, `query` and `chart` tell you *"this app sits 9th
for "meditation" in Mexico"*, which is the number an ASO team actually acts on.

***

### Three ways to ask, one table out

| Ask | What you get | App Store | Google Play |
|---|---|:--:|:--:|
| `apps` — a URL, ID, bundle or name | full metadata | ✅ | ✅ |
| `searchQueries` — a keyword | ranked results with position | ✅ | ✅ |
| `charts` — `top-free` / `top-paid` | ranked results with position | ✅ | — |

Google Play does not publish a chart feed comparable to Apple's, so `charts` returns App Store rows
only. That is stated here rather than sold and quietly empty.

***

### Quick start

Where does my app rank for a keyword, in three markets?

```json
{
  "searchQueries": ["meditation", "sleep sounds"],
  "countries": ["US", "GB", "MX"],
  "resultsPerQuery": 50
}
```

Metadata for a set of apps, mixing formats and stores freely:

```json
{
  "apps": [
    "https://apps.apple.com/us/app/duolingo/id570060128",
    "com.spotify.music",
    "notion"
  ]
}
```

Today's top free apps in five countries:

```json
{
  "charts": ["top-free"],
  "countries": ["US", "BR", "MX", "ES", "AR"],
  "resultsPerQuery": 100
}
```

A reference that already names its store — a URL, an Apple ID, an Android bundle — is always
honoured. Only a plain name is searched in both stores.

***

### Output

One row per app, identical shape from both stores:

```json
{
  "id": "app_store:570060128:US",
  "url": "https://apps.apple.com/us/app/duolingo-language-lessons/id570060128",
  "store": "app_store",
  "appId": "570060128",
  "name": "Duolingo - Language Lessons",
  "developer": "Duolingo",
  "category": "Education",
  "rating": 4.7,
  "ratingCount": 2183940,
  "isFree": true,
  "version": "8.11.1",
  "contentRating": "4+",
  "storeCountry": "US",
  "rank": 3,
  "query": "language learning",
  "chart": null
}
```

`id` is prefixed by store **and market**, so the same app in two countries stays two rows and never
collides — which is what you need to compare markets.

#### What each store publishes

| Field | App Store | Google Play |
|---|:--:|:--:|
| Name, developer, category, rating, rating count | ✅ | ✅ |
| Description | ✅ | short description only |
| Price and currency | ✅ | ✅ |
| Version, release date, last update | ✅ | — |
| Screenshots | ✅ | — |
| Content rating | ✅ | ✅ |

Fields a store does not publish come back as `null`. That is information, not a gap.

***

### What it is good for

- **Keyword rank tracking** — schedule it, turn `includeSeen` off, and watch positions move.
- **Market comparison** — the same keyword across `countries` shows where you rank and where you don't.
- **Competitor monitoring** — who else shows up for your keywords, and what their rating is.
- **Chart watching** — see what entered `top-free` today in each market.

***

### Notes and limits

- **Charts are App Store only.** Google Play has no equivalent public feed.
- **`top-grossing` is not offered.** It appears in older Apple documentation and now returns HTTP
  404, so it is rejected at input validation rather than failing mid-run.
- **Google Play search costs one extra request per app.** Its results page only exposes bundle IDs,
  so each app's page is fetched to fill the row — and only for apps that already passed your
  filters, so a strict filter makes a run cheaper, not more expensive.
- **Ranking positions are per market.** The same keyword returns a different order in every country.

***

### Pricing

Pay per result. You are charged for apps that actually land in your dataset — filtered out and
duplicate rows cost nothing.

# Actor input Schema

## `apps` (type: `array`):

Specific apps. Accepts a store URL from either store, an Apple numeric ID, an Android bundle, or just the app name.

## `searchQueries` (type: `array`):

Keywords to search. Returns what the store shows, in order, and every result carries its position in `rank`. This is the number ASO teams act on.

## `charts` (type: `array`):

Charts to read: top-free and/or top-paid. App Store only — Google Play publishes no equivalent feed. Apple retired top-grossing and it now returns 404.

## `stores` (type: `string`):

Which stores to query. A reference that already names its store (a URL or an ID) is always honoured.

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

Country codes. Searches and charts are per market: the same keyword ranks differently in every country, and that is where much of the value is.

## `resultsPerQuery` (type: `integer`):

How many apps to return for each keyword and each chart.

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

Drop apps below this rating. Leave empty for no filter.

## `onlyFree` (type: `boolean`):

Drop paid apps.

## `categoryKeywords` (type: `array`):

Only apps whose category mentions any of these. Ignores case and accents.

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

Total cap for the run, across apps, searches and charts. 0 = no cap.

## `includeSeen` (type: `boolean`):

Turn it off to skip apps returned by previous runs. Useful to catch when a new app enters a chart.

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

Parallel requests.

## `maxRequestsPerMinute` (type: `integer`):

Rate ceiling. Both stores are free and public: limiting the pace is courtesy to whoever leaves the data open.

## `maxRequestRetries` (type: `integer`):

How many times to retry a failed request before dropping it.

## Actor input object example

```json
{
  "apps": [
    "https://apps.apple.com/us/app/duolingo/id570060128"
  ],
  "searchQueries": [],
  "charts": [],
  "stores": "both",
  "countries": [
    "US"
  ],
  "resultsPerQuery": 50,
  "onlyFree": false,
  "maxItems": 200,
  "includeSeen": true,
  "maxConcurrency": 5,
  "maxRequestsPerMinute": 120,
  "maxRequestRetries": 3
}
```

# Actor output Schema

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

No description

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

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "apps": [
        "https://apps.apple.com/us/app/duolingo/id570060128"
    ],
    "searchQueries": [],
    "charts": [],
    "countries": [
        "US"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("juanoox/app-details").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 = {
    "apps": ["https://apps.apple.com/us/app/duolingo/id570060128"],
    "searchQueries": [],
    "charts": [],
    "countries": ["US"],
}

# Run the Actor and wait for it to finish
run = client.actor("juanoox/app-details").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 '{
  "apps": [
    "https://apps.apple.com/us/app/duolingo/id570060128"
  ],
  "searchQueries": [],
  "charts": [],
  "countries": [
    "US"
  ]
}' |
apify call juanoox/app-details --silent --output-dataset

```

## MCP server setup

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

```

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/hVtrXsH4jfFpbOexo/builds/FxMFGAxR0dY68RRS9/openapi.json
