# Shopify Store Monitor - Product, Price & Stock Change Scraper (`ivora/shopify-store-monitor`) Actor

Monitor competitor Shopify stores: new and removed products, price drops and increases, sales (compare-at price) started or ended, and sold-out / back-in-stock variants since the last run. Reads the public products.json feed; headless stores fall back to myshopify.com. Optional full product export.

- **URL**: https://apify.com/ivora/shopify-store-monitor.md
- **Developed by:** [Ivora Tools](https://apify.com/ivora) (community)
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 change or product rows

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/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

## Shopify Store Monitor: Product, Price & Stock Change Scraper

Watch **competitor Shopify stores** and get **only what changed** since your last run: new products, removed products, price drops and increases, sales that started or ended (compare-at price), and items that sold out or came back in stock. It's built for e-commerce brands, agencies, dropshippers and deal hunters who check rival stores by hand today.

- Works on any standard Shopify store through its **public `products.json` feed**. No login, no API key, no browser.
- **Monitor mode** (default) compares each run with the last one. A daily schedule turns it into a competitor alert feed.
- A **free summary row per store** gives catalog size, sold-out and on-sale counts, price range, top vendors and product types, newest launches and change counts.
- An optional **full catalog export** gives every product with its variants, prices, SKUs and stock flags.

### Sample output

Change rows from a real local test run (Oct 2026, store `allbirds.com`), shortened:

```json
[
  {"type": "change", "changeType": "priceChange", "store": "Allbirds", "title": "Men's Canvas Runner NZ - Deep Navy Stripes (Blizzard Sole)",
   "direction": "down", "oldPrice": 125.0, "newPrice": 100.0, "changePct": -20.0, "variantsChanged": 13, "currency": "USD",
   "url": "https://allbirds.com/products/mens-canvas-runner-nz"},
  {"type": "change", "changeType": "compareAtChange", "store": "Allbirds", "title": "Women's Wool Runner Mizzles - Natural Black (Natural Black Sole)",
   "direction": "saleStarted", "variantChanges": [{"title": "5", "sku": "AB006ZW050", "oldCompareAtPrice": null, "newCompareAtPrice": 125.0, "price": 99.0}, …]},
  {"type": "change", "changeType": "stockChange", "store": "Allbirds", "title": "Women's Canvas Runner NZ - Deep Navy Stripes (Blizzard Sole)", "direction": "productBackInStock",
   "variantsBackInStock": [{"title": "6.5", "sku": "A12500W065"}, …]}
]
```

(The test changed the saved baseline to produce these rows; on a live schedule they appear when the store really changes.)

### How it works

1. For each store, the Actor reads `https://<store>/products.json?limit=250&page=1…` until the catalog ends. Shopify's public feed allows up to 25,000 products. The default safety cap is 10,000, which you can raise.
2. If that feed is missing, it tries `/collections/all/products.json`. Then it looks in the homepage for the store's permanent `*.myshopify.com` domain, which often still serves the feed for **headless stores** (Hydrogen, Next.js…).
3. It saves a compact copy of the catalog in a **named key-value store** (`stateStoreName`). The next run compares against it:
   - **New products** are product IDs not seen before.
   - **Removed products** are products missing from a complete read. Each one is double-checked on `/products/<handle>.js`, because feeds can shift while being read. If a suspicious number is missing, removal detection is skipped for that run.
   - **Price changes** are listed per product, with every changed variant, old and new price and change %.
   - **Sale started / ended / changed** comes from the compare-at price moving above or below the price.
   - **Sold out / back in stock** compares the `available` flag per variant, with a product-level direction such as `productSoldOut`.
   - **Variants added / removed** covers new sizes and colours or dropped ones.
4. If a store answers HTTP 403/429 (some block cloud IPs), the Actor retries it through **Apify residential proxy** automatically. That path reads at most 5,000 products per store.

Paste a **collection URL** (`https://store.com/collections/sale`) to monitor just that collection. "Removed" then means it left the collection.

### Input example

```json
{
  "stores": ["allbirds.com", "https://www.gymshark.com", "https://colourpop.com/collections/lips"],
  "mode": "changes",
  "firstRunBehavior": "baseline",
  "changeTypes": ["newProduct", "priceChange", "compareAtChange", "stockChange", "removedProduct"],
  "minPriceChangePct": 5,
  "productKeywords": [],
  "stateStoreName": "competitors-daily"
}
```

| Option | What it does |
|---|---|
| `mode` | `changes` (monitor, default) or `allProducts` (every product every run, like a catalog scraper) |
| `firstRunBehavior` | `baseline` (default) only stores the catalog on the first run. `outputProducts` also outputs current products, newest first |
| `productRowsLimit` | Caps product rows per store (first run / `allProducts`). 0 = all |
| `changeTypes` | Which change rows you want. All changes are still counted in the summary |
| `minPriceChangePct` | Ignore small price moves |
| `productKeywords` | Output only products whose title, vendor, type or tags match. The whole catalog is still tracked |
| `includeVariants`, `includeDescription` | Extra detail on product rows |

### Output

- **`change` rows** (charged): `changeType` (`newProduct`, `priceChange`, `compareAtChange`, `stockChange`, `variantsChange`, `removedProduct`), `direction`, old/new values and per-variant details. Each row also has product context: title, vendor, product type, URL, image, currency, current price, availability and on-sale flag.
- **`product` rows** (charged, only when you ask for them): product ID, handle, title, vendor, type, tags, URL, image, price min/max, compare-at min/max, on sale, max discount %, available, variants count and in-stock count, published/created/updated dates, and variants (title, SKU, price, compare-at price, available).
- **`summary` rows** (free, one per store): products, variants, available / sold-out / on-sale counts, price min/median/max, top vendors and product types, newest products, changes since the last run, feed source, proxy used and notes.
- **`error` rows** (free) with a code and a hint:
  - `not_shopify`
  - `feed_closed` (a Shopify store with the feed disabled)
  - `password` (store closed or not launched)
  - `collection` (collection not found)
  - `blocked`
  - `unreachable`

### Honest limits

- **Prices are in the store's base currency**, as published in the feed (the currency comes from the store's `/meta.json`). Market-specific prices or discounts applied at checkout aren't visible.
- **Stock is in/out only.** Shopify's public feed exposes `available`, not inventory quantities.
- **Not every Shopify store works.** In our Apify cloud test (Oct 2026), 12 of 13 well-known brand stores were read directly, with no proxy (Allbirds, Gymshark, SKIMS, ColourPop, Fashion Nova, Brooklinen…). Some stores disable the feed, run a custom storefront (bombas.com refused even through the residential fallback), or block cloud IPs. Unsupported stores get a free error row and **are not charged**.
- **Huge catalogs:** above 25,000 products (Shopify's feed limit) or your `maxProductsPerStore` cap, only part of the catalog is read, and removed-product detection is switched off for that store.
- Product URLs are built as `https://<store>/products/<handle>`. A few headless storefronts use different URL paths.

### Pricing (pay per event)

| Event | Price |
|---|---|
| Actor start | $0.003 per run |
| Store checked (catalog read and compared) | $0.004 per store |
| Change row, or product row when requested | $0.0005 |

Summary and error rows are free. Stores that can't be read aren't charged.

Examples:

- 20 competitor stores checked daily with about 5 changes each: $0.003 + 20 × $0.004 + 100 × $0.0005 = **$0.133 per day** (about $4 per month).
- One-off export of a 1,000-product store (`allProducts`): $0.003 + $0.004 + 1,000 × $0.0005 = **$0.507**.
- First run with the default `baseline`: $0.007 for one store, and you only get the summary row.

### Daily alerts to Slack or email

1. Fill in the input and click **Save as a new task** (one task per client or competitor set is a good pattern).
2. In **Schedules**, create a schedule (for example every day at 08:00 in your time zone) and add the task.
3. In the task's **Integrations** tab, add the **Slack** or **Gmail** integration to get a message when a run finishes, or a **webhook** on "Run succeeded". The webhook can pass the run to Zapier, Make, n8n or your own endpoint, which can read the rows from `https://api.apify.com/v2/datasets/{defaultDatasetId}/items?view=changes`.

Monitor mode outputs only changes, so every scheduled run's dataset *is* your alert list. An empty changes view means nothing changed. Turn on Apify's run-failure notifications too, so you hear about a failed run instead of silence.

### Related actors

This is part of a small **competitor-intelligence suite** by the same developer, with the same conventions throughout: pay per event, failed items never charged, and monitors that return only what changed since the last run.

- [Google Ads Transparency Scraper & New Ads Monitor](https://apify.com/ivora/google-ads-transparency-monitor): what your competitors advertise on Google Search, Display and YouTube.
- [LinkedIn Ad Library Scraper & New Ads Monitor](https://apify.com/ivora/linkedin-ad-library-monitor): competitors' LinkedIn ads without login, including EU impressions and targeting.
- [Bing Ads Library Scraper - Microsoft Ads Monitor (EU)](https://apify.com/ivora/microsoft-ads-library-monitor): Bing ads from Microsoft's official Ad Library (EU/EEA).
- [Google Trends Scraper & API](https://apify.com/ivora/google-trends-api): interest over time, by region/city, top queries and Trending now.
- [ATS Jobs Scraper & Hiring Monitor](https://apify.com/ivora/company-hiring-monitor): new and closed jobs from Greenhouse, Lever, Ashby, Workday and 6 more job boards.
- [App Store & Google Play Scraper](https://apify.com/ivora/app-store-monitor): ratings, versions, chart and keyword ranks of iOS and Android apps.
- [Google Hotels Scraper & Rate Monitor](https://apify.com/ivora/google-hotels-rate-monitor): hotel prices by booking site (Booking.com, Expedia, Agoda, own site) for any dates, with price-change alerts.

### FAQ

**Do I need a proxy?** Usually not. The feed is public. The automatic residential fallback handles stores that block cloud IPs.

**Will the store owner notice?** The Actor reads the same public feed many apps use, at a polite pace (one request every ~0.5 s per store).

**Can I track only some products?** Yes. Use a collection URL, or `productKeywords` to filter output by title, vendor, type or tag.

**Is it legal?** It reads publicly available product data. You are responsible for how you use the data and for complying with applicable law and the store's terms.

# Actor input Schema

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

Store domains or URLs, e.g. allbirds.com, https://www.gymshark.com or weareallbirds.myshopify.com. A collection URL (…/collections/<handle>) monitors only that collection.

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

changes = new / removed products, price, sale and stock changes since the previous run. allProducts = one row per product every run (charged per row like a change).

## `firstRunBehavior` (type: `string`):

On the very first run there is nothing to compare with. baseline = only store the catalog (you pay just the store check). outputProducts = also output the current products (newest first), charged per row; use 'Product rows limit' to cap them.

## `productRowsLimit` (type: `integer`):

Caps full product rows (first-run output and 'All products' mode) per store and run, newest products first. 0 = no limit. Change rows are never capped.

## `changeTypes` (type: `array`):

Which change rows to output. All changes are still counted in the free summary row.

## `minPriceChangePct` (type: `integer`):

Ignore price changes smaller than this percentage (0 = report every change).

## `productKeywords` (type: `array`):

Optional: output only products whose title, vendor, product type or tags contain one of these words (case-insensitive). The whole catalog is still tracked.

## `includeVariants` (type: `boolean`):

Add the variant list (title, SKU, price, compare-at price, available) to product and new-product rows.

## `includeDescription` (type: `boolean`):

Add the product description as plain text (first 2,000 characters) to product rows.

## `includeSummary` (type: `boolean`):

Free row per store: product and variant counts, sold-out and on-sale counts, price range, top vendors and product types, newest products and change counts.

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

Safety cap for huge catalogs (Shopify's public feed stops at 25,000). If a catalog is cut off, removed-product detection is off for that store.

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

Named key-value store that remembers each store's catalog between runs. Use a different name for each independent monitor (a-z, 0-9, -).

## `residentialFallback` (type: `boolean`):

If a store blocks the request (HTTP 403/429), retry it through Apify residential proxy automatically (at most 5,000 products per store that way).

## `maxConcurrency` (type: `integer`):

How many stores are read at the same time. Requests to one store are always paced.

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

Not needed for most stores (the feed is public). The residential fallback above handles stores that block.

## Actor input object example

```json
{
  "stores": [
    "allbirds.com"
  ],
  "mode": "changes",
  "firstRunBehavior": "outputProducts",
  "productRowsLimit": 10,
  "changeTypes": [
    "newProduct",
    "priceChange",
    "compareAtChange",
    "stockChange",
    "variantsChange",
    "removedProduct"
  ],
  "minPriceChangePct": 0,
  "includeVariants": true,
  "includeDescription": false,
  "includeSummary": true,
  "maxProductsPerStore": 10000,
  "stateStoreName": "shopify-store-monitor-state",
  "residentialFallback": true,
  "maxConcurrency": 3,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `changes` (type: `string`):

No description

## `products` (type: `string`):

No description

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

No description

## `runSummary` (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 = {
    "stores": [
        "allbirds.com"
    ],
    "firstRunBehavior": "outputProducts",
    "productRowsLimit": 10,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("ivora/shopify-store-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": ["allbirds.com"],
    "firstRunBehavior": "outputProducts",
    "productRowsLimit": 10,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("ivora/shopify-store-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 '{
  "stores": [
    "allbirds.com"
  ],
  "firstRunBehavior": "outputProducts",
  "productRowsLimit": 10,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call ivora/shopify-store-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ivora/shopify-store-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/oTwIepGSDO0dgrwbn/builds/sqjMQjSXhtvDdHpTI/openapi.json
