# Google Play App Scraper API — Version, Rating & Installs (`kittiwake/google-play-app-scraper`) Actor

Google Play scraper for your own list of apps: title, developer, category, rating, installs band, price, ads, in-app purchases, version and last update per package ID. Watch mode reports only what changed since the last run. No reviews, no developer contact data.

- **URL**: https://apify.com/kittiwake/google-play-app-scraper.md
- **Developed by:** [Kittiwake Data](https://apify.com/kittiwake) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 app checkeds

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

## Google Play App Scraper API — Version, Rating & Installs

**A Google Play scraper for the apps you care about.** Give it a list of package IDs and get back
one clean row per app: title, developer, category, star rating, ratings count, installs band, price,
ads, in-app purchases, current version and last-update date. Run it on a schedule in **watch mode**
and it becomes an **app store API for ASO competitor monitoring**: a row only when a competitor
ships a new version, moves a rating, crosses an installs band or changes its price.

Built by **kittiwake**. Metadata only — no reviews, no developer contact data (see below).

### What it is for

- **ASO agencies and app studios** — watch a set of competitor apps weekly and see who shipped,
  whose rating slipped and who crossed 10M installs, without opening forty store pages.
- **Product and market research** — a table of an app category's price points, ad and IAP
  models and update cadence, from a list of package IDs you already have.
- **Portfolio owners** — a daily check that your own listings show the version, price and content
  rating you expect in each storefront.

### Quick start

**Scan three apps in the US store:**

```json
{
  "appIds": ["com.spotify.music", "com.duolingo", "org.telegram.messenger"]
}
```

**Weekly watch, German storefront, only what changed:**

```json
{
  "appIds": ["com.duolingo", "com.spotify.music", "de.danoeh.antennapod"],
  "country": "de",
  "language": "de",
  "mode": "watch"
}
```

The first run of a list records a **baseline**: every app gets a row tagged `baseline`, and nothing
is reported as a change, because there is nothing earlier to compare with. From the second run on,
`changeType` says what moved.

### Input

| field | type | default | what it does |
|---|---|---|---|
| `appIds` | string\[] | **required** | Package IDs such as `com.spotify.music`, or full Play details URLs. Duplicates read once; invalid entries skipped and listed in `RUN_SUMMARY` |
| `country` | string | `us` | Storefront (two letters). Price, currency and content rating depend on it |
| `language` | string | `en` | Page language, e.g. `en`, `de`, `pt-BR` |
| `mode` | `scan` | `watch` | `scan` | `scan`: a row per app every run. `watch`: a row only for apps that changed (plus the first-run baseline) |
| `stateStoreName` | string | `google-play-app-scraper-state` | Named key-value store for last snapshots. One name per watchlist |
| `requestDelayMs` | integer | `2000` | Pause between page requests. Minimum 1500 |

### Output

One row per app:

```json
{
  "appId": "com.mojang.minecraftpe",
  "title": "Minecraft: Dream it, Build it!",
  "developerName": "Mojang",
  "genre": "Arcade",
  "genreId": "GAME_ARCADE",
  "scoreAvg": 4.3072,
  "ratingsCount": 5842806,
  "installsBand": "50,000,000+",
  "minInstalls": 50000000,
  "free": false,
  "price": 6.99,
  "currency": "USD",
  "containsAds": false,
  "offersIAP": true,
  "version": "1.26.52.3",
  "updatedAt": "2026-09-25T17:26:28.000Z",
  "released": "2011-08-16T06:17:21.000Z",
  "contentRating": "Everyone 10+",
  "country": "us",
  "language": "en",
  "url": "https://play.google.com/store/apps/details?id=com.mojang.minecraftpe&hl=en&gl=us",
  "status": "ok",
  "changeType": ["version-changed", "updated"],
  "previous": { "version": "1.26.40.1", "updatedAt": "2026-09-01T00:00:00.000Z" },
  "checkedAt": "2026-09-26T12:00:00.000Z"
}
```

- `changeType`: `baseline` (first sight), `version-changed`, `updated`, `score-changed` (a move of
  0.1 stars or more), `installs-band-changed`, `price-changed`. `previous` holds the old values.
- `version` is `"Varies with device"` when the store shows that instead of a number — common for
  large apps. `updatedAt` still tells you when a new build shipped.
- `status`: `ok`, `not-found` (Google Play has no page for that ID in that storefront) or
  `parse-failed` (the page loaded but could not be read). Only `ok` rows are charged.
- Dates are ISO 8601 in UTC, so a release late in the US evening can show the next calendar day.
- A run summary (checked, changes, not found, parse failures, invalid IDs) is saved as `RUN_SUMMARY`
  in the run's key-value store.

### What you pay for

| event | price | when |
|---|---|---|
| **App checked** | $0.002 | one app read, compared and its state saved — only when all three succeed |
| App change detected | $0.01 | the app changed since the last run. Once per app per run, however many things changed; never on the baseline run |

Apify Store discounts apply by subscription plan: the prices above are the Free-plan prices;
Starter pays 10% less, Scale 20% less and Business 30% less on every event.

Nothing is charged for an invalid package ID, a `not-found` app, a `parse-failed` page or a failed
request. The run stops at the spending limit you set; rows already saved are yours. 1,000 apps
checked costs $2.

### What this Actor deliberately does not collect

- **No developer contact data.** Individual developers on Google Play publish their personal name,
  email and home address. This Actor never reads the developer's email, postal address, website,
  phone, legal name or privacy-policy link — only the display name shown under the app title.
  A test fails the build if any of those fields, or any email address, reaches a row.
- **No reviews and no reviewer names.** Reviews are personal data about the people who wrote them.
- **No search, no top charts, no data-safety pages.** Google Play's `robots.txt` disallows
  `/store/search`, `/store/apps/datasafety*` and `/store/apps/collection/p3_details_*`. This Actor
  reads **only** `/store/apps/details?id=…` pages, which `robots.txt` allows, and it re-reads
  `robots.txt` at the start of every run and stops if that ever changes. So you bring the package
  IDs; it does not discover apps for you.
- **No evasion.** One identifying User-Agent, a pause between requests, no proxies or header
  rotation. If Google Play refuses a request (HTTP 429 or 403), the run fails and says so.

### FAQ

**Where do I find an app's package ID?** In its Play Store URL, after `id=` — for
`https://play.google.com/store/apps/details?id=com.duolingo` it is `com.duolingo`. You can paste the
whole URL.

**How do I get only the changes?** Set `mode` to `watch` and schedule the Actor. Unchanged apps
write no row (they are still checked and charged `app-checked`).

**Can I track several countries?** Run once per country. State is kept per app, country and
language, so each storefront has its own baseline.

**Why did a run return `parse-failed`?** Google changes its page format from time to time. Those
rows are not charged; if every app in a run fails to parse, the run fails outright so a schedule
alerts you. Open an issue with the run ID.

**Can I export to CSV or Excel?** Yes — JSON, CSV, Excel or XML, or read it over the Apify API.

### Source

Data is read from the public Google Play app details pages at `play.google.com`. This Actor is not
affiliated with or endorsed by Google. Google Play is a trademark of Google LLC.

### Support

Use the **Issues** tab on this Actor. Include the run ID and the input you used.

# Actor input Schema

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

Google Play package IDs, e.g. com.spotify.music — the id= part of the app's Play Store URL. You can also paste the full details URL. Duplicates are read once; anything that is not a package ID is skipped and listed in the run summary.

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

Two-letter country code of the Play storefront (gl). Price, currency, availability and content rating depend on it.

## `language` (type: `string`):

Language of the page (hl), e.g. en, de, pt-BR. Category names, the installs band's number format and content-rating labels follow it.

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

scan: a row for every app, every run. watch: a row only for apps that changed since the last run (plus a baseline row the first time an app is seen). Both keep state, so changeType is filled in either mode.

## `stateStoreName` (type: `string`):

Named key-value store that keeps the last snapshot of each app between runs. Use a different name per watchlist if you run several.

## `requestDelayMs` (type: `integer`):

Pause between page requests. Values below 1500 are raised to 1500.

## Actor input object example

```json
{
  "appIds": [
    "com.spotify.music",
    "com.duolingo",
    "org.telegram.messenger"
  ],
  "country": "us",
  "language": "en",
  "mode": "scan",
  "stateStoreName": "google-play-app-scraper-state",
  "requestDelayMs": 2000
}
```

# Actor output Schema

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

No description

## `changes` (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 = {
    "appIds": [
        "com.spotify.music",
        "com.duolingo",
        "org.telegram.messenger"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("kittiwake/google-play-app-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 = { "appIds": [
        "com.spotify.music",
        "com.duolingo",
        "org.telegram.messenger",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("kittiwake/google-play-app-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 '{
  "appIds": [
    "com.spotify.music",
    "com.duolingo",
    "org.telegram.messenger"
  ]
}' |
apify call kittiwake/google-play-app-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kittiwake/google-play-app-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/j9hpsaW5R3GpyxW8Y/builds/YMjPuCqBwokCg5ey7/openapi.json
