# Shopify Price Stock Monitor (`titan_coder/shopify-price-stock-monitor`) Actor

- **URL**: https://apify.com/titan\_coder/shopify-price-stock-monitor.md
- **Developed by:** [Radu Furtuna](https://apify.com/titan_coder) (community)
- **Categories:** E-commerce, Lead generation
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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

## Shopify Price & Stock Monitor

Every Shopify storefront exposes its full catalog at `/products.json` — no login, no API key, no
scraping in the usual sense. This Actor polls that endpoint for the stores you pick and tells you the
moment a price, sale price, or stock status changes on a product you already know about.

**The market gap this fills, measured 10.09.2026:** general Shopify data scrapers have real demand
(2000+ users at the category leader), but the specific *monitor* niche — "tell me when something
changes" rather than "give me the whole catalog again" — is wide open: about ten competing monitor
products in the Store are each stuck at 2-3 users, despite the underlying demand being large.

### How it works

1. For each store (`domain`), fetch `/products.json?limit=250&page=N` — Shopify's own documented
   pagination, up to `maxPagesPerStore` pages (250 products each).
2. The **first run for a store establishes a baseline** — no changes to report yet, nothing to compare
   against. You're billed for the check, not for a stack of "new" rows on data you never had before.
3. Every run after that **diffs the new snapshot against the last confirmed one** and returns one row
   per changed field: `price`, `compareAtPrice`, `available`, or `removed` (a variant that
   disappeared).

### Input

```json
{
  "monitorId": "my-competitors",
  "watches": [{"watchId": "acme-store", "domain": "acme.com"}],
  "maxPagesPerStore": 20
}
```

### Output row

| Field | Meaning |
|---|---|
| `variantId`, `productHandle`, `productTitle` | what changed |
| `field` | `price`, `compareAtPrice`, `available`, or `removed` |
| `oldValue`, `newValue` | as strings (Shopify itself returns price as a string; comparing strings avoids float-rounding false positives on money) |
| `checkSequence` | which check of this store this change was found on |

### Pricing

Pay per event:

- **`store-checked`** — charged once per store per run that returned parsable data. This is where the
  real cost of the run sits (an HTTP call per page), not the number of rows.
- **`product-change-detected`** — charged per changed field, after the row is written to the dataset.
  A run that finds nothing beyond establishing a baseline is charged for the check, nothing more.

**What happens if a run is interrupted.** This Actor keeps a durable four-state delivery ledger
(intent → written → charge started → billed) per monitor, and a single-flight lease so two
overlapping runs of the same monitor can't double-process it. Delivery is at-least-once (a crash at
exactly the wrong instant can duplicate a row, which costs you nothing); charging is at-most-once
with a tracked uncertain window (`chargeUncertainRows`) for the one case that can't be resolved
automatically — a crash inside the charge call itself.

### Honest limits

- **New variants are not reported.** This Actor is scoped to price/stock changes on variants it
  already has a baseline for. A brand-new product appearing is a different signal (and a different
  product) from "the price changed" — conflating them would blur both.
- **A store that hit the page cap is not treated as fully seen.** If `maxPagesPerStore` cuts off
  before the store's own pagination ends, a variant that simply wasn't re-fetched this run is *not*
  reported as removed — only variants confirmed absent from a **complete** pass are. Price/stock
  changes on variants actually seen are still reported normally either way.
- **404 and "not a Shopify store" are indistinguishable** — both return HTTP 404 from
  `/products.json`. Either way, the watch is reported as a permanent configuration error, not retried.

### Coverage record

Every run writes a `coverage` record: per-watch status and reason, pages fetched, whether pagination
was complete, rows delivered and billed. If part of the run failed, the record says which part and why.

Author: OmniCoder (https://t.me/OmniCoder)

# Actor input Schema

## `monitorId` (type: `string`):

Your own identifier for this monitor instance, lowercase letters/digits/hyphens. Keep it stable across runs - it scopes this monitor's durable state.

## `watches` (type: `array`):

List of {watchId, domain} objects. domain is the store's own domain (e.g. allbirds.com), not a \*.myshopify.com subdomain required.

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

Each page is up to 250 products (Shopify's own limit). Default 20 pages = up to 5000 products per store per run.

## `webhookUrl` (type: `string`):

Optional. Receives a JSON summary of the run. HTTPS only.

## Actor input object example

```json
{
  "monitorId": "example-monitor",
  "watches": [
    {
      "watchId": "example-store",
      "domain": "allbirds.com"
    }
  ],
  "maxPagesPerStore": 20
}
```

# Actor output Schema

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

One row per changed field: watchId, domain, product handle/title, variantId, field (price/compareAtPrice/available/removed), oldValue, newValue, checkSequence, runId, scrapedAt.

## `coverage` (type: `string`):

Per-watch status and reason, pages fetched, whether pagination was complete, rows delivered/billed. Enough to reconcile every charge against every row.

# 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 = {
    "monitorId": "example-monitor",
    "watches": [
        {
            "watchId": "example-store",
            "domain": "allbirds.com"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("titan_coder/shopify-price-stock-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 = {
    "monitorId": "example-monitor",
    "watches": [{
            "watchId": "example-store",
            "domain": "allbirds.com",
        }],
}

# Run the Actor and wait for it to finish
run = client.actor("titan_coder/shopify-price-stock-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 '{
  "monitorId": "example-monitor",
  "watches": [
    {
      "watchId": "example-store",
      "domain": "allbirds.com"
    }
  ]
}' |
apify call titan_coder/shopify-price-stock-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,titan_coder/shopify-price-stock-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/SBsg7vWrzRanx4dBO/builds/uyYPEHFF0ngitrEDA/openapi.json
