# App Intelligence Bundle: Listing, Charts, Reviews & Developer (`glitchbound/app-intel`) Actor

One app in, a full profile out. Pulls the store listing, scans the country top charts for its position, samples recent reviews into a star breakdown, and lists everything else the developer ships. Four scrapers, one run, joined on the app.

- **URL**: https://apify.com/glitchbound/app-intel.md
- **Developed by:** [Daniel Meshulam](https://apify.com/glitchbound) (community)
- **Categories:** AI, Lead generation, E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 app profiles

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/platform/actors/running/actors-in-store#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 Intelligence Bundle: Listing, Charts, Reviews & Developer

Give it an app. Get back the listing, its top-chart position by country, a star
breakdown of recent reviews, and everything else that developer ships.

```json
{ "appIds": ["324684580", "com.spotify.music"], "chartCountries": ["us", "gb"] }
```

Four scrapers, one run, joined on the app. No key, no login.

### Why this is a bundle and not four runs

You could run the four Actors separately. Two of the joins are the reason not to.

**The developer name is not an input you have.** It comes out of the listing, and
the two stores spell the same company differently: Apple returns `Spotify`,
Google returns `Spotify AB`. So the portfolio lookup cannot happen until the
listing has, and it has to try both spellings. That ordering is the bundle.

**Chart position is not a per-app query.** The charts endpoint answers "what is
in the US top-free chart", not "where is this app". Answering the question you
actually have means pulling the chart and scanning it. Here `isCharting` is
`false` when you asked and it was not there, which is a real answer, rather than
a missing field you have to interpret. It is `null` when no chart covering that
app was read at all, which is a different answer and says so.

### What one row gives you

| Field | |
|---|---|
| `title`, `appId`, `platform`, `developer`, `developerId` | identity |
| `storeRating`, `storeRatingCount` | the store's own lifetime numbers |
| `price`, `isFree`, `version`, `updatedAt`, `genres` | listing state |
| `isCharting`, `chartPositions` | rank per country, chart and category |
| `reviews.starCounts`, `reviews.negativeShare`, `reviews.sampleMeanRating` | the recent sample, which is where a slide shows up first |
| `recentNegativeReviews` | the five most recent 1-2 star reviews, verbatim |
| `developerAppCount`, `developerOtherApps` | the rest of the portfolio, by rating count |
| `incompleteParts` | which step fell short, if any |

The store's lifetime rating and the recent sample mean are deliberately both
present. A 4.7 lifetime average with a 2.9 recent sample is the single most
useful thing this returns, and averaging them together would hide it.

### Three output views

The dataset ships with **Overview**, **Chart positions** and **Review
sentiment** tabs, so you can read the part you came for without writing a
transformation.

### Partial profiles are labelled, not hidden

A member Actor that fails does not fail the run. A listing without reviews is
still worth having, so the missing part is named in `incompleteParts` and the
rest is returned. What is never returned is a profile that looks complete and
is not.

### What it costs

Each member Actor charges its own normal rate, because the runs happen under
your account with your token: **App Store & Google Play Scraper**, **App Store
Top Charts Scraper**, **App Store & Google Play Reviews Scraper** and **App Store
Developer Portfolio Scraper**. This Actor adds one small charge per composed
profile on top.

That is the honest accounting. A bundle that hides the cost of what it triggers
is a trap, so: profiling two apps with charts in two countries and 100 reviews
each is four member runs, and the reviews are the expensive part at $0.20 per
1,000.

Set a **maximum charge per run** in the run options for a hard ceiling. It is
honoured, and the Actor stops cleanly rather than being killed mid-write.

### Use cases

- **ASO and competitive tracking**: schedule it weekly on a competitor set and watch rating, version cadence and chart rank move together
- **Publisher due diligence**: one call tells you how many apps a developer ships and how the rest of them are rated
- **Release regression**: `recentNegativeReviews` carries `appVersion`, so a bad release shows up as complaints clustered on one version
- **Market entry**: run the same app across `chartCountries` to see where it charts and where it does not
- **Feeding an LLM**: one composed JSON object per app rather than four datasets to reconcile

### Notes

- **Apple charts only.** Google Play publishes no equivalent public chart feed, so `chartPositions` covers the iOS side of a profile. The Android listing and reviews are unaffected.
- **The developer portfolio is an iOS catalogue.** An Android-only developer name may return nothing; that shows up in `incompleteParts` rather than as a silent empty list.
- Reviews are a recent sample, not the full history. Both stores expose only a recent window, and `reviewsSampled` tells you how many actually came back.
- `chartCountries` costs one chart pull per country per chart type, so three countries and two chart types is six pulls. Trim it if you only care about one market.

### FAQ

#### Do I need an App Store or Google Play API key?

No. Every member Actor reads public store endpoints with no key and no login.

#### Which identifier do I pass?

The App Store takes a numeric id (`324684580`) or an iOS bundle id; Google Play
takes the package name (`com.spotify.music`). Pass both forms of the same app to
get a profile per store in one run. Passing an Android package and expecting iOS
data back returns an empty iOS side, not an error.

#### Why is `isCharting` false when I know the app is popular?

Because it was not in the charts you asked for. Charts are per country, per chart
type and per category, and a very popular app can be absent from `top-free` while
sitting high in `top-grossing`. The default scans free and paid; add
`top-grossing` if that is the question.

On an **Android** row `isCharting` is `null` rather than `false`. The chart feeds
read here are Apple's, so a Play package is never going to appear in one, and
saying `false` would claim a check that never happened.

#### How do I spot a bad release?

Compare `storeRating`, which is lifetime, against `reviews.sampleMeanRating`,
which is the recent window. A gap between them is a change in sentiment. Then
read `recentNegativeReviews`, where each entry carries the `appVersion` it was
written against.

#### Can I track this over time?

Yes, and that is the intended use. Run it on an Apify Schedule; every run
appends to the dataset with its own timestamp, so a few weeks of runs is a
rating and chart history that neither store publishes.

#### Why is one part of my profile empty?

Check `incompleteParts`. It names the step that fell short and why, whether that
was a timeout, a member Actor error, or an identifier that resolved on one store
and not the other.

#### Is it cheaper than running the four Actors myself?

No, and it does not claim to be. The member Actors charge the same either way.
What you save is the ordering, the developer-name join, the chart scan, and
reconciling four datasets whose fields do not line up.

# Actor input Schema

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

The apps to profile. The App Store wants a numeric id or an iOS bundle id (324684580 or com.spotify.client); Google Play wants the package name (com.spotify.music). Pass both forms of the same app to get a profile for each store in one run.

## `chartCountries` (type: `array`):

Two-letter country codes. For each one the whole chart is pulled and scanned for your app, so 'not charting' is a real answer rather than an empty result. Apple charts only, so this applies to the iOS side of a profile. Leave empty to skip chart lookups entirely.

## `chartTypes` (type: `array`):

Which charts to scan. Defaults to both free and paid, which is usually what you want since you may not know which one the app is in.

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

How many recent reviews to pull per app before reducing them to a star breakdown and the most recent complaints. Set to 0 to skip reviews.

## `includePortfolio` (type: `boolean`):

Look up everything else the developer ships. The developer name is not something you have to supply: it is read off the listing. Apple and Google spell the same company differently, so both spellings are looked up. This is an iOS catalogue, so an Android-only developer name may come back empty.

## `memberTimeoutSecs` (type: `integer`):

How long to wait for each member Actor before giving up on that part of the profile and continuing. A step that times out is reported in incompleteParts rather than failing the run.

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

A hard ceiling on rows for the entire run, across every target. The per-target limits above cap each one separately, so fifty targets at twenty each is still a thousand rows; this caps the total. The run stops cleanly when it is reached and nothing beyond that point is fetched or charged. Leave empty for no ceiling.

## Actor input object example

```json
{
  "appIds": [
    "324684580",
    "com.spotify.music"
  ],
  "chartCountries": [
    "us"
  ],
  "chartTypes": [
    "top-free",
    "top-paid"
  ],
  "maxReviewsPerApp": 100,
  "includePortfolio": true,
  "memberTimeoutSecs": 600
}
```

# 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": [
        "324684580",
        "com.spotify.music"
    ],
    "chartCountries": [
        "us"
    ],
    "chartTypes": [
        "top-free",
        "top-paid"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("glitchbound/app-intel").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": [
        "324684580",
        "com.spotify.music",
    ],
    "chartCountries": ["us"],
    "chartTypes": [
        "top-free",
        "top-paid",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("glitchbound/app-intel").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": [
    "324684580",
    "com.spotify.music"
  ],
  "chartCountries": [
    "us"
  ],
  "chartTypes": [
    "top-free",
    "top-paid"
  ]
}' |
apify call glitchbound/app-intel --silent --output-dataset

```

## MCP server setup

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

```

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/SWTk4p8fi1hhLNbgF/builds/0b8tsOYa59FaQzA8J/openapi.json
