# Shopify Product Scraper & Price Monitor (`jakob_jerich/shopify-catalog-monitor`) Actor

Extract every product, variant, price and stock status from any public Shopify store or collection. Monitor mode returns only new, changed and removed products since the last run.

- **URL**: https://apify.com/jakob_jerich/shopify-catalog-monitor.md
- **Developed by:** [Jakob Jerič](https://apify.com/jakob_jerich) (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

Pay per event

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 Product Scraper & Price Monitor

Extract the product catalog of any public Shopify store: every product and variant with price, compare-at price, stock status, SKU, options, vendor, tags and images. Turn on **monitor mode** and each run returns only what changed since the last run: new products, price and stock changes, and removed products.

The Actor reads the public `products.json` feed that Shopify stores serve. No login, no browser and no proxies, so runs are fast and cheap.

### What you get

- One dataset item per product, with all variants nested inside it.
- Price summary per product: `priceMin`, `priceMax`, `compareAtPriceMax`, `onSale`, `available`, `availableVariantCount`.
- Shop name and currency from the store's public `meta.json`.
- Whole stores or single collections (`https://kith.com/collections/footwear`), several stores in one run.
- Monitor mode: `changeType` = `new`, `changed` or `removed`, plus a `changes` list with old and new values per variant (price, compare-at price, availability, added or removed variants, title).
- A `SUMMARY` record in the key-value store with the status of every store (products found, items saved, full catalog or not, error message).

### Input

| Field | Default | Description |
|---|---|---|
| `storeUrls` | — | Domains, store URLs or collection URLs |
| `maxProductsPerStore` | 1000 | Limit per store or collection; `0` = whole catalog |
| `monitorMode` | false | Output only new, changed and removed products since the previous monitoring run |
| `includeDescription` | false | Add the product description as plain text |

Example:

```json
{
  "storeUrls": ["https://www.deathwishcoffee.com", "https://www.allbirds.com/collections/mens"],
  "maxProductsPerStore": 200
}
```

### Output example

Copied from a real run (variants and tags shortened):

```json
{
  "store": "www.allbirds.com",
  "storeName": "Allbirds",
  "collection": "mens",
  "currency": "USD",
  "productId": 7211502370896,
  "handle": "mens-wool-cruiser-dark-camel",
  "title": "Men's Wool Cruiser - Dark Camel (Natural White Sole)",
  "url": "https://www.allbirds.com/products/mens-wool-cruiser-dark-camel",
  "vendor": "Allbirds",
  "productType": "Shoes",
  "tags": ["allbirds::carbon-score => undefined", "allbirds::cfId => color-4f31f677e85546749e40262e8f1e0b22"],
  "priceMin": 105,
  "priceMax": 105,
  "compareAtPriceMax": null,
  "onSale": false,
  "available": true,
  "variantCount": 13,
  "availableVariantCount": 7,
  "options": [
    {
      "name": "Size",
      "values": ["8", "8.5", "9", "9.5", "10", "10.5", "11", "11.5", "12", "12.5", "13", "13.5", "14"]
    }
  ],
  "imageUrl": "https://cdn.shopify.com/s/files/1/1104/4168/files/A11631_25Q3_Wool-Cruiser-Dark-Camel-Natural-White-Sole_PDP_LEFT__1_bec494cd-90bc-4d1a-b180-cd8a56464781.png?v=1756335125",
  "imageCount": 5,
  "description": null,
  "createdAt": "2025-08-18T12:18:42-07:00",
  "publishedAt": "2026-09-25T15:20:56-07:00",
  "updatedAt": "2026-10-31T16:53:07-07:00",
  "variants": [
    {
      "variantId": 41301538046032,
      "title": "8",
      "sku": "A11632M080",
      "price": 105,
      "compareAtPrice": null,
      "available": false,
      "options": ["8"],
      "grams": 664,
      "requiresShipping": true
    }
  ],
  "changeType": null,
  "changes": [],
  "scrapedAt": "2026-10-31T23:53:08.133Z"
}
```

In monitor mode, a changed product has the same fields plus `changeType` and `changes`. A real example from monitoring kith.com/collections/footwear (other fields omitted):

```json
{
  "title": "Giorgio Armani & Kith Espadrilla - Deer Black",
  "priceMin": 895,
  "currency": "USD",
  "changeType": "changed",
  "changes": [
    {
      "field": "available",
      "variantId": 45132888998016,
      "variantTitle": "45",
      "old": false,
      "new": true
    }
  ]
}
```

### Monitor mode

1. Run once with `monitorMode: true`. This saves a baseline in the named key-value store `shopify-catalog-monitor-state` in your account and outputs every product as `new`.
2. Schedule the same input (for example daily). Each run outputs only products that are new, changed or removed, so you pay only for changes.
3. Use the `Changes` dataset view, or connect a webhook or integration to get alerts.

Each store or collection URL has its own baseline. Products are reported as `removed` only when both this run and the previous one read the full catalog, so a product limit never produces false removals.

### Pricing

Pay per event:

| Event | Price |
|---|---|
| Store or collection read (charged once per store that returns products) | $0.002 |
| Product saved to the dataset | $0.0005 ($0.50 per 1,000) |

Examples: 1,000 products from one store cost $0.502. A daily monitor of a 2,000-product store with 15 changes costs $0.0095 per run. Stores that fail (not Shopify, blocked) are not charged.

### Use cases

- Track competitor prices, discounts and restocks.
- Build product feeds or price comparison data from brand stores.
- Research a store's assortment: vendors, product types, price ranges, new launches.
- Get alerts when a product comes back in stock.

### Limits

- Works only with Shopify stores that serve the public `/products.json` feed. Some stores disable it or block automated requests (for example shop.tesla.com returns HTTP 403); those are skipped with a clear message in the log and in `SUMMARY`. The Actor does not try to bypass blocks.
- Shopify's feed serves at most 25,000 products per store or collection. For bigger stores, use collection URLs.
- The feed does not include exact inventory counts, barcodes or reviews. `available` is Shopify's in-stock flag per variant.
- Prices are in the store's default currency (`currency`). Market-specific prices are not included.
- Product URLs in the output use the store's primary domain. Single product URLs are not accepted as input; the Actor extracts the whole store instead.

### FAQ

**How do I know if a site is a Shopify store?** Open `https://<site>/products.json` in a browser. If you see JSON with `products`, the Actor works on it.

**Why did a store fail?** See the run log or the `SUMMARY` record. `not found` means the site is not on Shopify or hides the feed; `HTTP 403`, `429` or `430` means the store blocks or rate-limits automated access. A collection that does not exist returns no products; it is reported as `empty` and not charged.

**Can I monitor only one category?** Yes, use a collection URL such as `https://www.allbirds.com/collections/mens`.

**How do I reset monitoring?** Delete the record for the store in the `shopify-catalog-monitor-state` key-value store (or the whole store). The next run saves a new baseline.

**What happens if a run reaches my maximum cost per run?** The Actor stops cleanly. In monitor mode the baseline of the interrupted store is not updated, so the next run reports those changes again instead of losing them.

**Is personal data collected?** No. The output contains only product data and the shop's name and currency.

# Actor input Schema

## `storeUrls` (type: `array`):

Shopify stores to extract. Use a domain ("allbirds.com"), a store URL ("https://www.allbirds.com") or a collection URL ("https://kith.com/collections/footwear") to get only that collection.

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

Stop after this many products per store or collection. 0 means the whole catalog (Shopify's feed serves at most 25,000 products per store or collection).

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

Compare with the previous monitoring run of the same store and output only products that are new, changed (price, compare-at price, stock, variants, title) or removed. The first run saves a baseline and outputs all products as "new". Schedule the Actor to get regular change reports.

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

Add the product description as plain text (HTML removed). Makes the dataset larger.

## Actor input object example

```json
{
  "storeUrls": [
    "https://www.deathwishcoffee.com",
    "https://www.allbirds.com/collections/mens"
  ],
  "maxProductsPerStore": 200,
  "monitorMode": false,
  "includeDescription": false
}
```

# Actor output Schema

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

One item per product, with all variants, prices and stock status.

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

Per-store status: products found, items saved, whether the catalog was complete, and errors.

# 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 = {
    "storeUrls": [
        "https://www.deathwishcoffee.com",
        "https://www.allbirds.com/collections/mens"
    ],
    "maxProductsPerStore": 200,
    "monitorMode": false,
    "includeDescription": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("jakob_jerich/shopify-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 = {
    "storeUrls": [
        "https://www.deathwishcoffee.com",
        "https://www.allbirds.com/collections/mens",
    ],
    "maxProductsPerStore": 200,
    "monitorMode": False,
    "includeDescription": False,
}

# Run the Actor and wait for it to finish
run = client.actor("jakob_jerich/shopify-catalog-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 '{
  "storeUrls": [
    "https://www.deathwishcoffee.com",
    "https://www.allbirds.com/collections/mens"
  ],
  "maxProductsPerStore": 200,
  "monitorMode": false,
  "includeDescription": false
}' |
apify call jakob_jerich/shopify-catalog-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jakob_jerich/shopify-catalog-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/e65oi3vUjuqJmK3dr/builds/5XlbX7SoCKZeHRImw/openapi.json
