# Shopify Product & Price Monitor (`codeclouds/shopify-price-monitor`) Actor

Track prices, sales and stock across any Shopify store. Detects price changes, new and removed products between runs. Variant-level data with SKU, sale detection and discount %.

- **URL**: https://apify.com/codeclouds/shopify-price-monitor.md
- **Developed by:** [Dennis](https://apify.com/codeclouds) (community)
- **Categories:** E-commerce, Lead generation
- **Stats:** 2 total users, 1 monthly users, 85.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 product-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/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 Product & Price Monitor 🛍️

Track **prices, sales and stock across any Shopify store** — and get notified of exactly what changed. This is not just another product scraper: it remembers the previous run and tells you which prices changed, which products are new, and which disappeared.

Works on any of the millions of Shopify-powered stores worldwide. No API keys, no login — it uses Shopify's public product endpoints.

### Why this monitor?

Most Shopify scrapers dump the full catalog every run and leave the diffing to you. This actor does the heavy lifting:

- 📉 **Price change detection** — every record is annotated as `new`, `price_change`, `removed` or `unchanged`, including the previous price and change percentage
- 📦📤 **Back-in-stock / out-of-stock alerts** — an availability flip is its own `back_in_stock`/`out_of_stock` changeType, detected independently of any price move
- 🏷️ **Sale detection** — automatically flags discounted items via compare-at price, with discount %
- 📦 **Variant-level data** — individual SKUs, sizes and colors, not just parent products
- 🏪 **Multiple stores per run** — monitor your whole competitive set at once; one blocked store never fails the run
- ⚡ **Reliable by design** — official JSON endpoints (no fragile HTML parsing), automatic retry with backoff on rate limits

### When should an AI agent use this?

- "Did any of my competitor's Shopify stores change prices since yesterday?"
- "Is \[product] currently on sale on \[shop.example.com], and how much off?"
- "Which products has \[store] added or delisted from their catalog this week?"
- "Is \[SKU] back in stock on the supplier's Shopify store?"
- "Are any resellers selling below the minimum advertised price on \[store]?"
- "Give me all sale items under €50 in the sneakers collection on \[store]."

### What data do you get?

One record per product variant:

```json
{
  "store": "shop.example.com",
  "productId": 111,
  "variantId": 1002,
  "title": "Men's Cruiser Terralux",
  "variantTitle": "Size 43",
  "sku": "CRZ-43",
  "vendor": "Allbirds",
  "productType": "Shoes",
  "tags": ["mens", "sale"],
  "price": 71.97,
  "compareAtPrice": 79.96,
  "onSale": true,
  "discountPercent": 10,
  "available": false,
  "imageUrl": "https://cdn.shopify.com/...",
  "productUrl": "https://shop.example.com/products/mens-cruiser-terralux",
  "changeType": "price_change",
  "previousPrice": 81.97,
  "priceChangePercent": -12.2
}
```

### Use cases

- **Competitive price intelligence** — know within hours when a competitor drops prices or starts a sale
- **MAP / brand compliance** — verify that resellers respect your minimum advertised price
- **Deal hunting & arbitrage** — schedule daily runs with `onlyOnSale` and `onlyChanges` for a clean deal feed
- **Assortment tracking** — see which products competitors add or delist
- **Dropshipping stock sync** — track availability of supplier products
- **AI agents & dashboards** — clean structured JSON, ideal for LLM tools (MCP), Google Sheets or BI pipelines

### Quick start

**Full catalog snapshot of one store:**

```json
{ "stores": ["https://www.allbirds.com"] }
```

**Daily price alerts on your three biggest competitors:**

```json
{
  "stores": ["https://storeA.com", "https://storeB.com", "https://storeC.com"],
  "monitorMode": true,
  "onlyChanges": true
}
```

Schedule this daily: the first run builds a baseline, every next run outputs *only* new products, price changes and removed items. Wire it to Slack, e-mail or Google Sheets via Apify integrations.

**Sale items under €50 in a specific collection:**

```json
{
  "stores": ["https://shop.example.com"],
  "collections": ["sneakers"],
  "onlyOnSale": true,
  "maxPrice": 50,
  "onlyAvailable": true
}
```

### Input reference

| Field | Description |
|---|---|
| `stores` | Shopify store URLs (required) |
| `collections` | Collection handles to limit scope |
| `vendor` / `productType` / `tags` | Product filters |
| `onlyOnSale` / `onlyAvailable` | Sale and stock filters |
| `minPrice` / `maxPrice` | Price range filter |
| `maxProductsPerStore` | Cap per store (default 1000) |
| `monitorMode` | Enable change detection between runs |
| `onlyChanges` | Output only new/changed/removed items |
| `minPriceChangePercent` | In monitor mode: only count a move as `price_change` (and charge the higher `price-alert` rate) if the absolute % change is at least this. Smaller moves report as `unchanged`. Default `0` = every difference counts |
| `notificationWebhookUrl` | Optional: POST a compact run summary (counts + biggest price drop) to this URL at the end of the run — a convenience for users without their own Apify integration. Same data already in `RUN_SUMMARY`/the dataset, no extra charge |
| `trackPriceHistory` | Keep a short price time series per variant across scheduled runs, exposed as `trend`. Default `false` |
| `maxHistoryPoints` | How many past price points to keep per variant when `trackPriceHistory` is on (default `30`) |
| `crossStoreComparison` | When monitoring 2+ stores, emit an extra record per SKU seen in more than one store, with the lowest price and which store offers it. Default `false` |

### Pricing

Pay per event: regular product records are billed at the base rate; detected **price alerts** (changes and removals), **stock alerts** (back-in-stock/out-of-stock), and **sale alerts** (sale started/ended) at a slightly higher rate — you pay for insight, not bulk. With `onlyChanges` enabled a typical daily monitoring run costs only a few cents.

- **`product-result`:** $0.003 per delivered record (base data)
- **`price-alert`:** $0.01 extra, for `price_change`/`removed` records
- **`stock-alert`:** $0.012 extra, for `back_in_stock`/`out_of_stock` records
- **`sale-alert`:** $0.01 extra, for `sale_started`/`sale_ended` records — a sale flip is usually also a
  price move, but this fires instead of `price-alert` for that record, not on top of it
- **`trend-insight`:** $0.0125 extra, only with `trackPriceHistory` enabled and a real multi-point comparison
  possible (not the first run for a variant)
- **`cross-store-comparison`:** $0.01 per matched SKU, only with `crossStoreComparison` enabled and the SKU
  actually found in 2+ monitored stores

#### Sale alerts (`sale_started`/`sale_ended`)

With `monitorMode: true`, a variant's `onSale` status flipping since the previous run is reported as its own
`changeType` — `sale_started` or `sale_ended` — independent of whether it also happens to be a `price_change`
(a sale flip almost always is one, but the sale signal is more specific and takes precedence over the generic
`price_change` label for that record). `previousPrice`/`priceChangePercent` are still filled when the price
also moved.

#### Historical price trend (`trackPriceHistory`, optional, separately charged)

With `trackPriceHistory: true`, each record gets a `trend` field once at least 2 price points exist for that
variant:

```json
{
  "trend": {
    "lowestPriceEver": 71.97,
    "highestPriceEver": 89.99,
    "priceChangePercentSinceOldestPoint": -10.5,
    "historyPoints": 12
  }
}
```

`priceChangePercentSinceOldestPoint` compares against the *oldest point still in the retained window*
(capped at `maxHistoryPoints`), not a fixed calendar period like "7 days ago" — this actor doesn't assume a
particular run schedule. `trend` is `null` on the very first measurement for a variant (nothing to compare
yet). Charged as `trend-insight`, only once a real comparison was possible.

#### Cross-store price comparison (`crossStoreComparison`, optional, separately charged)

With `crossStoreComparison: true` and 2+ `stores` configured, the actor emits one extra record per SKU that
appears in more than one monitored store:

```json
{
  "recordType": "cross_store_comparison",
  "sku": "CRZ-42",
  "lowestPrice": 129.0,
  "lowestPriceStore": "www.competitor.com",
  "lowestPriceProductUrl": "https://www.competitor.com/products/cruiser-42",
  "stores": [
    { "store": "www.allbirds.com", "price": 135.0, "productUrl": "https://www.allbirds.com/products/cruiser-42" },
    { "store": "www.competitor.com", "price": 129.0, "productUrl": "https://www.competitor.com/products/cruiser-42" }
  ]
}
```

Matching is exact-SKU only — no fuzzy title matching, since that risks wrong matches for automated arbitrage
decisions. Charged as `cross-store-comparison`, once per matched SKU.

### Run summary

A `RUN_SUMMARY` object (store/result/alert counts, per-store errors) is written to the actor's key-value store
after every run, and is also pushed as the last item in the **dataset** (marked `"recordType": "run_summary"`,
easy to filter out) — so no-code integrations (Zapier, Make, Google Sheets) that only read the dataset see it
too, without an extra API call. No extra charge.

### Reliability notes

- Uses Shopify's public `products.json` endpoints — stable for years, no HTML parsing that breaks on redesigns
- Automatic exponential backoff on HTTP 429 (rate limits) and 5xx responses
- Stores behind aggressive bot protection (e.g. some enterprise storefronts) may return 403; these are reported per store in the run summary while other stores complete normally
- Product data only (titles, prices, stock) — no personal data is collected, so GDPR exposure is minimal

### FAQ

**Does it work on every Shopify store?** The vast majority. A small number of stores disable the public product feed or sit behind bot protection; these are reported as errors in the run summary.

**How many products can it handle?** Tested with catalogs of thousands of products; pagination at 250 products per request.

**Can I monitor specific products only?** Use collection, vendor, tag or price filters to narrow scope.

**Where is the change history stored?** A compact per-store price snapshot lives in the actor's key-value store; each run compares and updates it.

### Related Actors

Also by this developer:

- **[Agrigrondstofprijzen Monitor](https://apify.com/codeclouds/agrigrondstofprijzen-monitor)** — same profile: a change-detection price-monitoring feed with period-over-period trend and alert thresholding, for EU agricultural commodities instead of Shopify products.

***

*Keywords: shopify scraper, shopify price tracker, price monitoring, competitor price tracking, shopify products json, price drop alert, e-commerce intelligence, MAP monitoring.*

### Changelog

#### 0.5.0 - Sale alerts, price history trend & cross-store comparison

- Added `sale_started`/`sale_ended` change types: a variant's `onSale` status flipping since the previous run
  is now its own signal, symmetric with `back_in_stock`/`out_of_stock`. New `sale-alert` event ($0.01), fires
  instead of `price-alert` for that record (not on top of it) when both flip at once.
- Added `trackPriceHistory` (opt-in) — keeps a short price time series per variant across scheduled runs,
  exposed as `trend` (lowest/highest price ever, % change vs. the oldest retained point). New `trend-insight`
  event ($0.0125), only charged once a real multi-point comparison exists.
- Added `crossStoreComparison` (opt-in) — when monitoring 2+ stores, emits an extra record per SKU matched
  across stores with the lowest price and which store offers it (exact SKU match only). New
  `cross-store-comparison` event ($0.01), once per matched SKU.

#### 0.4.0

- Added `notificationWebhookUrl`: POSTs a compact run summary (counts + biggest price drop) to a webhook at
  the end of the run. Convenience feature, same data already available elsewhere, no extra charge. A
  failed/unreachable webhook is logged and never fails the run.
- `RUN_SUMMARY` is now also pushed as the last dataset item (`"recordType": "run_summary"`), not just the
  key-value store, so dataset-only integrations see it. No pricing change.
- Fixed a potential snapshot-key collision: two different store hosts that sanitize to the same key (e.g.
  `myshop.co.uk` and `myshop-co.uk`) no longer silently overwrite each other's snapshot in monitor mode,
  which could have produced false new/removed signals. A short hash of the original host is now appended to
  the key.

#### 0.3.1 - Bugfix

- Fixed `price-alert`/`stock-alert` never firing across scheduled runs: the actor was reading/writing its
  snapshot to Apify's per-Run *default* key-value store, which is not shared between separate runs. Now uses a
  named, persistent key-value store. A new baseline will be captured on the next run.

#### 0.3.0

- Added back-in-stock/out-of-stock detection: an availability flip vs. the previous run is now its own
  `back_in_stock`/`out_of_stock` changeType, detected independently of any price move on the same variant.
  Charged as a new `stock-alert` event ($0.012), confirmed by the user (2026-07-14). The per-store snapshot
  now also tracks availability, not just price.

#### 0.2.0

- Added `minPriceChangePercent` — filters out micro price-changes (rounding, A/B-test pricing) from being
  charged as `price-alert` events, while still reporting the delta on the record. No pricing change — this
  reduces noise/cost for the user, it doesn't add a new event.

#### 0.1.0 - Initial release

- Full Shopify catalog scraping via the public `products.json` and collection endpoints
- Change detection between runs (`new`, `price_change`, `removed`, `unchanged`) using a per-store key-value snapshot
- Sale detection via compare-at price with computed discount percentage
- Pay-Per-Event pricing with separate `product-result` and `price-alert` events

# Actor input Schema

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

One or more Shopify store URLs, e.g. \["https://www.allbirds.com"].

## `collections` (type: `array`):

Limit to specific collection handles (the part after /collections/ in the URL), e.g. \["mens-shoes"]. Empty = full catalog.

## `vendor` (type: `string`):

Only products of this brand/vendor, e.g. "Allbirds".

## `productType` (type: `string`):

Only products of this type, e.g. "Shoes".

## `tags` (type: `array`):

Product must have at least one of these tags, e.g. \["sale", "new-arrival"].

## `onlyOnSale` (type: `boolean`):

Only products with a discount (compare-at price above current price).

## `onlyAvailable` (type: `boolean`):

Only variants that are currently available.

## `minPrice` (type: `integer`):

Skip variants below this price, e.g. 20.

## `maxPrice` (type: `integer`):

Skip variants above this price, e.g. 200.

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

Cap on products fetched per store (1–50000).

## `monitorMode` (type: `boolean`):

Compare against the previous run: marks records as new / price\_change / removed / unchanged and fills previousPrice + priceChangePercent. Uses a shared, persistent key-value store ('shopify-price-monitor-snapshot', with a separate snapshot per store) that is also read and updated by scheduled monitoring runs, not just this one. On the very first run for a store (no snapshot yet), there is nothing to compare against: every record is marked 'new' and none are marked 'removed'.

## `onlyChanges` (type: `boolean`):

In monitor mode: only output new, changed and removed items. Ideal for scheduled price alerts. Has no effect on the very first run for a store: since there is no previous snapshot to compare against yet, every record is marked 'new' (not 'unchanged'), so the full catalog is still output that first time — filtering down to real deltas only starts from the next run onward.

## `minPriceChangePercent` (type: `integer`):

In monitor mode: only treat a price move as a price\_change (and charge the price-alert event) if the absolute percentage change is at least this. Smaller moves are reported as unchanged instead. 0 = every price difference counts.

## `notificationWebhookUrl` (type: `string`):

Optional: POST a compact run summary (counts + biggest price drop) to this URL at the end of the run. Convenience feature for users without their own Apify integration — same data already in RUN\_SUMMARY/the dataset, no extra charge.

## `trackPriceHistory` (type: `boolean`):

Keeps a short price time series per variant across scheduled runs, exposed as `trend` (lowest/highest price ever seen, % change vs. the oldest retained point). New charged event trend-insight ($0.0125), only when a real multi-point comparison was possible (not on the first run for a variant). Uses the same shared, persistent key-value store as Monitor Mode ('shopify-price-monitor-snapshot', history kept per store per variant) that is also read and updated by scheduled monitoring runs, not just this one.

## `maxHistoryPoints` (type: `integer`):

How many past price measurements to keep per variant when Track Price History is enabled. Oldest points are dropped once this cap is reached.

## `crossStoreComparison` (type: `boolean`):

When monitoring 2+ stores, emits an extra record per SKU that appears in more than one store, naming the lowest price and which store offers it. Exact SKU match only. New charged event cross-store-comparison ($0.01), once per matched SKU.

## Actor input object example

```json
{
  "stores": [
    "https://www.allbirds.com"
  ],
  "collections": [],
  "tags": [],
  "onlyOnSale": false,
  "onlyAvailable": false,
  "minPrice": 20,
  "maxPrice": 200,
  "maxProductsPerStore": 1000,
  "monitorMode": false,
  "onlyChanges": false,
  "minPriceChangePercent": 0,
  "trackPriceHistory": false,
  "maxHistoryPoints": 30,
  "crossStoreComparison": 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": [
        "https://www.allbirds.com"
    ],
    "minPrice": 20,
    "maxPrice": 200
};

// Run the Actor and wait for it to finish
const run = await client.actor("codeclouds/shopify-price-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": ["https://www.allbirds.com"],
    "minPrice": 20,
    "maxPrice": 200,
}

# Run the Actor and wait for it to finish
run = client.actor("codeclouds/shopify-price-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": [
    "https://www.allbirds.com"
  ],
  "minPrice": 20,
  "maxPrice": 200
}' |
apify call codeclouds/shopify-price-monitor --silent --output-dataset

```

## MCP server setup

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

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/7qpe2MabXU22mX4Jn/builds/B6WDTIKYvc5gpTzmg/openapi.json
