# Shopify Store Monitor: Product Prices & Stock Changes (`southern-data/shopify-store-monitor`) Actor

Watch public Shopify catalogues for new products, price changes and stock status via /products.json. Non-Shopify, blocked or password-protected stores are reported as skipped rows — never a silent empty success.

- **URL**: https://apify.com/southern-data/shopify-store-monitor.md
- **Developed by:** [Southern Data](https://apify.com/southern-data) (community)
- **Categories:** E-commerce, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.64 / 1,000 product returneds

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

## Shopify Store Monitor: Product Prices & Stock Changes

Read a Shopify store’s **public catalogue** (`/products.json`) and get structured product rows — title, vendor, price range, compare-at price, stock status and the product URL. Optional **monitor mode** remembers the last catalogue so a daily schedule only returns **new products, removals, price changes and stock changes**.

If a domain is not Shopify, is password-protected, is blocked, or really has zero products, you get a **skipped or empty row that says so**. The run never reports a silent success with zero unexplained rows.

### Limits (read these first)

1. **Public catalogue only.** This Actor reads `/products.json` (or `/collections/{handle}/products.json`). It does not log in, does not bypass a store password, and does not copy checkout or customer data.
2. **About 250 products per page, capped per store.** Default cap is 2,000 products per store (maximum 5,000). Shopify’s public feed itself often stops around 10,000 products, so very large catalogues are incomplete by design.
3. **One row per product, not per variant.** `price` is the lowest variant price, `priceMax` the highest, and `available` is true if any variant is in stock. Variant-level detail is not written as separate rows.
4. **Headless or custom storefronts may not expose `/products.json`.** Those stores are reported as skipped (`not_shopify` or `not_json`) rather than guessed.
5. **Respect the merchant.** This is for watching public prices and stock — not for copying a store wholesale. Keep request volume reasonable (this Actor already pauses between pages).
6. **Monitor first run returns a full snapshot.** In `mode=changed`, the first run saves a baseline *and* returns every product (`changeType=baseline`) so you can see the catalogue. Later runs return only changes.

### Quick start

```json
{
  "stores": ["allbirds.com", "https://www.gymshark.com"],
  "mode": "all",
  "maxProducts": 200
}
```

Daily competitor watch (schedule this):

```json
{
  "stores": ["allbirds.com", "https://www.gymshark.com"],
  "mode": "changed",
  "monitorName": "competitors-daily"
}
```

First scheduled run: full snapshot + baseline saved. Later runs: only new products, removals, price moves and stock changes.

A collection URL limits the scrape to that collection:

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

### Output fields

| Field | Meaning |
|---|---|
| `status` | `ok`, `skipped`, or `empty` |
| `reason` / `reasonCode` | Why a store was skipped (`not_shopify`, `password_protected`, `blocked`, `empty_catalogue`, …) |
| `store`, `title`, `vendor`, `url` | Product basics |
| `price`, `priceMax`, `compareAtPrice` | Lowest / highest variant price and lowest “was” price |
| `available`, `variantCount`, `availableVariantCount` | Stock summary |
| `previousPrice`, `previousAvailable`, `changeType`, `priceDelta` | Set in monitor mode (`baseline` / `new` / `price_drop` / `price_rise` / `sold_out` / `back_in_stock` / `removed`) |

### Pricing (pay per event)

| Event | Price | When |
|---|---|---|
| `actor-start` | $0.001 | Once per run, after the first successful Shopify catalogue fetch |
| `store-check` | $0.005 | Each store whose public catalogue was fetched (including a genuine empty catalogue) |
| `product` | $0.0008 | Each product row in `mode=all`, and each snapshot row on the first `changed` run |
| `change` | $0.002 | Each change / new / removed row on later `changed` runs |

**Store discount tiers:** free-plan and Bronze users pay the prices above. Silver gets 10% off and Gold (and higher) gets 20% off.

**Free:** invalid / empty input (exit 0, $0), skipped stores (not Shopify, password-protected, blocked, network errors), unchanged products in monitor mode.

Typical cost to snapshot one 200-product store: **$0.001 + $0.005 + 200×$0.0008 = $0.166**. A later monitor run of that store with 8 changes: **$0.001 + $0.005 + 8×$0.002 = $0.022**.

### Tips

- Set `monitorName` and keep it stable when using Apify Schedules.
- Cap spend with Apify’s `maxTotalChargeUsd` on the run or task.
- Prefer specific collection URLs when you only care about one category.
- Default memory is 512 MB — enough for this Actor.

### Self-test

Set `"selfTest": true` to run offline checks of parsing, skip reasons, pagination and change detection. Used by our daily health schedule.

### Related tools

- [Google Shopping Price Monitor](https://apify.com/southern-data/google-shopping-monitor) — see how the same products are priced across Google Shopping, not just on one store
- [Website Contact Finder](https://apify.com/southern-data/website-contact-finder) — find a merchant's published email or social profiles from their website

# Actor input Schema

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

Store domains or URLs, one per line — e.g. "allbirds.com", "https://store.myshopify.com", or a collection URL like "https://allbirds.com/collections/mens". Each store's public /products.json catalogue is read (no login).

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

all = every product in the public catalogue (up to maxProducts per store). changed = first run returns a full snapshot and saves it as a baseline; later runs return only new products, removals, price changes and stock changes.

## `monitorName` (type: `string`):

Stable name for the catalogue memory, e.g. "competitors-daily". Use the same name on every scheduled run. If empty, memory is keyed from your store list and resets when the list changes.

## `maxProducts` (type: `integer`):

Hard cap on products fetched from each store (default 2000, maximum 5000). Shopify's public /products.json feed itself often stops around 10,000 products.

## `selfTest` (type: `boolean`):

Offline checks of parsing, skip reasons, pagination and change detection (no live stores). For the publisher's daily health check. Leave off for normal use.

## Actor input object example

```json
{
  "stores": [
    "allbirds.com",
    "https://www.gymshark.com"
  ],
  "mode": "all",
  "maxProducts": 2000,
  "selfTest": false
}
```

# Actor output Schema

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

One row per product returned, plus a skipped/empty row for any store that was not a public Shopify catalogue.

## `stores` (type: `string`):

STORES record: per store, pages loaded, products found/returned, skip reasons.

## `runSummary` (type: `string`):

RUN_SUMMARY record: stores scraped/skipped, products returned, charged events, settings, any input problem.

## `selfTest` (type: `string`):

SELF_TEST record: only present when the Actor runs with selfTest: true.

# 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",
        "https://www.gymshark.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("southern-data/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",
        "https://www.gymshark.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("southern-data/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",
    "https://www.gymshark.com"
  ]
}' |
apify call southern-data/shopify-store-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,southern-data/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/RxX3zQO1eA05fVfcR/builds/ijIAIrHX6XAJmEGKB/openapi.json
