# App Store Screenshot Monitor (`junipr/app-store-screenshot-monitor`) Actor

Monitor public app listing screenshots and creative assets for visual/listing changes.

- **URL**: https://apify.com/junipr/app-store-screenshot-monitor.md
- **Developed by:** [junipr](https://apify.com/junipr) (community)
- **Categories:** Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $15.00 / 1,000 image inspecteds

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/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 Store Screenshot Monitor

Compare app listing screenshot sets by locale and device. The actor can download the actual image bytes, compute SHA-256 hashes, read PNG/JPEG/WebP dimensions, enforce byte limits, and detect creative replacements, dimension changes, reorders, additions, removals, URL changes, and failed inspections.

### Input Sources

Supply `previousScreenshots` and `currentScreenshots` arrays containing image URLs and placement metadata. Existing hash, dimension, and byte-size metadata can be included for deterministic manifest comparisons.

The `previousSnapshot` and `currentSnapshot` fields also accept JSON arrays, `{ "screenshots": [...] }` objects, or Apple lookup responses containing `screenshotUrls`, `ipadScreenshotUrls`, or `appletvScreenshotUrls`.

Set `inspectImages` to `true` to retrieve every supplied HTTP(S) image URL and replace supplied metadata with verified byte-derived evidence. Private, loopback, credentialed, and non-HTTP URLs are rejected by default.

### Example Input

```json
{
  "targets": [
    {
      "sourceId": "acme-ios-en-us",
      "sourceUrl": "https://apps.apple.com/app/acme/id123456789",
      "appId": "123456789",
      "platform": "apple-app-store",
      "locale": "en-US",
      "device": "iphone-6.9",
      "previousScreenshots": [
        { "assetId": "iphone-01", "url": "https://example.com/onboarding-v1.png", "position": 1, "sha256": "1111111111111111111111111111111111111111111111111111111111111111", "width": 1320, "height": 2868, "inspectionStatus": "supplied" }
      ],
      "currentScreenshots": [
        { "assetId": "iphone-01", "url": "https://example.com/onboarding-v2.png", "position": 1, "sha256": "2222222222222222222222222222222222222222222222222222222222222222", "width": 1320, "height": 2868, "inspectionStatus": "supplied" }
      ]
    }
  ],
  "inspectImages": false,
  "maxImagesPerTarget": 20,
  "maxImageBytes": 10000000,
  "includeUnchanged": false,
  "includeReport": true
}
```

### Output

Each row contains:

- App placement: `appId`, `platform`, `locale`, `device`, `assetId`, and old/new positions
- Image evidence: old/new URL, SHA-256, width, height, byte size, MIME type, and dimension change
- Verification evidence: old/new inspection status and explicit inspection errors
- Decision fields: `changeType`, `status`, `severity`, `score`, `summary`, and `recommendation`

`inspectionStatus` distinguishes `verified` byte-derived metadata from `supplied` manifest metadata and `failed` retrievals.

Empty manifests and failed image retrievals also set `diagnosticCode` and `sourceError`, so blocked checks remain visible in the dataset.

### Example Output

```json
{
  "appId": "123456789",
  "platform": "apple-app-store",
  "locale": "en-US",
  "device": "iphone-6.9",
  "assetId": "iphone-01",
  "oldPosition": 1,
  "newPosition": 1,
  "oldHash": "1111111111111111111111111111111111111111111111111111111111111111",
  "newHash": "2222222222222222222222222222222222222222222222222222222222222222",
  "oldWidth": 1320,
  "newWidth": 1320,
  "oldInspectionStatus": "supplied",
  "newInspectionStatus": "supplied",
  "changeType": "content-changed"
}
```

### PPE Pricing

Event prices include Apify platform usage for the configured fixed-inclusive model.

- `actor-start`: $0.05000 once per paid run
- `image-inspected`: $0.01500 per previous or current image inspected
- `issue-detected`: $0.01300 per emitted changed or failed row
- `report-generated`: $0.10000 when report artifacts are written

The actor charges before each image inspection and stops before additional output when the charge limit cannot cover the next paid event.

### Limits

- Up to 50 listing targets and 100 images per side per target.
- Maximum image response size is configurable up to 25 MB.
- Supply the final CDN asset URL; redirects are rejected during image inspection.
- The actor accepts screenshot URLs or listing API manifests; it does not use a browser to discover JavaScript-rendered store creatives.
- Hash equality proves byte equality, not perceptual similarity. Re-encoded but visually identical images appear as content changes.

# Actor input Schema

## `targets` (type: `array`):

Capped prior/current screenshot comparisons by app, locale, and device.

## `sourceUrl` (type: `string`):

Listing URL attached to output rows. Screenshot assets must be supplied separately.

## `appId` (type: `string`):

Apple app ID, Google Play package name, or internal app identifier.

## `platform` (type: `string`):

Store associated with the screenshot set.

## `locale` (type: `string`):

Default locale for screenshot rows.

## `device` (type: `string`):

Default device or display class.

## `previousScreenshots` (type: `array`):

Prior screenshot URLs and optional supplied hashes/dimensions.

## `currentScreenshots` (type: `array`):

Current screenshot URLs and optional supplied hashes/dimensions.

## `previousSnapshot` (type: `string`):

Optional JSON manifest or Apple lookup response.

## `currentSnapshot` (type: `string`):

Optional JSON manifest or Apple lookup response.

## `inspectImages` (type: `boolean`):

Download each supplied screenshot URL and compute SHA-256, dimensions, byte size, and MIME type.

## `fetchTimeoutMs` (type: `integer`):

Per-image timeout in milliseconds.

## `maxImageBytes` (type: `integer`):

Reject an image that exceeds this byte limit.

## `maxTargets` (type: `integer`):

Maximum listing comparisons.

## `maxImagesPerTarget` (type: `integer`):

Maximum previous and current screenshots per target.

## `includeUnchanged` (type: `boolean`):

Emit screenshot pairs with no detected changes.

## `includeReport` (type: `boolean`):

Write JSON result, JSON summary, and Markdown artifacts.

## `dryRun` (type: `boolean`):

Validate input without downloads, charges, or output rows.

## `debug` (type: `boolean`):

Enable debug logging.

## Actor input object example

```json
{
  "targets": [],
  "sourceUrl": "",
  "appId": "",
  "platform": "apple-app-store",
  "locale": "en-US",
  "device": "unspecified",
  "previousScreenshots": [],
  "currentScreenshots": [],
  "previousSnapshot": "",
  "currentSnapshot": "",
  "inspectImages": false,
  "fetchTimeoutMs": 10000,
  "maxImageBytes": 10000000,
  "maxTargets": 1,
  "maxImagesPerTarget": 20,
  "includeUnchanged": false,
  "includeReport": true,
  "dryRun": false,
  "debug": false
}
```

# Actor output Schema

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

Screenshot-level comparison rows.

## `resultsJson` (type: `string`):

All screenshot comparison rows.

## `summary` (type: `string`):

Change and inspection-failure counts.

## `markdownReport` (type: `string`):

Readable creative change digest.

# 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("junipr/app-store-screenshot-monitor").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("junipr/app-store-screenshot-monitor").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 junipr/app-store-screenshot-monitor --silent --output-dataset

```

## MCP server setup

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

```

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/rRzjj6kvpXwE3II2G/builds/VA3XYmCgCGe3UK36a/openapi.json
