# Shopify Price & Availability Change Monitor — CatalogDelta (`nounkim/catalog-delta`) Actor

Unofficial Shopify catalog monitor. Not affiliated with Shopify. Compare previous/current prices and availability; export CSV, JSON and HTML. No login, proxies or inventory quantity claims.

- **URL**: https://apify.com/nounkim/catalog-delta.md
- **Developed by:** [Ashim Credeor](https://apify.com/nounkim) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$20.00 / 1,000 completed public store checks

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

## CatalogDelta

### Shopify price and availability changes, ready to read

**Unofficial tool. CatalogDelta is not affiliated with, endorsed by, or sponsored by Shopify.**

Compare a public Shopify catalog with its previous complete snapshot. Get **previous and current prices**, availability changes, a spreadsheet-safe CSV, a JSON dataset and a standalone HTML report.

Designed for small store operators, catalog analysts and agencies checking a few public stores. No store password, Admin API credential, proxy or external AI subscription is required. This is a public-feed monitor, not an inventory-management system.

### Quick start on Apify

1. Run with **Synthetic demo** enabled to see three invented examples: a price change, a restock and an unavailable variant. The demo sends no store requests and charges no `store-check` event.
2. Switch demo off. Enter a canonical HTTPS store homepage, such as `https://your-store.example`. A product/collection URL will not work.
3. For comparisons across runs, set **Saved baseline store name** to `catalogdelta-my-shop`.
4. Run once to create a baseline. Run again later with the **same name** to see changes.
5. Open **Readable HTML report** in the **Output** tab. For files, open **Storage → Key-value store** and use the download action beside `CHANGES.csv`, `SUMMARY` or `SNAPSHOTS`. For JSON dataset exports, use **Storage → Dataset**. JSON/CSV are downloads, not embedded previews.

The first real run is a baseline, not a historical change report. No previous prices are invented.

### What changes are reported?

- `PRICE_CHANGED`: exact decimal feed price changed.
- `COMPARE_AT_CHANGED`: reference/compare-at price changed.
- `AVAILABLE_AGAIN` / `UNAVAILABLE`: public availability boolean changed.
- `DETAILS_CHANGED`: title, variant title, SKU or vendor changed.
- `NEW_IN_FEED`: listing/variant pair not in the previous complete feed.
- `NOT_IN_FEED`: pair absent from the current complete feed. This **does not prove deletion**.
- `BASELINE`: first observation without a saved snapshot.
- `FIRST_OBSERVED`: not in a partial imported baseline; not proof of a new product.
- `UNCHANGED`: optional unchanged rows.

Multiple changes can appear in one row, separated by `|`. Identity is **store + product ID + variant ID**. A variant shared under two product listings stays two distinct observations.

### Limits and safe interpretation

- Maximum **5 stores**, **1,000 products per store**, **10,000 product/variant pairs per store**.
- Public `/products.json` only; no password/login pages, browser rendering, CAPTCHA solving, proxy rotation or access-control bypass.
- Checks robots.txt before each feed page, observes its crawl delay (up to 10 seconds), and sends at least one second between feed requests.
- A 401/403/429, non-JSON response, malformed data, duplicate product page or unsafe destination fails the store check without retrying it.
- Cross-origin redirects are refused. Enter the final canonical homepage shown by the store.
- Reaching the product cap can make coverage **partial**, including a full page exactly at the cap. Partial results are useful only for observed rows; they never cause missing-item events and **never replace the saved baseline**. Raise the cap when possible. If the catalog is too large, use another tool.
- A complete feed means pagination reached an observed end. Shopify can change while pages are fetched, so this is not a transactionally consistent snapshot.
- Availability is **not units in stock** and is not a checkout guarantee.
- Prices are decimal strings in the feed's context. Currency, location-specific pricing, tax, shipping and checkout discounts are **not inferred**. Use a stable domain and market context.
- Empty or disabled public feeds cannot reveal unpublished/private products.
- Same-run resurrection/restart is disabled to prevent duplicate billing. Start a new run instead.

### Saved state and scheduling

Without a named state store, every run is baseline-only. With one, only a complete, delivered snapshot advances the baseline. A state-save warning means the next run may repeat changes. Read `SUMMARY`.

Use one saved task and one non-overlapping schedule for each state store. **Do not run concurrently against the same state name**; there is no distributed locking. For a different store set or experiment, choose a different name. No schedule is created automatically.

State is stored using the run's Apify storage context, not in a developer-operated database. A named store can persist until deleted; see [Apify storage retention](https://docs.apify.com/storage/key-value-store). Restrict storage sharing and delete the corresponding named store to reset comparisons. Confirm required named-storage permissions when deploying the Actor.

### Outputs

- `REPORT.html`: standalone readable comparison, no JavaScript/tracking/remote assets.
- `CHANGES.csv`: UTF-8 BOM and quoted cells; formula-looking text is prefixed with an apostrophe. Use JSON when exact raw text is required.
- Default dataset: the same rows as JSON. No-change runs can have an empty dataset; Summary still records a successful check.
- `SUMMARY`: per-store outcome, coverage, baseline status and billing confirmation.
- `SNAPSHOTS`: collected current observations.
- Per-store files with a `v1-<hash>` prefix are saved before charging, so they remain accessible if a later store fails.

Export fields include store URL, product/variant IDs, title, SKU, change type, previous/current price and compare-at price, previous/current availability, capture times, coverage and product URL.

### Pricing

Price: **$0.02 per completed store check** ($20 per 1,000), independent of exported row count, within the limits above. A baseline, a no-change check and a successful partial check each count as one store check. Failed collection and synthetic demos have no store-check event charge.

There are no separate Actor-start or dataset-item charges. Platform usage is included in the Store event price. The pricing shown in Apify Console at the time you run the Actor is authoritative. Set a maximum run cost to control spending; one store check requires a remaining event budget of at least $0.02. If the remaining budget cannot cover the next check, that store is not fetched and the run reports the budget limit.

For illustration only: five stores checked once a day for 30 days would be 150 checks, or **$3 in event charges**. This is customer cost, not an earnings forecast. No subscriptions or schedules are created automatically.

### Local use

Requires Node.js 22 or newer.

```sh
npm ci --ignore-scripts
npm test
npm run demo
node scripts/run-local.js examples/demo-input.json output/local
```

A local run prints its output folder. Each run has separate output files; named baseline stores persist in the shared storage directory. To test live collection against Shopify's public demo origin:

```sh
node scripts/run-local.js examples/live-input.json output/live-smoke
```

Run it again with the same storage directory to compare with the previous complete snapshot. No Apify account is required for local execution. Source fixtures and generated demos are explicitly synthetic.

Optional local HTML preview after `npm run demo`: `node scripts/preview.js`, then open `http://127.0.0.1:4387`. Stop with Ctrl+C.

### Data handling and appropriate use

Only public product metadata needed for comparison is retained; descriptions and images are not downloaded into outputs. No third-party analytics or external AI calls are made. Requests include the CatalogDelta user agent. Logs and storage are managed by Apify.

Use only where you have permission and where applicable site terms permit it. Robots permission alone is not legal permission. No platform affiliation, guaranteed coverage, guaranteed accuracy, trading advice or revenue guarantee is claimed.

Report problems through the Actor's Apify Issues tab. Include the store domain, run ID and error, but never a password or token.

# Actor input Schema

## `demoMode` (type: `boolean`):

Uses three invented products without any network requests or saved baseline. Turn off for live store checks.

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

1–5 canonical HTTPS homepages. No collection/product paths. Only needed when demo is off.

## `maxProducts` (type: `integer`):

1–1000. A capped feed is marked partial and does not replace the saved baseline. Each product can contain several variants; the hard limit is 10,000 listing/variant pairs.

## `stateStoreName` (type: `string`):

For recurring comparisons use the same name, e.g. catalogdelta-my-shop. Empty means baseline-only. Names must begin catalogdelta-. Do not overlap runs using the same state store.

## `includeUnchanged` (type: `boolean`):

Also export UNCHANGED rows. Does not change the per-store price.

## Actor input object example

```json
{
  "demoMode": true,
  "storeUrls": [],
  "maxProducts": 250,
  "stateStoreName": "",
  "includeUnchanged": false
}
```

# Actor output Schema

## `report` (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 = {
    "demoMode": true,
    "storeUrls": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("nounkim/catalog-delta").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 = {
    "demoMode": True,
    "storeUrls": [],
}

# Run the Actor and wait for it to finish
run = client.actor("nounkim/catalog-delta").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 '{
  "demoMode": true,
  "storeUrls": []
}' |
apify call nounkim/catalog-delta --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nounkim/catalog-delta"
        }
    }
}

```

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/5jxiFT0cny6q3nkT7/builds/7yPYvb289QQi4mbSq/openapi.json
