# Shopify Price, Stock & Catalog Monitor — Batch + Diff (`johnatan029/shopify-price-catalog-monitor`) Actor

Competitor price tracking for Shopify stores: batch of store domains, diff mode returns only CHANGE EVENTS (price, stock, new/removed products) plus a free per-store summary every run. Public /products.json feed — no login, no browser. Not affiliated with Shopify.

- **URL**: https://apify.com/johnatan029/shopify-price-catalog-monitor.md
- **Developed by:** [Johnn Mottin](https://apify.com/johnatan029) (community)
- **Categories:** E-commerce, Business, Automation
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 change events

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

## Shopify Price, Stock & Catalog Monitor — Batch + Diff

**You found out your competitor cut prices three days after your customers did.** Price-monitoring SaaS starts at $99/month and sells by the seat; generic "page change" watchers can't tell a price drop from a banner swap. This Actor watches the public `/products.json` feed of any standard Shopify store and returns **only the change events**: price changes (with old/new/Δ%), restocks and sell-outs, new products, removed products, new variants. No login, no browser, no LLM.

**Not affiliated with, sponsored by, or endorsed by Shopify Inc. or any store monitored.** All data comes from each store's public `/products.json` feed and remains subject to that store's terms.

Who it's for:

- **Merchants and dropshippers:** track competitor stores daily — "these 3 of my 20 rivals cut prices last night" straight to Slack, Sheets or your CRM via Apify integrations.
- **Brands and suppliers:** watch resellers for MAP violations (price below the floor) and stock-outs.
- **Analysts / agencies:** catalog velocity (launches, delistings) across a niche, without maintaining scrapers.

### How it works

You provide store **domains** (e.g. `gymshark.com`). Default mode is **monitor**: the first run of each store creates a free baseline; every later run diffs the live catalog against the saved state and writes one record per change event. **Every monitor run also writes one free `STORE_SUMMARY` record per store** — so a day with no changes still yields "monitored: 631 products checked, 0 changes", which is exactly what a monitor is for. `snapshot` mode is a one-off full catalog export instead.

**You are only charged for change events (or snapshot products) actually written** — the free per-store summaries, filtered events and everything discarded by caps cost you nothing.

### Input

Copy-paste ready (daily competitor watch):

```json
{
  "stores": ["gymshark.com", "colourpop.com"],
  "mode": "monitor",
  "minPriceDeltaPct": 0
}
```

| Field | Type | Default | Description |
|---|---|---|---|
| `stores` | array | required | 1–50 domains or URLs; protocol/www/path stripped automatically |
| `mode` | string | `monitor` | `monitor` (change events + free summaries) or `snapshot` (full export) |
| `collectionHandle` | string | – | Watch a single collection (e.g. `best-sellers`) instead of the whole catalog |
| `eventTypes` | array | all 5 | Any of `PRICE_CHANGE`, `STOCK_CHANGE`, `NEW_PRODUCT`, `REMOVED_PRODUCT`, `NEW_VARIANT` |
| `minPriceDeltaPct` | number | 0 | Ignore price changes smaller than this absolute % — filtered events are free |
| `maxProductsPerStore` | integer | 5000 | Safety cap per store (250/page until the catalog ends) |
| `maxResults` | integer | 5000 | Global cap on charged records. If it truncates a store, that store's diff state is **not** advanced — nothing is silently lost |
| `pageDelayMs` | integer | 500 | Polite pacing between pages of the same store (floor 250 ms, deliberate) |

### Output

One dataset record per change event (`recordType: "CHANGE_EVENT"`), e.g.:

```json
{
  "store": "gymshark.com",
  "recordType": "CHANGE_EVENT",
  "eventType": "PRICE_CHANGE",
  "productId": "6806936649930",
  "title": "Gymshark Training Straight Leg Leggings",
  "url": "https://gymshark.com/products/gymshark-training-straight-leg-leggings",
  "variantId": "39799616536778",
  "variantTitle": "XS / Black",
  "oldPrice": 42.0,
  "newPrice": 21.0,
  "priceDeltaPct": -50.0,
  "oldAvailable": true,
  "newAvailable": true,
  "detectedAt": "2026-07-30T13:00:00.000Z",
  "previousCheckedAt": "2026-07-29T13:00:00.000Z"
}
```

Plus one **free** `STORE_SUMMARY` per store per monitor run (`status`, `productsChecked`, `variantsChecked`, `eventCounts`, `error` when the store failed) and, in snapshot mode, one `PRODUCT` record per product (variants embedded, min/max price, availability).

`STOCK_CHANGE` events carry `stockDirection`: `RESTOCK` or `OUT_OF_STOCK`. Availability comes from the feed's per-variant `available` boolean — Shopify does not expose inventory counts publicly, so quantity is out of scope by honesty, not omission.

### Schedule it (recommended — cloud, not your desktop)

Diff quality equals run cadence — daily is the intended cadence. **Use Apify's own Schedules, not a local scheduler** — configure it once and it runs in the cloud whether or not your machine is on.

1. Save your input as a **Task** (Console → this Actor → *Create task*), e.g. your competitor store list in `monitor` mode.
2. Console → **Schedules → Create schedule**, add the Task, set the cron (e.g. daily 6am → `0 6 * * *`).
3. Route the dataset to Slack, Sheets, your CRM or webhook via Apify integrations.

The first run of each store is a free baseline; every run after it returns only what changed.

### Honest limits (read before relying on it)

- **Standard Liquid storefronts only.** Headless Shopify stores (Hydrogen/custom frontends — e.g. ruggable.com) do not expose `/products.json` and become a controlled `NOT_SHOPIFY_OR_HEADLESS` status, never a fake result. Password-protected stores likewise (`STORE_PASSWORD_PROTECTED`); this Actor never logs in.
- **If a store blocks us (403/430), that is the answer.** It becomes a `STORE_BLOCKED` status. No proxy rotation against an explicit block, no TLS tricks, no evasion — by design.
- **Diff quality equals run cadence.** Two price changes between runs collapse into one event (oldest→newest). A daily schedule is the intended cadence.
- **First run of a store is a baseline** (free): state is saved, no events are possible yet.
- One unknown/unsupported store never kills the batch; the run fails only if **no** store answers (`ALL_STORES_FAILED`).
- Robots note: the sampled stores' `robots.txt` does not disallow `/products.json` (verified 2026-07-30); pacing is polite and identified regardless. Monitoring respects each store's terms — you are responsible for how you use the data.

### Ops notes

- `STATS` key in the run's key-value store: per-store summary (status, pages, products, events by type), HTTP counters, field-completeness health check, warnings. `ERRORS` on failure.
- Diff state lives in the named key-value store `SHOPIFY-MONITOR-STATE`, one key per store (+collection). Delete a key to force a fresh baseline for that store.
- Typical cost drivers: pages fetched (250 products/page) + records written. A 2-store daily watch (~1,500 products) runs in well under a minute at 512 MB.

### FAQ

**Do I need a Shopify account or API key?** No. The Actor reads each store's public `/products.json` feed without logging in.

**What exactly am I charged for?** Per record written to the dataset (Pay Per Event) — everything discarded by filters, windows or caps costs you nothing. The per-store `STORE_SUMMARY` records are free. The Pricing tab on this page is always the authoritative source for current rates and for any per-run fee.

**Can I schedule it?** Yes — that is the intended use. See "Schedule it" above.

**Is this affiliated with Shopify?** No. This is an unofficial community Actor, not affiliated with Shopify Inc. or any store monitored; all data comes from each store's public product feed and remains subject to that store's terms.

# Actor input Schema

## `stores` (type: `array`):

1–50 store domains or URLs (e.g. "gymshark.com" or "https://www.gymshark.com"). Protocol, www and paths are stripped automatically. Headless storefronts (no /products.json) and password-protected stores become a controlled status without stopping the batch.

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

monitor (default): diff vs the previous run — only change events are charged; first visit of a store creates a free baseline. snapshot: full catalog export, one record per product.

## `collectionHandle` (type: `string`):

Monitor only one collection instead of the whole catalog — the handle from the collection URL (e.g. "best-sellers" in /collections/best-sellers). Applies to every store in the batch. Monitor state is kept per store+collection, so switching this does not corrupt the full-catalog diff.

## `eventTypes` (type: `array`):

Which change events to emit. Default: all of PRICE\_CHANGE, STOCK\_CHANGE, NEW\_PRODUCT, REMOVED\_PRODUCT, NEW\_VARIANT.

## `minPriceDeltaPct` (type: `integer`):

Ignore PRICE\_CHANGE events smaller than this absolute percentage (0 = report every change). Filtered events cost nothing.

## `maxProductsPerStore` (type: `integer`):

Safety cap per store (250 products per page are fetched until the catalog ends or this cap is reached).

## `maxResults` (type: `integer`):

Global cap on charged records (events or products). Free per-store summaries do not count. If the cap truncates a store's events, that store's diff state is NOT advanced, so nothing is silently lost.

## `pageDelayMs` (type: `integer`):

Polite pacing between catalog pages of the same store. The 250 ms floor is deliberate.

## `proxyConfiguration` (type: `object`):

Off by default — the public feed answers without proxies. Reserved for future use; the Actor never uses proxies to work around a store that blocked it (403/430 becomes a controlled status instead).

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

Verbose logs.

## Actor input object example

```json
{
  "stores": [
    "gymshark.com",
    "colourpop.com"
  ],
  "mode": "monitor",
  "eventTypes": [
    "PRICE_CHANGE",
    "STOCK_CHANGE",
    "NEW_PRODUCT",
    "REMOVED_PRODUCT",
    "NEW_VARIANT"
  ],
  "minPriceDeltaPct": 0,
  "maxProductsPerStore": 5000,
  "maxResults": 5000,
  "pageDelayMs": 500,
  "debug": false
}
```

# 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 = {
    "stores": [
        "gymshark.com",
        "colourpop.com"
    ],
    "mode": "monitor"
};

// Run the Actor and wait for it to finish
const run = await client.actor("johnatan029/shopify-price-catalog-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 = {
    "stores": [
        "gymshark.com",
        "colourpop.com",
    ],
    "mode": "monitor",
}

# Run the Actor and wait for it to finish
run = client.actor("johnatan029/shopify-price-catalog-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "stores": [
    "gymshark.com",
    "colourpop.com"
  ],
  "mode": "monitor"
}' |
apify call johnatan029/shopify-price-catalog-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=johnatan029/shopify-price-catalog-monitor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/XagalAeMsPEcwABLN/builds/yN6B9c8G1Lj0nKlre/openapi.json
