# Shopify & WooCommerce Price Tracker: Products, Stock & Changes (`watchmere/shopify-woocommerce-price-tracker`) Actor

Track competitor Shopify and WooCommerce stores: every product and variant with price, sale price, stock and SKU, plus what changed since your last run: new and removed products, price rises and drops, back in stock and sold out. No login or API key. Schedule it for daily alerts.

- **URL**: https://apify.com/watchmere/shopify-woocommerce-price-tracker.md
- **Developed by:** [Watchmere .](https://apify.com/watchmere) (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 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

### What does Shopify & WooCommerce Price Tracker do?

It reads the **whole product catalog of any Shopify or WooCommerce store** (every product and variant with its
price, sale price, stock status and SKU) and tells you **what changed since your last run**: new products,
removed products, price rises, price drops, items back in stock and items that sold out.

Add your competitors' stores, run it once, then put it on a daily schedule with **Only what changed** and you get a
short list of changes each morning instead of a spreadsheet of thousands of unchanged rows.

- Shopify and WooCommerce, detected automatically
- One row per variant (size, colour...) or one row per product with its lowest and highest price
- No login, no API key, no browser extension
- Export to CSV, Excel, JSON or Google Sheets, or call it from your own code, Zapier, Make or n8n

### Why use it?

- **Online sellers:** see the moment a competitor cuts a price, starts a sale or runs out of a best seller
- **Agencies:** monitor dozens of client competitors on one schedule
- **Buyers and resellers:** catch restocks and new launches
- **Analysts:** build a price history from a daily run

### How it works

Shopify and WooCommerce stores publish their own catalog in a public, machine-readable form for their website to use:
Shopify at `/products.json` and WooCommerce through its Store API (`/wp-json/wc/store/v1/products`). This tool
reads those, a few hundred products per request, so it's quick and gentle on the store. It doesn't scrape web pages.

After each run it keeps a compact copy of what it saw in a key-value store in **your** Apify account called
`store-tracker-history`. The next run compares against it, so "what changed" means "since this account last checked
this store", whichever task or schedule ran it.

### How to use it

1. Add one or more **stores**. Any page of the store works: `allbirds.com`, a product page or a collection page.
2. Choose **what to save**:
   - *Every product, with what changed*: the full catalog, each row labelled `unchanged`, `price_down`, `new`...
   - *Only what changed since the last run*: just the changed rows. Best for a daily schedule.
3. Keep **one row per variant** on to see which sizes or colours change, or switch it off for one row per product.
4. Click **Start**. To monitor, create a **Schedule** (for example daily at 7:00) and add an integration (email,
   Slack, webhook...) to get the results.

The first time a store is checked in *Only what changed* mode there's nothing to compare with yet, so that run just
records a starting point and saves no rows. If you'd like to see the catalog first, do the first run with *Every
product*: it records the same starting point.

### Input example

```json
{
  "storeUrls": ["https://www.nalgene.com", "https://www.deathwishcoffee.com"],
  "mode": "changes",
  "maxProductsPerStore": 2000,
  "includeVariants": true
}
```

### Output example

Each product or variant is one row. This one is from a real WooCommerce store, on the tool's first check of it:

```json
{
  "store": "https://porterandyork.com",
  "platform": "woocommerce",
  "change": "first_seen",
  "productId": "63137",
  "variantId": "63143",
  "title": "Italian Sausage Links",
  "variantTitle": "Italian Sausage Links: Mild Italian Sausage Links",
  "brand": null,
  "category": "Grinds, Pork",
  "sku": "PY4520",
  "price": 15.0,
  "previousPrice": null,
  "priceChangePercent": null,
  "maxPrice": null,
  "compareAtPrice": null,
  "currency": "USD",
  "available": true,
  "previousAvailable": null,
  "url": "https://porterandyork.com/product/italian-sausage-links/?attribute_italian-sausage-links=Mild+Italian+Sausage+Links",
  "imageUrl": "https://porterandyork.com/wp-content/uploads/Italian-Sausage-links-HOT-scaled.jpg",
  "firstSeenAt": "2026-10-18T01:48:35Z",
  "lastSeenAt": "2026-10-18T01:48:35Z",
  "checkedAt": "2026-10-18T01:48:35Z",
  "createdAt": null,
  "updatedAt": null
}
```

On later checks the change fields are filled in. For example (illustrative values), a price drop looks like:

```json
{ "change": "price_down", "price": 12.0, "previousPrice": 15.0, "priceChangePercent": -20.0, "available": true, "previousAvailable": true }
```

| Field | Meaning |
|---|---|
| store / platform | The store and whether it's `shopify` or `woocommerce` |
| change | `new`, `removed`, `price_up`, `price_down`, `back_in_stock`, `out_of_stock`, `unchanged`, or `first_seen` (nothing to compare with yet, e.g. on a store's first check) |
| productId / variantId | The store's own IDs (variantId is empty for product rows and simple WooCommerce products) |
| title / variantTitle | Product name and variant name (e.g. "Black / 10") |
| brand / category | Shopify vendor and product type; WooCommerce brands and categories |
| sku | The store's SKU, when it publishes one |
| price | Current price (for product rows: the lowest variant price) |
| maxPrice | For product rows: the highest variant price |
| compareAtPrice | The "was" price when the item is on sale, otherwise empty |
| currency | Shopify: from the store's own settings, left empty if the store doesn't say. WooCommerce: from the Store API |
| available | In stock (can be bought) or not |
| previousPrice / previousAvailable | The values at the last check |
| priceChangePercent | Price change since the last check, in percent |
| firstSeenAt / lastSeenAt | When this tool first and last saw the item in your account |
| url / imageUrl | Product link (with the variant selected) and main image |
| createdAt / updatedAt | When the store created and last edited the product (Shopify only) |

When one item changes in two ways at once, `change` shows the price change and `previousAvailable` shows the stock
change.

Each run also saves a **SUMMARY** record (Output tab → *Run summary*) with the number of products read and the count
of each kind of change per store, the reason for any store that couldn't be read, and a warning for any store that
wasn't read in full.

### How much does it cost?

You pay per event; see the **Pricing** tab for the current rates:

- **Each row saved:** one price per product or variant row.
- **Catalog scan:** a small charge per 1,000 items (variants, or products with *One row per variant* off) read from a
  store, only for stores that could be read. A store with 2,500 variants counts as 3 scans. In *Only what changed*
  mode this is what a check that finds nothing costs.

Stores that can't be read cost nothing. Set a **maximum cost per run** in the run options and the tool ends cleanly
at the limit: it doesn't start a store it can't pay for, it stops reading pages it couldn't pay for, and changes it
couldn't save are kept for the next run instead of being lost. If Apify restarts a run (for example when it moves it
to another server), the run carries on where it stopped: the catalog scan isn't charged again and rows are saved in
batches of 500 with progress recorded after each, so a planned move repeats nothing and, at worst, a hard crash in
the middle of a batch could save (and charge) that one batch of up to 500 rows twice. Switch off
*One row per variant* for fewer, cheaper rows.

### Good to know

- **Size limits:** up to 10,000 products and 50,000 variant rows per store per run. Stores are read one at a time.
- **Removed products** are only reported when the whole store was read, and each one is double-checked with the store
  (one request per product, up to 50 per store per run, the longest-unseen first) before it's reported: a product
  list read page by page can skip an item when the store changes while it's being read, and some WooCommerce stores
  hide sold-out items from their list. When the store says the item still exists, its current stock from that answer
  is used (and, for WooCommerce, its price), so a hidden sold-out item is reported as `out_of_stock`, not removed. Items it can't answer
  about yet stay in the history and are asked about on a later run; if the store asks the tool to slow down, the
  checks stop for that run. If a store suddenly answers with an empty list, or more than 30% of its
  items vanish at once, nothing is marked removed until the next run sees the same.
- **New products** are only reported when an earlier check read the whole store, so they really are new. Items the
  tool meets for the first time without that (a store's first check, or a store bigger than *Max products per store*
  where a different set of products comes first) are labelled `first_seen`: they're saved in *Every product* mode
  and quietly added to the history in *Only what changed* mode. To catch new products on a big store, set *Max
  products per store* above its size. When a store isn't read in full, the run's status message and the SUMMARY
  record say so.
- Variant rows and product rows keep separate histories, so switching *One row per variant* starts a new history.
- **WooCommerce variations:** variation prices and stock are read 100 at a time from the Store API. If a store
  doesn't allow that, each variable product is saved as one row with its price range (the log and SUMMARY say so).
- Shopify limits how fast one IP address may read its stores. With Apify Proxy (the default) a blocked request is
  retried on a fresh IP; without a proxy the tool waits when asked to slow down, so runs take longer.
- If a store changes its currency between runs (for example it starts showing another country's prices), prices
  aren't compared that run, so you don't get false price alerts.

### Which stores can't be read?

- Shopify stores that switched off their public product list, and stores built on Shopify with a fully custom front
  end on a different address. The log says "this Shopify store hides its public product list".
- WooCommerce stores that switched off the Store API (some security plugins do), and stores on other platforms
  (Magento, BigCommerce, Wix...). The SUMMARY gives the reason; nothing is charged for them.

### FAQ

**Is this legal?** The tool reads product information that stores publish openly for anyone (it's what their own
website loads): names, prices, stock status and images. It doesn't log in and collects no personal data. Check each
store's terms of use for how you may use the data.

**Why was my first "only changes" run empty?** It recorded the starting point. Run it again later (or schedule it)
and you'll get the changes.

**How do I get alerts?** Schedule the tool, then add an integration on the Integrations tab (email, Slack, a webhook,
Zapier, Make...) that runs after each successful run.

**Pricing set-up (a note for the tool's owner).** Both prices must be flat prices, one price for everyone:
`apify-default-dataset-item` per row and `catalog-scan` per 1,000 items read. Per-plan (tiered) prices aren't
supported; with a missing or tiered price the tool stops before reading or charging anything and says why.

**Something isn't working or you'd like a feature?** Open an issue on the Issues tab. Fixes usually ship within a
day or two.

# Actor input Schema

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

Shopify or WooCommerce stores to read, one per line. Any page of the store works (home page, a product or a collection); the tool finds the whole catalog from there.

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

Every product saves the whole catalog each run, each row labelled with what changed since your last run. Only changes saves just the rows that changed (new, removed, price up or down, back in stock, sold out), which is what you want for a daily schedule; the first run of a store in this mode only records a starting point and saves no rows. Either way you pay per row saved plus a small catalog scan charge per 1,000 items read from each store (see the Pricing tab).

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

Stops reading a store after this many products (at most 10,000; and at most 50,000 variant rows per store). Keep it above the store's size if you want new and removed products reported: they're only reported when the whole store has been read.

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

On: one row per variant, so you see which sizes or colours change price or sell out. Off: one row per product with its lowest and highest price (fewer rows, cheaper).

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

Stores slow down or block busy visitors. Apify Proxy spreads the requests out; the default works for most stores.

## Actor input object example

```json
{
  "storeUrls": [
    "https://www.nalgene.com",
    "https://www.deathwishcoffee.com"
  ],
  "mode": "all",
  "maxProductsPerStore": 1000,
  "includeVariants": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

No description

## `summary` (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 = {
    "storeUrls": [
        "https://www.nalgene.com",
        "https://www.deathwishcoffee.com"
    ],
    "maxProductsPerStore": 1000,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("watchmere/shopify-woocommerce-price-tracker").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.nalgene.com",
        "https://www.deathwishcoffee.com",
    ],
    "maxProductsPerStore": 1000,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("watchmere/shopify-woocommerce-price-tracker").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.nalgene.com",
    "https://www.deathwishcoffee.com"
  ],
  "maxProductsPerStore": 1000,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call watchmere/shopify-woocommerce-price-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,watchmere/shopify-woocommerce-price-tracker"
        }
    }
}
```

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/oPkmzlQT9kEDmNb3y/builds/UwpEBTeRnd9yy0S0B/openapi.json
