# WooCommerce Catalog & Price Change Monitor (`maydit/woocommerce-catalog-monitor`) Actor

Export public WooCommerce products, prices and availability. Compare complete catalog snapshots for price changes, restocks and products entering or leaving a scope. No API key required.

- **URL**: https://apify.com/maydit/woocommerce-catalog-monitor.md
- **Developed by:** [Brandt May](https://apify.com/maydit) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 product results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## WooCommerce Catalog & Price Change Monitor

Export products from public WooCommerce Store APIs, or run a recurring monitor that returns the changes since the last complete catalog snapshot. No API key, browser or proxy is required for supported stores.

Use this for competitor assortment checks, supplier availability monitoring, and product exports. Every row includes its store, observation time and catalog completeness. Every run writes a per-store `SUMMARY` report.

### Quick start

Run with empty input to retrieve up to 50 products from WordPress Mercantile:

```json
{}
```

Export another supported store:

```json
{
  "storeUrls": ["https://mercantile.wordpress.org"],
  "mode": "catalog",
  "maxProductsPerStore": 200
}
```

The supplied URL must be a WooCommerce store base URL, optionally with a WordPress installation subdirectory. The store must expose `/wp-json/wc/store/v1/products` and allow it in `robots.txt`. Stores that disable or restrict this endpoint are reported as failed sources. This Actor does not bypass HTTP 401/403 responses or robots restrictions.

### Monitor changes

```json
{
  "storeUrls": ["https://mercantile.wordpress.org"],
  "mode": "changes",
  "maxProductsPerStore": 1000,
  "baselineKey": "merch-monitor",
  "minimumPriceChangePercent": 0
}
```

The first changes run emits all initial products with `changeTypes: ["baseline"]`. A subsequent unchanged run succeeds with zero rows and a warning. To repeat the initial comparison, select a new `baselineKey`.

The named key-value store `woocommerce-catalog-monitor-state-v1` holds baselines independently of individual run storage. Each scope includes the normalized store URL, search, category IDs, availability filter, sale filter, and baseline namespace. Changing any of those starts a separate scope. Changing the run limits or price threshold does not start a new scope. Catalog mode never writes or advances baselines.

**A partial crawl never replaces a baseline or emits removals.** Product limits, page limits, timeouts, bad pages, changing source totals, and duplicate products across pages make a crawl incomplete. Observed products may still produce changes, with `catalogComplete: false`. Check `SUMMARY` and raise limits if the intended scope is larger. Exactly reaching the product limit can still be complete when the source headers confirm the full count.

Baselines advance only after output is saved. Avoid overlapping runs using the same baseline key and scope. A pre-write check detects many concurrent updates, but the key-value store does not provide an atomic lock.

If the dataset charge limit prevents another row from being saved, the run stops cleanly, preserves that store's previous baseline, and reports `SUMMARY.stoppedOnChargeLimit: true`. Emitted counts reflect saved rows; remaining stores appear in `notProcessed`. Earlier saved rows are retained.

### Inputs

| Field | Default | Meaning |
|---|---|---|
| `storeUrls` | WordPress Mercantile | Up to 20 stores; omitted or empty uses the sample. |
| `mode` | `catalog` | Current products, or `changes` compared with a complete baseline. |
| `maxProductsPerStore` | 50 | 1–5,000 observed products per store. Removal events can add output rows. |
| `maxPagesPerStore` | 100 | 1–100 pages; up to 100 products per page. |
| `search` | none | Public Store API text search, up to 200 characters. |
| `categoryIds` | none | Up to 50 store-specific integer category IDs. |
| `onSale` | false | True restricts to sale products; false includes all. |
| `stockStatus` | `any` | `any`, `instock`, `outofstock`, or `onbackorder`. |
| `minimumPriceChangePercent` | 0 | Minimum current-price movement for the `price_changed` label. |
| `baselineKey` | `default` | Independent monitor namespace; letters, digits, `_`, `-`; 1–80 characters. |
| `maxRunSeconds` | 240 | 30–3,600 seconds, also limited by the platform deadline. |

The threshold applies to current price only. Availability, currency, regular/sale price and other detail changes can still emit a record. A move from a zero current price always qualifies. Below-threshold changes still advance the complete baseline, so thresholds measure changes since the previous complete run, not cumulative movement since the last emitted event.

### Output

One dataset row represents a current product, or a product leaving a completely read scope. Standard Apify exports support JSON, CSV and Excel; JSON preserves nested before/after records.

| Fields | Meaning |
|---|---|
| `storeUrl`, `productId`, `productUrl`, `name`, `sku`, `type` | Source identity. |
| `currency`, `currencyMinorUnit`, `price`, `priceMinor`, `regularPrice`, `salePrice`, `priceRange` | Public prices; decimal amounts are **strings** to preserve precision. |
| `onSale`, `isInStock`, `isOnBackorder` | Public status, or null when absent. These are not inventory quantities. |
| `averageRating`, `reviewCount` | Public product rating summary, when supplied. |
| `categories`, `imageUrls`, `variationCount` | Public catalog details; image URLs are capped at 20 and categories at 100 per product. |
| `observedAt`, `catalogComplete` | Observation time and whether the entire scope was read. |
| `changeTypes` | One or more labels listed below. |
| `previousValues`, `currentValues` | Before/after normalized product records; null where no side exists. |

Change labels: `snapshot` (catalog export), `baseline` (initial changes run), `added_to_scope`, `removed_from_scope`, `price_changed`, `availability_changed`, `currency_changed`, and `details_changed`.

`removed_from_scope` means absent from a complete current response for that scope. A product may have stopped matching a filter, become unpublished, or been removed; the Actor does not claim to know which. Removed rows retain the last observed product fields at the top level; `currentValues` is null.

`SUMMARY` reports observed/emitted counts, expected source totals, pages read, errors, completeness, stop reasons and baseline updates. One failed store does not discard other usable results. If no products were observed and no source completed legitimately, the Actor fails with a source diagnostic. A complete empty scope and an unchanged comparison succeed with warnings. Error rows are never written to the paid dataset.

### Prices and limitations

Launch price: **$2 per 1,000 emitted records** ($0.002 each) on Free/Bronze, with an Actor Start event of $0.00005. Silver is 20% lower and Gold/Platinum/Diamond 40% lower. Changes mode charges only emitted records, including initial baseline and removal records. Consult the live Pricing tab for the applied rate.

- This reads the [official public WooCommerce Store API](https://developer.woocommerce.com/docs/apis/store-api/), not the authenticated WooCommerce management API.
- It returns publicly published products. It does not log in, supply product passwords, access customers/orders, or change carts.
- Public prices may depend on a merchant's currency or tax plugins. `price` is what the API returned in its declared currency. Product price ranges are retained; exact variation prices and inventory quantities are not promised.
- Optional fields can be null or empty. This Actor does not infer sales volume, revenue, inventory quantity, or store ownership.
- An API is not a transactional database snapshot. Stable sorting, total checks and duplicate checks reduce unsafe comparisons, but changes during collection can still affect results.
- A source can block cloud IPs or return different results from different locations. Such failures are reported, without proxy fallback.
- Use store data in accordance with the merchant's terms and your authorization.

### Development

`npm test` runs fixture tests for money precision, filters/scopes, baseline safety, pagination, error-shaped responses, robots denial, deadlines and bounded retries. The shared `src/runtime.js` supplies bounded requests with public network validation and redirect checks. Use isolated `CRAWLEE_STORAGE_DIR` directories for local execution.

Three catalog-only example task inputs are in `tasks/`. They are drafts for verification before publication; none schedules a monitor or changes external settings.

# Actor input Schema

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

Up to 20 public WooCommerce store base URLs, optionally including a WordPress installation subdirectory. Empty or omitted uses WordPress Mercantile. The public Store API must be enabled and robots.txt must permit access.

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

Catalog exports current products without changing saved baselines. Changes compares with a named baseline; its first run emits initial products labeled baseline. Only complete crawls advance a baseline. Omitted mode means catalog.

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

Hard cap on observed current products per store. A run that hits this cap before the full source scope is read preserves the old baseline and suppresses removals. Removed-product events may add rows beyond this cap in changes mode.

## `maxPagesPerStore` (type: `integer`):

Pagination safety limit. Pages contain up to 100 products. Reaching the limit before the complete catalog preserves the saved baseline.

## `search` (type: `string`):

Optional public Store API product search, up to 200 characters. Changing the search selects a separate baseline scope.

## `categoryIds` (type: `array`):

Optional store-specific positive integer category IDs. These are category IDs from product output, not category names. Up to 50 values; changing them selects a separate baseline scope.

## `onSale` (type: `boolean`):

When true, request only sale products. False or omitted includes both sale and regular products. Leaving this filtered scope does not prove that a product was deleted.

## `stockStatus` (type: `string`):

Limit the source scope to a public stock status, or include all. Filters affect scope membership; removed\_from\_scope means absent from this scope, not necessarily deleted from the store.

## `minimumPriceChangePercent` (type: `number`):

Only emit a price\_changed label when the current price moves by at least this percentage from the previous complete snapshot. Other changes can still emit the product. A move from zero always qualifies. The baseline advances even for changes below this threshold.

## `baselineKey` (type: `string`):

Changes mode only. Use 1-80 letters, digits, underscores or hyphens to isolate independent monitors. Store URL and filters are also included automatically. First use emits all initial products. Avoid overlapping runs using the same key and scope.

## `maxRunSeconds` (type: `integer`):

Own wall-clock budget, also bounded by the platform timeout with a safety margin. Requests and retry sleeps share this budget. Partial catalogs never replace a baseline.

## Actor input object example

```json
{
  "storeUrls": [
    "https://mercantile.wordpress.org"
  ],
  "maxProductsPerStore": 50,
  "maxPagesPerStore": 100,
  "onSale": false,
  "stockStatus": "any",
  "minimumPriceChangePercent": 0,
  "baselineKey": "default",
  "maxRunSeconds": 240
}
```

# Actor output Schema

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

One current product or removed-from-scope event per row, with exact decimal price strings, public availability, before/after values and completeness.

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

Per-store observed/emitted counts, pagination, expected totals, errors, baseline updates and stop reasons.

# 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://mercantile.wordpress.org"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("maydit/woocommerce-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://mercantile.wordpress.org"] }

# Run the Actor and wait for it to finish
run = client.actor("maydit/woocommerce-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://mercantile.wordpress.org"
  ]
}' |
apify call maydit/woocommerce-catalog-monitor --silent --output-dataset

```

## MCP server setup

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