# WooCommerce Catalog & Price Monitor - Diffs & Low-Stock Alerts (`yasaslive/woo-monitor`) Actor

Export any public WooCommerce store's full catalog (products, variations, prices, stock) from the Store API and get typed change alerts on every scheduled run — price drops, new products, stock-outs, and low-stock-remaining warnings.

- **URL**: https://apify.com/yasaslive/woo-monitor.md
- **Developed by:** [Eonix Pvt Ltd](https://apify.com/yasaslive) (community)
- **Categories:** Lead generation, E-commerce, Integrations
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.00005 / actor start

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## WooCommerce Catalog & Price Monitor

**Track any public WooCommerce store's prices and stock. Get told the moment something changes.**

Point it at a competitor, a supplier, or your own shop. The first run exports the whole catalog.
Every run after that tells you exactly what moved: price drops, price rises, products going out of
stock, new products, discontinued products — and the one thing a Shopify monitor cannot give you,
**how many units are left**.

Runs on a schedule. Sends the summary to Slack. Costs cents per check.

***

### Who this is for

- **Retailers watching competitors** — know within the hour when someone undercuts you.
- **Brands enforcing MAP** — catch resellers pricing below your minimum, with a timestamped record.
- **Dropshippers and resellers** — know when a supplier's stock is running down *before* it hits zero.
- **Store owners** — monitor your own catalog for accidental price changes or products falling out
  of stock.
- **Analysts and AI agents** — a clean, consistent JSON feed of catalog and change data.

***

### What you get

Every run produces:

|                           |                                                                                                                                                                                       |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Full catalog export**   | One row per product — or per variation, where a product has them. Price, sale price, currency, stock, SKU, categories, tags, brands, attributes, images, weight, dimensions, ratings. |
| **Change events**         | One row per detected change, with the old value, the new value, and the percentage move.                                                                                              |
| **Low-stock warnings**    | Fired when remaining units fall into your threshold.                                                                                                                                  |
| **Run summary**           | Which stores worked, what changed, how long it took.                                                                                                                                  |
| **Cost estimate**         | Written *before* the work starts, so you always know what a run will cost.                                                                                                            |
| **Slack / webhook alert** | A grouped digest per store: every low-stock hit plus the biggest price moves.                                                                                                         |

The first run for each store is a **baseline** — it exports the catalog and remembers it. From the
second run onwards you get change events. That is how it knows what "changed" means.

***

### The seven change events

| Event             | Fires when                                                                   |
| ----------------- | ---------------------------------------------------------------------------- |
| `price_drop`      | The price went down. Includes old price, new price, and % change.            |
| `price_increase`  | The price went up.                                                           |
| `low_stock`       | Remaining units fell into your threshold, or fell further while already low. |
| `out_of_stock`    | A product sold out.                                                          |
| `back_in_stock`   | A product returned.                                                          |
| `new_product`     | A product appeared in the catalog.                                           |
| `removed_product` | A product disappeared.                                                       |

**Low-stock alerts do not repeat themselves.** If an item sits at 2 units for a week, you get told
once — when it drops to 2 — not every single run. A monitor that reports the same three items every
fifteen minutes gets muted, and a muted monitor is worthless. Each alert carries the previous count so
you can see the movement.

***

### Getting started

1. Paste one or more store URLs into **Store URLs**. Any page on the store works — a product page, a
   category page, the homepage.
2. Set **Low-stock threshold** to whatever "running low" means for you (default: 5 units).
3. Click **Start**.
4. Run it again — or better, put it on a **schedule** — to start receiving change events.
5. Optionally paste a **Slack webhook** to get the digest in a channel.

That is the whole setup. No API keys, no store credentials, no plugin to install on the store.

**Tip:** keep the *Snapshot key-value store name* the same across scheduled runs. That stored snapshot
is what makes change detection possible. Changing it starts over from a fresh baseline.

***

### Not every WooCommerce store can be monitored — check yours first

This is the single most important thing to know before you buy, so it is at the top rather than
buried.

The actor reads a store's **WooCommerce Store API** — the same public endpoint the shop's own website
calls to display its products. Most WooCommerce stores expose it. Some do not:

- Some WordPress hosts and SEO plugins publish a rule asking crawlers to stay away from that endpoint.
  Those stores are reported as **`skipped`** and **cost you nothing**.
- Some stores have the Store API switched off or behind a firewall. Reported as
  **`endpoint_unavailable`**, and again **not charged**.

**You are never charged for a store that cannot be read.** Run the actor once against your list of
targets to find out which qualify — failed detections are free, so a qualification run on a list of
non-WooCommerce domains costs $0.00.

For transparency, from a sample of 42 domains tested while building this actor: 6 ran WooCommerce with
the Store API reachable, and 3 of those 6 asked crawlers to stay away from it. Test your own list.

***

### Pricing

Pay per event. No subscription, no rental.

| You are charged                    | Price  |
| ---------------------------------- | ------ |
| Per store successfully snapshotted | $0.05  |
| Per 1,000 catalog records exported | $0.50  |
| Per change event                   | $0.002 |
| Per low-stock alert                | $0.005 |
| Per Standby API request            | $0.001 |

**Nothing is charged for a store that fails, is skipped, or is not WooCommerce.** Every charge follows
data actually being written.

A low-stock alert is charged **instead of** a change event, never on top of it.

#### What a real run costs

Two real stores, 212 products, 286 records including variations:

| Line            | Quantity         | Cost      |
| --------------- | ---------------- | --------- |
| Store snapshots | 2                | $0.10     |
| Catalog export  | 286 records      | $0.50     |
| Change events   | 0 (baseline run) | $0.00     |
| **Total**       |                  | **$0.60** |

Turn the catalog export **off** and the same monitoring run costs **$0.10**. That is the pattern most
people want: export the catalog once, then run the alert loop hourly for pennies.

#### You see the cost before the run does the work

Every run calculates what it expects to cost and writes it to `COST-ESTIMATE.json` before fetching
anything — using each store's own published product count, not a guess:

```
Estimated cost of this run:
  catalog-snapshot        2 × $0.0500 = $0.1000
  variant-exported        1 × $0.5000 = $0.5000
  change-alert            0 × $0.0020 = $0.0000
  low-stock-alert         0 × $0.0050 = $0.0000
  TOTAL                                $0.60
```

Change and low-stock lines show zero because they honestly cannot be known before the comparison runs
— a monitoring run where nothing moved costs nothing beyond its snapshots. Every line explains where
its number came from.

#### Free plan limits

On the Apify free plan, runs are capped at **1 store, 50 records, and no alert delivery**. Change
events are still detected and still appear in your results — they just are not posted to Slack or a
webhook. Any paid plan runs uncapped.

***

### Settings

| Setting                          | Default                 | What it does                                                                         |
| -------------------------------- | ----------------------- | ------------------------------------------------------------------------------------ |
| **Store URLs**                   | two demo stores         | The stores to monitor. Any URL on the store works.                                   |
| **Export the full catalog**      | on                      | Turn off for cheap alert-only monitoring.                                            |
| **Low-stock threshold**          | 5 units                 | When to warn you. Set to 0 to switch low-stock alerts off.                           |
| **Snapshot store name**          | `woo-monitor-snapshots` | Keep it stable across scheduled runs.                                                |
| **Change event filters**         | none                    | Watch only certain products, categories or tags — or only moves above a % threshold. |
| **Slack webhook**                | —                       | Where to send the digest.                                                            |
| **Alert webhook**                | —                       | Any HTTPS endpoint that accepts JSON.                                                |
| **Maximum products per store**   | 5,000                   | Bounds run time on very large catalogs.                                              |
| **Fetch exact variation prices** | on                      | See "Products with variations" below.                                                |
| **Variation lookup budget**      | 500                     | How many of those lookups to allow per store.                                        |
| **Proxy**                        | Apify Proxy             | Switch to residential if a store blocks datacenter addresses.                        |

#### Filters only narrow your alerts, never your data

Filters apply to change events only. The catalog export always contains everything. A filter can never
silently shrink the data you paid to export.

```json
{
    "nameIncludes": ["ribeye", "wagyu"],
    "categories": ["Beef"],
    "minPctChange": 10
}
```

That watches only beef products whose name mentions ribeye or wagyu, and only price moves of 10% or
more. Stock and low-stock alerts are never filtered out by a percentage threshold.

***

### How low-stock detection actually works

Most listings would promise "low stock alerts" and leave it there. Here is the honest version, because
finding out afterwards is worse.

WooCommerce only publishes a "units remaining" number when a store has **stock management switched on**
*and* the quantity is already low. Measured on two real stores while building this:

| Store                   | Products checked | Published a units-remaining number |
| ----------------------- | ---------------- | ---------------------------------- |
| Store A (food retailer) | 69               | **0**                              |
| Store B (software)      | 100              | **5**                              |

So the actor uses **two** signals:

1. The store's explicit remaining-units field, when it publishes one.
2. Otherwise, the number written on the product page itself — `"2 in stock"`, `"Only 3 left in stock"`
   — tracked across runs so a falling count triggers the alert.

Every record shows both readings, and every alert says which signal fired it. **If a store publishes
no quantity at all, no low-stock alert can fire for it** — you will see `out_of_stock` instead, when
it finally sells out. That is a real limit of what WooCommerce makes public, not something any tool can
work around.

***

### Products with variations

A variable product — three sizes of the same shirt — is exported as one row per variation, each with
its own price, SKU and stock.

WooCommerce does not include variation prices in its main product listing, so the actor fetches them
separately, within the budget you set. Rows priced exactly are marked `priceSource: "variation"`. If a
catalog has more variations than the budget allows, the remaining rows inherit the parent product's
price and are marked `priceSource: "parent"` — so you always know which number you are looking at.

***

### Sample output

A catalog row (prices are exact — held as integer cents internally, so a price that did not move can
never look like it did):

```json
{
    "kind": "record",
    "target": "examplestore.com",
    "status": "ok",
    "collectedAt": "2026-08-16T15:29:13.551Z",
    "source": "wc/store/v1",
    "name": "Infinity Bundle Lifetime License – Payment Plan (6 – Pay)",
    "permalink": "https://examplestore.com/product/infinity-bundle-6-pay/",
    "type": "subscription",
    "price": 500,
    "priceCents": 50000,
    "regularPrice": 1700,
    "salePrice": 500,
    "currency": "USD",
    "onSale": true,
    "isInStock": true,
    "stockAvailability": "2 in stock",
    "stockCount": 2,
    "lowStockRemaining": 2,
    "categories": [],
    "imageSrc": "https://examplestore.com/wp-content/uploads/infinity-bundle.jpeg",
    "priceSource": "product"
}
```

A low-stock alert:

```json
{
    "kind": "change",
    "type": "low_stock",
    "target": "examplestore.com",
    "name": "Infinity Bundle Lifetime License – Payment Plan (3 – Pay)",
    "url": "https://examplestore.com/product/infinity-bundle-3-pay/",
    "lowStockRemaining": 2,
    "previousRemaining": 9,
    "signal": "low_stock_remaining",
    "threshold": 5,
    "detectedAt": "2026-08-16T15:29:13.555Z"
}
```

A price drop:

```json
{
    "kind": "change",
    "type": "price_drop",
    "target": "examplestore.com",
    "name": "LifterLMS Powerpack",
    "oldPrice": 298,
    "newPrice": 149,
    "oldPriceCents": 29800,
    "newPriceCents": 14900,
    "pctChange": -50
}
```

Every row from every actor in this series carries the same envelope — `kind`, `target`, `status`,
`collectedAt`, `source` — so you can wire up one integration and reuse it.

***

### Use it from an AI agent

**MCP.** The actor is available as a tool through Apify's MCP server at `https://mcp.apify.com`. Your
agent can ask for a store's catalog, or for what changed since last time, and gets structured JSON
back.

**Standby HTTP API.** With Standby enabled the actor answers instantly over HTTP, with no run to
schedule:

```
GET https://<your-actor>.apify.actor/?storeUrls=examplestore.com&lowStockThreshold=3
GET https://<your-actor>.apify.actor/health
```

The response is the same JSON a scheduled run produces. Health checks are free.

***

### Reliability

Measured on this build, 2026-08-16:

|                                              |                                       |
| -------------------------------------------- | ------------------------------------- |
| Runs completed successfully                  | **10 of 10**                          |
| Typical run, two stores, 286 records         | **under 1 minute**                    |
| Products captured vs. published by the store | 69/69 and 143/143 — nothing missed    |
| Repeat run with nothing changed              | 0 false alerts                        |
| Pointed at a non-WooCommerce site            | Completed successfully, charged $0.00 |
| Automated tests                              | 199 passing                           |

**When a source breaks, you will know.** If a store starts returning data in an unexpected shape, the
actor records it in your results as a health row rather than quietly returning nothing. A run that
finds no usable data tells you why — it never reports success while handing you an empty file.

### Support

Report problems through the **Issues** tab on this actor's page. Broken-source issues get a first
response within **3 business days** and a fix or documented workaround within **7**.

### What this actor does not do

- It **cannot read stores that ask crawlers away from their product API**, or that have it disabled.
  Both are reported clearly and cost nothing.
- It **cannot report a stock quantity a store does not publish.**
- It **does not read Shopify stores** — those use a completely different system.
- It **does not see prices behind a login**, member pricing, or per-customer pricing. Public prices only.
- It **does not keep price history for you.** It compares the last run with this one. Keep your results
  if you want a long-term series.
- It **does not use a browser**, so it cannot get past a JavaScript bot wall or a CAPTCHA.
- It **does not touch carts, orders, or customer data.** It reads the public product catalog and
  nothing else.

### Is this allowed?

The actor reads the same public product data any shopper's browser downloads when they visit the shop
— names, prices, and stock status. It reads nothing private, sends nothing to the store, and respects
each store's stated crawler preferences, skipping those that ask to be left alone. It is intended for
competitive price research, MAP enforcement, and monitoring your own catalog.

# Actor input Schema

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

Store URLs to monitor. Any URL on the store works — it is normalised to the https origin (for example https://porterandyork.com/product-category/beef/ becomes https://porterandyork.com). Each store is read from its public WooCommerce Store API (/wp-json/wc/store/v1/products). Not sure which of your prospects qualify? Run `npm run discover -- domain1.com domain2.com` first. Note that some WordPress hosts ship a robots.txt that excludes /wp-json/; those stores are reported as "skipped" and cost nothing.

## `exportCatalog` (type: `boolean`):

Push every product/variation to the dataset (kind: "record"). Turn this off to run in alert-only mode: snapshots and change events are still produced, but the catalog rows are not stored — much cheaper for high-frequency schedules.

## `lowStockThreshold` (type: `integer`):

Fire a low\_stock event when a product's remaining units fall to this number or below. The event fires when stock CROSSES into the threshold or falls further while low — not on every run while it sits there — so a scheduled monitor does not repeat the same three items every fifteen minutes. Set to 0 to disable low-stock alerting entirely.

## `storeName` (type: `string`):

Named key-value store that holds the previous run's snapshot per store (one record per hostname). Keep this stable across scheduled runs — it is what makes diffing possible. Use a different name to keep separate monitoring streams apart.

## `watchFilters` (type: `object`):

Optional filters applied to change events only — the catalog export is never filtered. All supplied conditions must match (AND); within a list, any entry matches (OR). nameIncludes/categories/tags are case-insensitive; nameIncludes is a substring match, categories and tags are exact matches. minPctChange (default 0 = report every price move) keeps only price moves whose absolute percentage change is greater than or equal to the threshold; it never filters stock, low-stock or product events.

## `slackWebhookUrl` (type: `string`):

Optional. Slack incoming webhook (https://hooks.slack.com/services/...). Receives a grouped summary per store: event counts, EVERY low-stock hit, and the ten most significant price moves. Nothing is sent when a run produces no change events.

## `alertWebhookUrl` (type: `string`):

Optional. Any HTTPS endpoint that accepts a POST with a JSON body. Receives the same grouped summary as a machine-readable payload. Must resolve to a public address.

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

Stop paginating a store after this many products. A store cut short by this limit is reported as "partial" and its change detection is skipped for that run, because a truncated catalog would look like mass product removal on the next diff. Raise it for large catalogs; lower it to bound run time.

## `fetchVariations` (type: `boolean`):

The Store API's product list returns variation ids and attributes but NO per-variation price or stock, so exact variation figures cost one extra request each. With this on, variation rows carry their own price and stock (priceSource: "variation"). With it off — or past the budget below — they inherit the parent product's price and are labelled priceSource: "parent" so you always know which you are looking at.

## `maxVariationFetchesPerStore` (type: `integer`):

Ceiling on the extra per-variation requests described above. Variation rows beyond the budget keep the parent price and are labelled priceSource: "parent". Set to 0 to never fetch variations individually.

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

Apify Proxy settings. Datacenter proxies are enough for the Store API; switch to residential if a store blocks datacenter ranges.

## Actor input object example

```json
{
  "storeUrls": [
    "https://porterandyork.com",
    "https://lifterlms.com"
  ],
  "exportCatalog": true,
  "lowStockThreshold": 5,
  "storeName": "woo-monitor-snapshots",
  "watchFilters": {
    "nameIncludes": [],
    "categories": [],
    "tags": [],
    "minPctChange": 0
  },
  "maxProductsPerStore": 5000,
  "fetchVariations": true,
  "maxVariationFetchesPerStore": 500,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

One row per product or variation (kind: "record"), one row per detected change (kind: "change"), any health signals (kind: "health"), and a final run summary (kind: "summary").

## `changeReport` (type: `string`):

Every change event this run detected, with the per-store results and the filters that were applied. Empty events array when nothing moved.

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

Per-store outcome, event counts, durations and artifact links for this run.

## `costEstimate` (type: `string`):

What this run was expected to cost, written BEFORE the main work loop, with the basis for every line. Derived from each store's own published product count.

# 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://porterandyork.com",
        "https://lifterlms.com"
    ],
    "watchFilters": {
        "nameIncludes": [],
        "categories": [],
        "tags": [],
        "minPctChange": 0
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("yasaslive/woo-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://porterandyork.com",
        "https://lifterlms.com",
    ],
    "watchFilters": {
        "nameIncludes": [],
        "categories": [],
        "tags": [],
        "minPctChange": 0,
    },
}

# Run the Actor and wait for it to finish
run = client.actor("yasaslive/woo-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://porterandyork.com",
    "https://lifterlms.com"
  ],
  "watchFilters": {
    "nameIncludes": [],
    "categories": [],
    "tags": [],
    "minPctChange": 0
  }
}' |
apify call yasaslive/woo-monitor --silent --output-dataset

```

## MCP server setup

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