# Shopify Price & Stock Monitor (`brianlee9527/shopify-price-stock-monitor`) Actor

Reads any Shopify store's public /products.json catalogue feed and returns clean per-variant rows: price, compare-at price, availability, SKU, vendor and tags. Optionally pass a previous run's rows as a snapshot and every variant is flagged for price changes, stock-outs, restocks and new...

- **URL**: https://apify.com/brianlee9527/shopify-price-stock-monitor.md
- **Developed by:** [Donghai Li](https://apify.com/brianlee9527) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 product scrapeds

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 Price & Stock Monitor — Apify Actor (deploy-ready, not yet published)

Reads any Shopify store's public `/products.json` catalogue feed and returns
clean **per-variant rows** (price, compare-at price, availability, SKU,
vendor, tags) **plus a run summary**. Pass a previous run's rows back in as
a snapshot and every variant is flagged for **price changes, stock-outs,
restocks and new arrivals** — built for competitor price tracking, catalogue
audits and scheduled price/stock alert pipelines (Make/Zapier/n8n via Apify
schedules + webhooks).

**Status:** built and locally verified 2026-10-29 (offline sample test,
change-detection test, live test — all below). Deployed to Brian's account
unpublished, no pricing set; publishing is a separate approved step.

### Why this product (scan summary, Oct 2026)

This was the strong #2 in the Actor #1 niche scan: real merchant pain
(competitor price/stock watching), and the data source is the store's own
public feed — no login, no browser, no proxies, so pay-per-event economics
can't go negative (same structural safety as Actor #1). Change detection is
the differentiator vs raw catalogue scrapers: they sell rows, this sells
"what changed since yesterday?" in one scheduled run.

#### Input (`.actor/input_schema.json`)

| Field | Type | Default | Notes |
|---|---|---|---|
| `storeUrl` | string (required) | `allbirds.com` | Domain or full URL; feed must be enabled (most Shopify stores) |
| `maxProducts` | int ≥0 | `0` | 0 = whole catalogue; cap for cheap trial runs |
| `previousSnapshot` | array of rows | — | Prior run's dataset rows; enables change flags |

#### Output (per variant row)

`store, productId, productTitle, handle, productUrl, vendor, productType,
tags, variantId, variantTitle, price, compareAtPrice, available, sku,
fetchedAt, priceChanged, oldPrice, wentOutOfStock, backInStock, isNew`

#### Summary (`SUMMARY` key in the key-value store)

`productCount, variantCount, outOfStockVariants, onSaleVariants, priceMin,
priceMax, priceAvg, snapshotProvided`, and with a snapshot:
`changes{priceChangedCount, wentOutOfStockCount, backInStockCount,
newVariantCount, removedVariantCount}`

Removed products are counted from the snapshot's variant IDs. Note (same
caveat as comparable monitors): removal counts are only meaningful when the
whole catalogue was read on both runs — a capped run can shift feed order.

### Pricing (proposed, pay-per-event — in `.actor/pay_per_event.json`)

| Event | Price | Notes |
|---|---|---|
| `actor-start` | $0.10 / run | Mirrors Actor #1; covers fixed cost |
| `product-scraped` | $0.001 / product | Per product (all its variant rows included) |

Sanity check against live Store pricing (Oct 29, 2026): the closest
comparable, `pistachio_implementation/shopify-catalog-monitor`, charges
$0.001 per product checked (+$0.005 per change); Media Pro Tools' Shopify
scraper/monitor charges $0.0005 per product/variant row (+$0.002 per
change); a generic product-page extractor charges $0.0015/row. The brief's
first guess of $0.002/product sat above that whole band, so it was adjusted
to **$0.001/product** — mid-band, at parity with the most comparable
monitor, with margin protected by near-zero compute (plain HTTPS GETs).

### Local tests (2026-10-29)

- **Offline sample** (`tests/sample_products.json`, 3 products / 5
  variants): parsed exactly — string prices → floats, `compare_at_price`
  `""`/`null` → null, 1 out-of-stock, 2 on sale, price avg 64.7.
- **Change detection**: mutated fixture (one price 120→99, one variant
  flipped to unavailable, one variant removed) against the first run as
  snapshot → `priceChangedCount 1 (oldPrice 120.0), wentOutOfStockCount 1,
  removedVariantCount 1`, all flags on the right rows.
- **Live** (`allbirds.com`, cap 30 products): 30 products → 328 variant
  rows, 222 out of stock, 20 on sale, prices 50.0–145.0 (avg 109.74).

### Project layout

```
shopify-monitor/
├── .actor/
│   ├── actor.json            # name/title/description, dockerfile + schema pointers
│   ├── input_schema.json
│   ├── output_schema.json    # legacy output schema (templates, string types)
│   ├── dataset_schema.json   # dataset fields + table view
│   └── pay_per_event.json    # PPE pricing (proposed; NOT set on the platform)
├── Dockerfile                # apify/actor-python:3.12
├── requirements.txt          # core is stdlib-only; SDK from base image
├── src/
│   ├── core.py               # feed fetch/pagination, normalize, snapshot diff, summary
│   └── main.py               # thin Apify entrypoint (charges actor-start, product-scraped)
├── local_run.py              # offline/live test harness (no account needed)
└── tests/sample_products.json
```

### Deployment

Deploy (unpublished, no pricing) via the connector helper:

```
python3 ~/workspace/custom-connectors/custom.apify/bin/apify.py deploy ~/workspace/apify/shopify-monitor
```

Publishing to the Store and applying the pricing above are separate,
approval-gated steps.

# Actor input Schema

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

Stop after this many products (0 = read the whole catalogue). Useful for cheap trial runs on huge stores.

## `previousSnapshot` (type: `array`):

Rows from a previous run of this Actor (same store). When provided, each variant is matched by variantId and flagged: priceChanged (+oldPrice), wentOutOfStock, backInStock, isNew; the summary counts removed variants too. Leave empty for a plain snapshot run.

## `storeUrl` (type: `string`):

Shopify store domain or URL, e.g. allbirds.com or https://www.gymshark.com. The public /products.json feed must be enabled on the store (it is on most Shopify storefronts).

## Actor input object example

```json
{
  "maxProducts": 0,
  "storeUrl": "allbirds.com"
}
```

# Actor output Schema

## `available` (type: `string`):

No description

## `backInStock` (type: `string`):

No description

## `compareAtPrice` (type: `string`):

No description

## `fetchedAt` (type: `string`):

No description

## `handle` (type: `string`):

No description

## `isNew` (type: `string`):

No description

## `oldPrice` (type: `string`):

No description

## `price` (type: `string`):

No description

## `priceChanged` (type: `string`):

No description

## `productId` (type: `string`):

No description

## `productTitle` (type: `string`):

No description

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

No description

## `productUrl` (type: `string`):

No description

## `sku` (type: `string`):

No description

## `store` (type: `string`):

No description

## `tags` (type: `string`):

No description

## `variantId` (type: `string`):

No description

## `variantTitle` (type: `string`):

No description

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

No description

## `wentOutOfStock` (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 = {
    "storeUrl": "allbirds.com"
};

// Run the Actor and wait for it to finish
const run = await client.actor("brianlee9527/shopify-price-stock-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 = { "storeUrl": "allbirds.com" }

# Run the Actor and wait for it to finish
run = client.actor("brianlee9527/shopify-price-stock-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 '{
  "storeUrl": "allbirds.com"
}' |
apify call brianlee9527/shopify-price-stock-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,brianlee9527/shopify-price-stock-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/7IQIaQGmD2i2JtRHQ/builds/4XFZVeA6t8S4J7tyB/openapi.json
