# Shopify Competitor Change Monitor (`natty_thomsonite/shopify-competitor-change-monitor`) Actor

Monitor public Shopify stores for new products, promotions, stock changes, and other meaningful catalog activity. See prioritized changes after the first run.

- **URL**: https://apify.com/natty\_thomsonite/shopify-competitor-change-monitor.md
- **Developed by:** [Kibira](https://apify.com/natty_thomsonite) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$15.00 / 1,000 successful store snapshots

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?

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

See what's changing in a competitor's Shopify catalog, including new products, promotions, restocks, and price moves. Run it again anytime to check for what's new.

### What does Shopify Competitor Change Monitor do?

This Actor watches one or more public [Shopify](https://www.shopify.com/) stores and tells you what's changed since the last time you checked. Give it a list of store web addresses. It checks each store's public product catalog, remembers what it saw, and reports what's different on every run after that: new or removed products, promotions starting or ending, items going in or out of stock, price moves, and more. Each result is tagged with an importance level, so you can focus on what matters most.

It only reads what a store already shows to any visitor: the same public catalog page a browser or another shopper can load. No login or private account access is required, and it doesn't collect customer data. It only works with Shopify stores.

Because it runs on Apify, you also get scheduling, an API, dataset export, and run history built in. There's no separate tool to set up.

### What can you use it for?

- **Competitor catalog monitoring:** Keep a running record of what's changing in a competitor's store, all in one place.
- **New product monitoring:** Catch new items as soon as they're listed.
- **Promotion and sale monitoring:** See when a competitor starts or ends a discount.
- **Stock and restock monitoring:** Know when popular items sell out or come back.
- **Price change tracking:** Get notified when a price goes up or down.

### How to get started

1. Open the Actor and enter the store web addresses you want to watch (for example, `https://allbirds.com`).
2. Run it. This first run doesn't report changes yet: it's building a starting point. See [What happens on the first run](#what-happens-on-the-first-run) below.
3. Run it again later, either by hand or on an Apify [schedule](https://docs.apify.com/platform/schedules), using the same store list. Every run after the first compares what it finds against what it saw last time and reports what changed.
4. Open the **Dataset** tab to see the results, or download them as JSON, CSV, Excel, and more.

### What happens on the first run

The first time you check a store, there's nothing to compare against yet, so this run builds a starting point rather than reporting changes. It will never claim a price just changed or an item just sold out on this first run, since there's no earlier state to compare against.

What it can tell you right away, just from how the catalog looks today:

- **Recently published products:** Anything the store listed in the last 7 days.
- **Advertised discounts:** Products currently priced below the store's own listed reference price. This first run can't confirm a sale is genuinely underway, since there's no earlier price to compare against yet. It only reports what the catalog shows today.

If 30% or more of a store's products show an advertised discount, that's reported as a single **widespread advertised discounts** summary instead of hundreds of individual rows. Many stores use a standing reference price across most of their catalog, and one summary is more useful than a wall of near-identical results.

Everything else about the catalog is saved quietly so the next run can compare against it.

### What happens on later runs

Every run after the first compares the current catalog against what was saved last time and reports genuine changes, including:

- New products or products that were removed
- New product options (sizes, colors, etc.) or options that were removed
- Items going out of stock or coming back in stock (an item has to have been in stock for at least 14 days first, so normal pre-launch stock wobble on a brand-new item isn't mistaken for a stockout)
- Promotions (sales) starting or ending
- Price increases or decreases
- A product's advertised discount changing with no change to the actual price
- Old listings reappearing (a sign a product may have been reactivated rather than being genuinely new)

To keep results useful rather than noisy:

- When several things happen to the same product at once (say, a price drop and a stockout), they're reported together as one result with the details attached, instead of several separate rows.
- When a large share of a store's products (30% or more) change price in the same run, that's reported as a single **store-wide price change** instead of flooding the Dataset with individual rows.

### Understanding priorities

Every result is tagged with an importance level: **High**, **Medium**, or **Low**. This lets you tell at a glance what's most likely to matter.

- **High:** A promotion starting, a store-wide price change, or a price move of 15% or more.
- **Medium:** A promotion ending, an item going out of stock, a newly published product, or a new or removed product.
- **Low:** Everything else, including smaller price moves, restocks, new or removed product options, possible relists, and discount-only changes.

Choose the minimum importance level you want to see in the Dataset. The setting is called `minPriority`. Set it to Medium or High to cut down on noise. This only affects what's shown; it never changes what the Actor collects or remembers, so you can change it later without losing anything.

### Understanding your results

Each run adds rows to the **Dataset** tab. Every row tells you which store it's about, what happened, how important it is, and a link straight to the product on the live store so you can see it for yourself. The Dataset tab's default **Overview** shows just these plain-language columns; switch to the **Details / technical** view there if you need the underlying record types and exact field names.

Here's a real result, captured from a live run against allbirds.com. It's a product with an advertised discount:

```json
{
    "store": "allbirds.com",
    "productTitle": "Trino® Tubers - Navy Night",
    "eventLabel": "Advertised discount",
    "priority": "MEDIUM",
    "variantTitle": "S (W5-7)",
    "discountLabel": "75% off",
    "evidenceUrl": "https://allbirds.com/products/trino-tubers-navy-night?variant=31338069262480"
}
```

You don't need any technical background to read this: the store, the product, what happened, how important it is, and a link to check it yourself. The [Technical information](#technical-information) section below documents the exact machine-readable field names (`signalType`, `percentOffCompareAt`, etc.) and every record type in full, for anyone integrating with the Dataset programmatically.

### Pricing

You're charged **$0.015 each time the Actor successfully checks a store and saves its results.** If a store can't be reached or fails to load, you're not charged for that store. There's no extra fee based on how many products a store has, and no extra fee based on how many changes are found.

Your actual monthly cost depends on how many stores you watch and how often you check them. There's no flat subscription fee. For example:

| Usage                     | Approximate monthly cost |
| ------------------------- | ------------------------ |
| 1 store, checked daily    | ≈ $0.45                  |
| 5 stores, checked daily   | ≈ $2.25                  |
| 10 stores, checked daily  | ≈ $4.50                  |
| 25 stores, checked daily  | ≈ $11.25                 |
| 50 stores, checked daily  | ≈ $22.50                 |
| 100 stores, checked daily | ≈ $45.00                 |

Checking weekly instead of daily costs roughly a seventh as much. These are illustrative examples, not guaranteed totals. Your bill reflects your actual usage.

### Limitations and data handling

- **Shopify stores only.** It reads a Shopify store's public product catalog; it doesn't work with other e-commerce platforms.
- **Public data only.** It reads only what the store already shows to any visitor. It never logs in, never bypasses a password or login page, and never accesses a store's admin area or customer accounts. It doesn't collect personal or customer data of any kind.
- **No history before you start.** The first run can't know about changes that happened before you started watching a store. See [What happens on the first run](#what-happens-on-the-first-run) for details.
- **Not real-time.** It reports what changed between the runs you schedule. It doesn't watch continuously in the background, so it can't guarantee catching every change (for example, something that changed and then changed back between two runs).
- **Fixed rules for now.** Thresholds like the 7-day "recently published" window and the 30%-of-catalog store-wide trigger are fixed at this stage and aren't user-configurable, aside from the `minPriority` setting above.

### Technical information

#### Input reference

| Field                 | Type                        | Required           | Description                                                                                                                                     |
| --------------------- | --------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `storeUrls`           | array of strings            | Yes                | Store web addresses to watch, up to 10 per run (e.g. `"https://allbirds.com"` or `"gymshark.com"`).                                             |
| `maxProductsPerStore` | integer                     | No                 | Optional limit on how many products are checked per store. Useful for testing or keeping costs predictable. Leave empty to check every product. |
| `minPriority`         | `LOW` | `MEDIUM` | `HIGH` | No (default `LOW`) | The minimum importance level to include in the Dataset. See [Understanding priorities](#understanding-priorities) above.                        |

See the **Input** tab for the full schema with inline descriptions.

#### Dataset record types

Every pushed row has a `recordType` field identifying which of five record families it belongs to, following one hierarchy: store-level → product-level → variant-level detail.

The record types below that describe a signal or change also carry a few additive, display-only fields for the Dataset's default **Overview** view: `eventLabel` (a plain-English rendering of `signalType`/`primaryType`/`type`) and `discountLabel`/`priceChangeLabel` (plain-English renderings of `percentOffCompareAt`/`percentChange`/`medianPercentMove`/`affectedPercent`, for example `"75% off"` or `"18% decrease"`). These are additive: they never replace or change the machine-readable fields, which you should use for anything programmatic.

**`store_summary`**: One per store per run, always present. Reports whether the check succeeded, how many products and variants were found, and whether this was the first run (`snapshotStatus: "baseline_created"`) or a comparison run (`"baseline_updated"`).

**`classified_baseline_signal`**: First run only. A current-state observation (recently published, or an advertised discount against the store's own reference price), never a historical change. `signalType: "currently_on_sale"` is the machine-readable value behind the "Advertised discount" label; the name is kept for compatibility with existing integrations, but the label was changed because Run 1 has no earlier price to compare against, so it can't confirm a genuine sale is underway.

**`classified_baseline_rollup`**: First run only, and only when 30% or more of a store's products show an advertised discount in the same run. Replaces those individual `classified_baseline_signal` rows with one store-level summary (`eventLabel: "Widespread advertised discounts"`), so a store with a widespread reference-pricing pattern doesn't flood the Dataset with hundreds of near-identical rows. Reports `affectedProductCount`, `totalProductCount`, and `affectedPercent` so you can see exactly how much of the catalog was affected. The per-product facts behind it aren't destroyed: they're independently visible on the store's own public product pages, and they're recomputed fresh on every run.

Real example, captured from a live run:

```json
{
    "recordType": "classified_baseline_rollup",
    "store": "taylorstitch.com",
    "type": "widespread_advertised_discount",
    "eventLabel": "Widespread advertised discounts",
    "priority": "MEDIUM",
    "affectedProductCount": 2647,
    "totalProductCount": 3813,
    "affectedPercent": 69.42,
    "discountLabel": "69.42% of catalog"
}
```

**`classified_change`**: Later runs only. One prioritized, product-level result. `evidence` lists any other classifications folded into it, and `variantEvidence` preserves every underlying variant's own numbers when more than one contributed.

Illustrative example (field shapes are real and tested; this specific event wasn't captured live during development):

```json
{
    "recordType": "classified_change",
    "store": "example-store.com",
    "productTitle": "Trail Runner - Charcoal",
    "primaryType": "sale_started",
    "eventLabel": "Sale started",
    "priority": "HIGH",
    "evidence": [],
    "variantTitle": "US 10",
    "percentChange": -25,
    "priceChangeLabel": "25% decrease",
    "evidenceUrl": "https://example-store.com/products/trail-runner-charcoal?variant=123"
}
```

**`classified_sitewide_change`**: Later runs only, and only when 30% or more of active products moved price in one run.

```json
{
    "recordType": "classified_sitewide_change",
    "store": "example-store.com",
    "type": "sitewide_price_change",
    "eventLabel": "Store-wide price change",
    "priority": "HIGH",
    "affectedProductCount": 42,
    "medianPercentMove": -18.5,
    "priceChangeLabel": "18.5% decrease"
}
```

#### Local development

```bash
npm install
npm run build
npm run start:dev   # runs src/main.ts directly via tsx, using local storage/
npm test            # vitest
```

Local runs read input from `storage/key_value_stores/default/INPUT.json` and write to the local `storage/` directory, which is **not** synced to Apify Cloud. Use a real Apify run to verify Cloud behavior. See [`AGENTS.md`](AGENTS.md) for the project's engineering rules and frozen product decisions.

# Actor input Schema

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

The Shopify stores you want to monitor. Enter each store's web address, for example "https://allbirds.com" or "gymshark.com".

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

Optional limit on how many products to check per store. Useful for testing or keeping costs predictable. Leave empty to check every product.

## `minPriority` (type: `string`):

Only show results at or above this importance level. It doesn't change what the Actor checks or remembers, so you can adjust it later without losing anything.

## Actor input object example

```json
{
  "storeUrls": [
    "https://allbirds.com"
  ],
  "minPriority": "LOW"
}
```

# Actor output Schema

## `results` (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://allbirds.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("natty_thomsonite/shopify-competitor-change-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://allbirds.com"] }

# Run the Actor and wait for it to finish
run = client.actor("natty_thomsonite/shopify-competitor-change-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://allbirds.com"
  ]
}' |
apify call natty_thomsonite/shopify-competitor-change-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,natty_thomsonite/shopify-competitor-change-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/3aD9JxxUpaowElnAv/builds/J9QotQ25X3NgssjDv/openapi.json
