# Shopify Competitors · Catalog Changes by Store URL (`corent1robert/shopify-catalog-change-monitor`) Actor

Schedule public Shopify shops. Get one row per change: new variants, price drops, sold out or back in stock. First run saves a baseline — not a catalog dump. No login. No Shopify admin.

- **URL**: https://apify.com/corent1robert/shopify-catalog-change-monitor.md
- **Developed by:** [Corentin Robert](https://apify.com/corent1robert) (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 $2.80 / 1,000 catalog changes

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?

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

## Shopify Competitors · Catalog Changes by Store URL

**Watch public Shopify shops and get one row per catalog move** — new variants, price drops, price increases, sold out, back in stock. Not another full catalog dump you re-export every morning.

Paste a **collection**, a **product**, or a **shop home**. The Actor photographs the public storefront on the first run, then only writes **what changed**. Optionally ping **Slack** or **Telegram** when something moved.

**No login. No Shopify Admin. No API key. No proxy.**

**Built for:** DTC / brand ops · e-com agencies · merchandisers · marketplace sourcing · founders watching 1–2 rivals without a BI stack

***

### What does Shopify Catalog Watch do?

It opens the **same public catalog shoppers see** on a [Shopify](https://www.shopify.com/) storefront (`*.myshopify.com` or a custom domain on Shopify), compares it to the **last snapshot**, and writes a dataset row when a variant appeared, disappeared, changed price, or flipped public availability.

| This Actor | Not this Actor |
|------------|----------------|
| **Deltas** vs last run | Full catalog dump every time |
| Public **price** and **available** | Warehouse qty, cost, metafields, Admin |
| Collection / product / home URLs | Pages, blogs, cart, Instagram (ignored) |
| Slack or Telegram **when it moved** | A chat flood on quiet days |

Need the **whole catalog** as rows (not the diff)? Use **[Shopify Catalog · Products by Store URL](https://apify.com/corent1robert/shopify-products-scraper)**.

***

### Why schedule this instead of re-scraping the shop?

Competitor dumps are expensive and noisy. You already know the assortment. You want **what moved overnight**: a new SKU, a 15% drop, a restock.

- **First run = baseline** (one billed snapshot per shop / collection). The Actor remembers the catalog.
- **Later runs = change rows only.** A quiet day is a cheap start, not 2,000 variant rows.
- **Schedule daily** in Apify Console. Same input every time. Snapshots live in a named key-value store (`shopify-catalog-watch`).
- **Integrations:** Dataset → Google Sheets / Make / n8n, **or** a Slack / Telegram ping from the run itself.

***

### Who is this for?

| You are… | Typical goal | Suggested setup |
|----------|--------------|-----------------|
| Brand / DTC ops | Catch a competitor promo the same day | 3–10 rival URLs, schedule daily, price **5%** |
| E-com agency | Tell a client “what moved overnight” | One shop per client, Slack webhook, **Changes** view |
| Merchandiser | New SKUs on a reference store | Collection URL, default **0%** price |
| Marketplace / sourcing | Back in stock on a target brand | Shop home, schedule twice a day |
| Founder doing it yourself | Watch 1–2 shops without a BI stack | Default input, first run = baseline |

**What you get by default:** a **baseline** on the first run, then **deltas**. Public price and availability — not warehouse quantity or cost.

***

### What data can it extract?

| Category | Fields |
|----------|--------|
| Change | `changeType` — `baseline`, `added`, `removed`, `price_drop`, `price_up`, `out_of_stock`, `back_in_stock`, `promo_on`, `promo_off` |
| Shop | `hostname`, `shopUrl`, `watchKind`, `watchUrl` |
| Product | `title`, `productTitle`, `sku`, `handle`, `variantId`, `productUrl` |
| Price | `previousPrice`, `currentPrice`, `priceChangePercent`, **`currency`** |
| List / sale | `previousCompareAtPrice`, `currentCompareAtPrice` (Shopify compare-at) |
| Stock signal | `previousAvailable`, `currentAvailable` (public `available` only) |
| When | `scrapedAt` |

Two events on the same variant can produce **two rows** (e.g. drop + sold out). Filter `changeType` in the dataset.

**Not extracted (and not invented):** Admin inventory, cost, drafts, password-protected shops, non-Shopify sites.

***

### How to watch Shopify competitor catalogs

**Three minutes to the first photograph:**

1. Open the Actor **Input** tab.
2. Paste a public URL, e.g. `https://enky.com/collections/workspace` (collection), `/products/…`, or the shop home. Pages, blogs and Instagram are ignored.
3. Leave **Alert on price moves from (%)** at **0** (any price move) or set **5** for real discounts only.
4. Leave **Send alerts to** = **Dataset only** for the first run.
5. Click **Start**. Read the log: *baseline saved*.
6. **Schedule** the same input daily. Next runs write change rows. Download JSON / CSV / Excel from the **Dataset** tab — **Changes** view hides the baseline.

#### Slack — Incoming Webhook only

1. Slack → **Apps** → **Incoming Webhooks** → Add to a channel (e.g. `#competitors`).
2. Copy the URL that starts with `https://hooks.slack.com/services/…`.
3. Set **Send alerts to** = **Slack**. Open the **Slack** section and paste that URL.

#### Telegram — bot token + chat ID

1. Open **@BotFather** → `/newbot` → copy the token (`123456:ABC…`).
2. Message **your** bot `/start`.
3. Get your numeric **chat ID** from **@userinfobot** (a group ID is often negative).
4. Set **Send alerts to** = **Telegram**. Open the **Telegram** section and paste token + chat ID.

The first run pings a short “watching — N products” so you know the channel works. **Quiet days do not ping.** A sale with hundreds of SKUs is summarized: counts by type, **top 12** by % move, then “+N more in the dataset (all rows billed)”.

***

### Input

See the **Input** tab for the live form. Slack and Telegram sit in **collapsed sections** — open only the channel you picked. Apify cannot hide fields dynamically.

**Dataset only (default)**

```json
{
  "startUrls": ["https://enky.com/collections/workspace"],
  "priceDropPercent": 0,
  "notifyVia": "none"
}
```

**Slack when the catalog moved**

```json
{
  "startUrls": ["https://www.jimmyfairly.com/"],
  "priceDropPercent": 5,
  "notifyVia": "slack",
  "slackWebhookUrl": "https://hooks.slack.com/services/T000/B000/XXX"
}
```

**Telegram when the catalog moved**

```json
{
  "startUrls": ["https://enky.com/collections/workspace"],
  "priceDropPercent": 0,
  "notifyVia": "telegram",
  "telegramBotToken": "123456:ABC…",
  "telegramChatId": "123456789"
}
```

| Parameter | Type | Default | Fill when |
|-----------|------|---------|-----------|
| `startUrls` | string\[] | — | **Always.** Public collection / product / home URLs |
| `priceDropPercent` | integer | `0` | Always. `0` = any price change; `5` = ±5% or more |
| `notifyVia` | string | `none` | Always. `none` · `slack` · `telegram` |
| `slackWebhookUrl` | string | — | **Slack only** (`hooks.slack.com/services/…`) |
| `telegramBotToken` | string | — | **Telegram only** (@BotFather) |
| `telegramChatId` | string | — | **Telegram only** (@userinfobot or group id) |

Choosing Slack without a webhook (or Telegram without token + chat ID) **stops the run** with a clear error.

***

### Output example

Download from the Dataset tab in JSON, CSV, Excel, or HTML. **Changes** = spreadsheet of moves. **All fields** includes baselines.

**Price drop**

```json
{
  "changeType": "price_drop",
  "hostname": "www.jimmyfairly.com",
  "shopUrl": "https://www.jimmyfairly.com",
  "watchKind": "shop",
  "title": "Aviator · Black",
  "sku": "AV-BK",
  "previousPrice": 129,
  "currentPrice": 99,
  "previousCompareAtPrice": 129,
  "currentCompareAtPrice": 129,
  "currency": "EUR",
  "priceChangePercent": -23.26,
  "previousAvailable": true,
  "currentAvailable": true,
  "productUrl": "https://www.jimmyfairly.com/products/aviator",
  "scrapedAt": "2026-08-20T12:00:00.000Z"
}
```

**First run (baseline)** — one row per watch, then schedule:

```json
{
  "changeType": "baseline",
  "hostname": "enky.com",
  "watchKind": "collection",
  "watchUrl": "https://enky.com/collections/workspace",
  "productCount": 8,
  "variantCount": 12
}
```

During the run, **Key-Value Store → RUN\_LOG** is the plain-language progress log. **OUTPUT** is the count summary.

***

### How much does it cost to watch Shopify catalogs?

**Pay-per-event** on the Apify Store: you pay for the **baseline photograph** and then for **each change row**, not for the full catalog on every schedule.

| Event | FREE tier | When |
|-------|-----------|------|
| Actor start | ~$0.00005 | Every run (× GB RAM, min 1) |
| `baseline-store` | **$0.01** | First snapshot of one shop / collection |
| `catalog-change` (primary) | **$0.004** | One change vs last snapshot |

**Quick examples (event charges, FREE, rounded):**

| Scenario | Approx. |
|----------|---------|
| First run, 1 collection | Start + **$0.01** |
| Next day, 12 drops + 3 new SKUs | 15 × **$0.004** ≈ **$0.06** |
| Quiet day, 0 changes | Start only |

A shop that is not Shopify, or that hides the public product feed, is **skipped** — not billed as `catalog-change`. Slack / Telegram pings are **not** extra PPE events.

Your Apify **plan** and **compute (CU)** may add separate platform charges — see [Apify pricing](https://apify.com/pricing) and the run usage tab. Set a **maximum cost per run** in Console if you want a hard cap.

HTTP-only, **512 MB** default. Typical one-collection run is seconds to a few minutes, not hours.

***

### Ready-made examples

Pre-configured **Actor tasks**. The Store **Examples** tab appears once the Actor is public ([publish a task](https://docs.apify.com/platform/actors/publishing/publish-task)).

| Example | Best for |
|---------|----------|
| [Baseline one competitor shop](https://apify.com/corent1robert/shopify-catalog-change-monitor/examples/baseline-one-shopify-competitor) | First photograph |
| [Daily 5% drops on one shop](https://apify.com/corent1robert/shopify-catalog-change-monitor/examples/daily-price-drops-one-shop) | Promo watch |
| [Two Shopify competitors](https://apify.com/corent1robert/shopify-catalog-change-monitor/examples/watch-two-shopify-competitors) | Small set |
| [New SKUs only](https://apify.com/corent1robert/shopify-catalog-change-monitor/examples/new-shopify-skus-only) | Assortment, less price noise |

***

### FAQ

**Is this an official Shopify app?**\
No. It reads the public storefront a shopper can open. It does not use the Admin API.

**Do I need a proxy?**\
No. Public Shopify JSON is not blocked by default. There is no proxy field in the form.

**What URLs work?**\
`/collections/{handle}`, `/products/{handle}`, `/collections/…/products/…`, or the shop home. Locale prefixes like `/en-us/` are stripped. `/pages/`, `/blogs/`, `/cart`, and social links are ignored.

**Why is the first run empty of “changes”?**\
That is the **baseline**. Schedule the next run. Resetting the photograph is API-only (`resetBaseline: true`) after a huge seasonal swap.

**Why fewer products than the website?**\
Drafts, hidden channels, or shops that disable `/products.json` (and collection JSON) cannot be listed. The run logs the skip.

**Will Slack / Telegram spam me?**\
Only on baseline confirmation and when at least one change exists. No ping on a quiet schedule. A 400-SKU sale is **one message**: type counts + top 12 + “+388 more in the dataset (all rows billed)”.

**Can I watch several shops in one run?**\
Yes. Paste several URLs. Each collection / product / home is its own snapshot.

***

### Is it legal?

Only **public storefront** data is extracted. You are responsible for the shop’s terms and your jurisdiction. This is **not** Shopify Admin data. Personal data is uncommon here (SKUs and prices); if a product title includes a person’s name, treat it under GDPR like any other public scrape. See [Apify on the legality of web scraping](https://blog.apify.com/is-web-scraping-legal/).

***

### Local development

```bash
cd shopify-catalog-change-monitor
npm ci
npm test
apify run --purge --input-file=./input.json
```

`storage/` is local-only. The named snapshot store is created next to the run storage so a second local run can diff.

***

### Support

Questions, custom watches (password shops, Admin, extra fields), or a private Slack format: <corentin@outreacher.fr>. Use the **Issues** tab on the Actor page for bugs.

Programmatic runs: **API** tab on the Actor (same JSON as Input).

**Ready?** Paste a collection URL, leave alerts on **Dataset only**, click **Start**, then schedule daily — add Slack or Telegram when the baseline looks right.

# Actor input Schema

## `startUrls` (type: `array`):

Example: `https://enky.com/collections/workspace`

## `priceDropPercent` (type: `integer`):

**0** = any new price. **5** = only moves of 5% or more (up or down).

## `notifyVia` (type: `string`):

**Dataset only** = no ping. **Slack** = open the Slack section below. **Telegram** = open the Telegram section below.

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

Slack → Apps → Incoming Webhooks → add to a channel → paste `https://hooks.slack.com/services/…`

## `telegramBotToken` (type: `string`):

@BotFather → /newbot → paste the token (`123456:ABC…`).

## `telegramChatId` (type: `string`):

Message your bot /start, then get the numeric ID from @userinfobot (groups are often negative).

## Actor input object example

```json
{
  "startUrls": [
    "https://enky.com/collections/workspace"
  ],
  "priceDropPercent": 0,
  "notifyVia": "none"
}
```

# Actor output Schema

## `changes` (type: `string`):

New SKUs, price drops, stock, removed

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

All rows including baseline

## `output` (type: `string`):

Counts per shop

## `runLog` (type: `string`):

Live progress

# 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 = {
    "startUrls": [
        "https://enky.com/collections/workspace"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("corent1robert/shopify-catalog-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 = { "startUrls": ["https://enky.com/collections/workspace"] }

# Run the Actor and wait for it to finish
run = client.actor("corent1robert/shopify-catalog-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 '{
  "startUrls": [
    "https://enky.com/collections/workspace"
  ]
}' |
apify call corent1robert/shopify-catalog-change-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,corent1robert/shopify-catalog-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/9LpQFLf87nKoH8Val/builds/c2fDrWHdMVkwectsl/openapi.json
