# Shopify Store Monitor — Products, Prices & Stock Changes (`harvestworks/shopify-store-monitor`) Actor

Get every product and variant from any public Shopify store, or run it on a schedule to get only price drops, restocks, sold-outs, and new or removed products.

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

## Pricing

from $1.50 / 1,000 products

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 — Products, Prices & Stock Changes

Get every product and variant from any public Shopify store as clean JSON, or run it on a schedule to get **only what changed**: price drops, price increases, restocks, sold-outs, new products and removed products.

- **No browser, no proxies, no login.** Reads the public product feed Shopify serves on every store (`/products.json`), politely and within the store's robots.txt.
- **Change alerts, not just dumps.** Schedule it daily and get a short list of changes instead of re-downloading the whole catalog.
- **Never crashes on a bad URL.** Non-Shopify sites, password-protected stores, disabled feeds and rate limits each come back as a free `store_error` row that says what happened.

### What you can do with it

- Track competitor prices and discounts across many Shopify stores.
- Get alerted when an out-of-stock product comes back.
- Spot new product launches the day they go live.
- Export a store's full catalog (titles, variants, SKUs, prices, compare-at prices, stock status, images) for analysis.

### Input

| Field | Required | Description |
|---|---|---|
| `storeUrls` | yes | Store URLs or domains, up to 100 per run. |
| `mode` | no | `full` (default) returns every product. `changes` returns only what changed since the last run. |
| `collection` | no | A collection handle, e.g. `sale`, to limit to one collection. |
| `vendor` | no | Only this vendor/brand (case-insensitive). |
| `productType` | no | Only this product type (case-insensitive). |
| `includeDescription` | no | Full mode: include the description as plain text. Default `true`. |
| `maxProductsPerStore` | no | Full mode: stop after this many products per store. |

```json
{
  "storeUrls": ["https://colourpop.com", "allbirds.com"],
  "mode": "changes",
  "collection": "sale"
}
```

### Output

#### Full mode: one row per product

```json
{
  "type": "product",
  "store": "colourpop.com",
  "storeName": "ColourPop",
  "currency": "USD",
  "productId": 7661008683090,
  "title": "Carry the Universe",
  "handle": "carry-the-universe",
  "url": "https://colourpop.com/products/carry-the-universe",
  "vendor": "ColourPop",
  "productType": "Gloss Bundle + Bag",
  "tags": ["lip-gloss", "new-arrival"],
  "description": "Every gloss, plus somewhere to put them. …",
  "available": true,
  "priceMin": 62,
  "priceMax": 62,
  "variants": [
    { "variantId": 42739735986258, "title": "Default Title", "sku": "Bundle-CarryTheUniverse", "options": [], "price": 62, "compareAtPrice": null, "available": true, "grams": 0 }
  ],
  "images": ["https://cdn.shopify.com/…"],
  "createdAt": "2026-09-01T09:00:00-07:00",
  "updatedAt": "2026-09-24T11:46:01-07:00",
  "publishedAt": "2026-09-02T11:33:11-07:00",
  "scrapedAt": "2026-09-24T18:49:00.058Z"
}
```

Prices are numbers in the store's currency. `compareAtPrice` is `null` when the product isn't on sale.

#### Changes mode: one row per change

```json
{
  "type": "change",
  "changeType": "price_drop",
  "store": "example-store.com",
  "currency": "USD",
  "productId": 7369944137808,
  "variantId": 42436493115472,
  "title": "Classic Runner",
  "variantTitle": "10",
  "sku": "CR-M-10",
  "url": "https://example-store.com/products/classic-runner",
  "previousPrice": 98,
  "price": 79,
  "previousAvailable": true,
  "available": true,
  "detectedAt": "2026-09-24T18:49:00.000Z"
}
```

`changeType` is one of `price_drop`, `price_increase`, `restock`, `sold_out` (per variant), or `new_product`, `removed_product` (per product; `variantId` is `null`).

**The first `changes` run for a store saves a baseline and returns no rows.** Changes are reported from the second run on. Each combination of store, collection and filters has its own baseline.

#### Errors: free rows

```json
{ "type": "store_error", "store": "example.com", "reason": "not_shopify", "message": "https://example.com does not look like a Shopify store (no public /products.json)." }
```

`reason` is one of `invalid_url`, `not_shopify`, `password_protected`, `json_disabled`, `robots_disallowed`, `blocked`, `rate_limited`, `collection_not_found`, `network_error`. A run summary is also saved to the `OUTPUT` record of the run's key-value store.

### Pricing

Pay per event: you pay per product row (full mode) or per change row (changes mode). `store_error` rows are free. A changes run where nothing changed costs only the run start.

Set a **maximum charge per run** to cap spending. When the run reaches it, it stops cleanly, keeps everything already delivered, and says so in the status message. In changes mode, changes that didn't fit are reported on the next run and are never billed twice.

### Limits

- Public catalog only. Password-protected stores and stores that disable their product feed can't be read.
- Up to 100 stores per run, up to 100,000 products per store.
- The Actor sends about one request per second to each store and honors `Retry-After`. It never tries to get around a block. A store that refuses access is reported as `blocked`.
- Shopify's public feed has no inventory counts, only in stock or out of stock per variant.
- A new variant added to an existing product is not reported as a change. The product's other changes still are.

### Use with AI agents

The input and output are flat and predictable. The `type` field on every row tells product, change and error rows apart, and every error has a fixed `reason` code. An agent can call this Actor with just `storeUrls`.

# Actor input Schema

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

Store home pages or domains, e.g. "https://colourpop.com" or "allbirds.com". Up to 100 stores per run. Sites that are not Shopify, password-protected, or have their public product JSON disabled are reported as store\_error rows (free) instead of failing the run.

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

"full" returns every product with its variants. "changes" compares the store with the previous run and returns only price drops, price increases, restocks, sold-outs, new products and removed products. The first "changes" run for a store saves a baseline and returns nothing; schedule it to get changes on later runs.

## `collection` (type: `string`):

Only products in this collection. Use the handle from the collection URL, e.g. "sale" for https://store.com/collections/sale.

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

Only products from this vendor/brand. Exact match, case-insensitive.

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

Only products of this product type. Exact match, case-insensitive.

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

Full mode only. Adds the product description as plain text. Turn off for smaller output.

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

Full mode only. Stop after this many products per store. Leave empty for the whole catalog. Your run's max charge limit also caps results: the run stops cleanly when it is reached.

## Actor input object example

```json
{
  "storeUrls": [
    "https://colourpop.com"
  ],
  "mode": "full",
  "includeDescription": true
}
```

# Actor output Schema

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

All rows. Each row's "type" is "product", "change" or "store\_error".

## `productsTable` (type: `string`):

Key product fields for full-mode runs.

## `changesTable` (type: `string`):

Key change fields for changes-mode runs.

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

Per-store status, result counts, and whether the max charge limit stopped the run.

# 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://colourpop.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("harvestworks/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 = { "storeUrls": ["https://colourpop.com"] }

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

```

## MCP server setup

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