# Product Price & Stock Monitor (`al_mansouri/product-price-monitor`) Actor

Watch product prices and stock on Shopify, Salla (سلة), and Zid (زد) storefronts. Get a row only when something actually changes — a price moved, a sale started or ended, an item sold out or came back.

- **URL**: https://apify.com/al\_mansouri/product-price-monitor.md
- **Developed by:** [Hussein Al-Mansori](https://apify.com/al_mansouri) (community)
- **Categories:** E-commerce, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## 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

## Product Price & Stock Monitor

**Watch product pages and get told only when something actually changes** — a price moved, a sale
started or ended, an item sold out or came back. Free.

Point it at product addresses on **Shopify**, **Salla (سلة)**, or **Zid (زد)** stores, run it on a
schedule, and it publishes a row only when there is something to act on. Nothing changed means
nothing in your dataset and nothing in your inbox.

### The number this rests on

Most monitors are judged on what they catch. The harder problem is what they *invent*: a monitor
that reports price moves that never happened stops being read, and the one alert that mattered goes
with it.

**Across 21 live products from 12 stores on all three platforms, read twice a minute apart with
nothing edited in between: 0 false alerts.** Reproduce it with the `run_false_alert_sweep.py`
script kept beside the Actor — it reads real products, waits, reads them again, and every change it
reports is by definition a false one. Ask on the issues page for the product list.

One further product could not be read at all and is counted as a failure rather than quietly
dropped: removing the awkward cases after seeing the result is how a measurement stops being
evidence.

That number is measured against the web as it looked in August 2026, and the web moves.

### Who this is for

| You are | What you get |
|---|---|
| **A reseller or dropshipper** | Told the moment a supplier's price drops or an item is back in stock |
| **A merchant** | Competitor price moves, without paying a monitoring SaaS per product |
| **A shopper waiting on stock** | One row the moment a sold-out size returns |

### What you paste in

Product page addresses — up to 500 per run. The platform is detected for you, including stores on
their own domain.

```
https://www.kyliecosmetics.com/products/kylies-everyday-look-bundle
https://elfaleh.sa/2011097-al-faleh-football-training-signals-tools
```

Run it on Apify's scheduler — hourly, daily, whatever suits — and the Actor compares each check
against what it saw last time.

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `productUrls` | list of URLs | — | The product pages to watch. Required. |
| `onlyReportChanges` | boolean | `true` | Publish a row only when something moved. Turn off to keep a full price history. |
| `stateStoreName` | string | `product-price-monitor-state` | Where the baseline lives. Change it to run two independent watch lists. |
| `respectRobots` | boolean | `true` | Skip any address the store asks crawlers not to read. Checked on every page. |
| `proxyConfiguration` | object | off | Leave off unless a store refuses the connection. |

### What you get back

```json
{
  "event": "change-detected",
  "platform": "shopify",
  "url": "https://shop.example.com/products/wool-runners",
  "nameEn": "Women's Wool Runners",
  "price": 140.0,
  "salePrice": 98.0,
  "currency": "USD",
  "availability": "in_stock",
  "changeKinds": ["price-decreased", "sale-started"],
  "previousPrice": 140.0,
  "previousSalePrice": null,
  "priceChangePercent": -30.0,
  "previouslyCheckedAt": "2026-08-25T09:00:00Z",
  "status": "verified"
}
```

Every row is self-contained: it repeats the store, the current values, and what they were before,
so one row is enough to act on.

**`changeKinds`** is what actually moved: `price-increased`, `price-decreased`, `sale-started`,
`sale-ended`, `back-in-stock`, `out-of-stock`, `variant-stock-changed`, or `baseline` for the first
sighting of a product.

### How honest a row is

| `status` | Meaning |
|---|---|
| `verified` | Read cleanly. Nothing was uncertain. |
| `warning` | Delivered, but something was uncertain — the reason is on the row. |
| `failed` | The product could not be read. No price is published, and never a stale one. |

Four rules this Actor does not bend:

- **A price that could not be read is `null`, and `null` is never a change.** "The store did not
  say" is not "the price is nothing", so a reading failure is never reported as a price move.
- **Stock only changes between two things the store actually said.** A product whose stock could
  not be read has not gone out of stock — you get `unknown`, not an alert.
- **A currency change cancels the price comparison.** Some storefronts price by where the request
  comes from; one measured product answered `290 SAR` from Saudi Arabia and `105 USD` from a US
  data centre. Comparing those two numbers would report a 64% cut no shopper ever saw.
- **A drop too large to believe is reported but not called `verified`.** It carries a warning so
  nobody reprices against it without looking.

### What each platform can tell you

Platforms publish different things, and this Actor reports only what it can actually see.

| | Price moves | Sale started / ended | Stock | Per-variant stock |
|---|---|---|---|---|
| **Shopify** | ✅ | ✅ | ✅ | ✅ |
| **Zid (زد)** | ✅ | ✅ | ✅ | ✅ |
| **Salla (سلة)** | ✅ | — | ✅ | — |

Salla's product pages publish what a shopper pays but do not separate it from the list price, so a
Salla row tells you the price moved and never claims a sale started. Inferring one would be a
defensible-looking guess, and this Actor does not make those.

### Limits

| Limit | Value |
|---|---|
| Products per run | 500 |
| Products checked at once | 4 |
| Time per product | 30 seconds |
| Time per run | 900 seconds |
| Variants tracked per product | 50 |
| Smallest price move reported | 0.005 |

### What this deliberately does not do

- No logged-in prices, carts, or checkouts. Public product pages only.
- No catalogue discovery: it watches the products you name, and does not crawl a shop for more.
- No email or SMS. Use Apify's own integrations to route a run's dataset wherever you want it.

### Pricing

**Free.** Only Apify's platform usage applies. Two events are defined — `product-checked` (every
read) and `change-detected` (a real change) — and neither is charged today.

They exist so that adding a price later would be a price change and not a redesign.

### Works well with

**Salla Store Scraper** and **Zid Store Scraper** — pull a catalogue, pick the products worth
watching, and hand them to this Actor. Field names are identical across all three, so the datasets
concatenate with no renaming.

***

### FAQ

**How does it know what changed?**
It stores a small record of each product — price, sale price, currency, stock — in a key-value
store on *your* account, and compares the next check against it. Only those fields, never the page.

**What happens on the first run?**
Every product is a `baseline` row: the starting point, not a change. You are never told a price
moved on a product the Actor has not seen before.

**Can I watch a whole store?**
Not directly — it checks the addresses you give it. Use the Salla or Zid store scraper to get the
catalogue first.

**Does it need my store login?**
No. Public product pages only.

**Why is stock `unknown` on some rows?**
Because the store did not say. Guessing `in_stock` there is exactly the alert you would act on and
regret.

**Will it tell me twice about the same change?**
It can, in one case: if the row is published but the baseline cannot be saved. That is the
deliberate trade — the alternative is losing the change silently and forever.

***

### Support

Open an issue on the Actor's Apify page. Include the run ID and the product URL.

*Not affiliated with, endorsed by, or sponsored by Shopify, Salla (سلة), or Zid (زد).*

# Actor input Schema

## `productUrls` (type: `array`):

The product pages to check, up to 500 per run. Shopify, Salla, and Zid storefronts are recognised automatically, including stores on their own domain.

## `onlyReportChanges` (type: `boolean`):

On by default, which is what a monitor is for. Turn it off to record every check, including the ones where nothing moved, and keep a full price history.

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

The named key-value store on your account that remembers what each product cost last time. Change it to run two independent watch lists side by side.

## `respectRobots` (type: `boolean`):

Skip any address the store's robots.txt asks crawlers not to read. Checked on every page read, not just the first.

## `proxyConfiguration` (type: `object`):

Proxy used for requests. Leave off unless a store refuses the connection — a data-centre proxy reaches fewer stores than no proxy at all, and a residential proxy is the escalation.

## Actor input object example

```json
{
  "productUrls": [
    "https://store.zid.sa"
  ],
  "onlyReportChanges": true,
  "stateStoreName": "product-price-monitor-state",
  "respectRobots": true
}
```

# Actor output Schema

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

Every published check, with what moved and what it was before.

## `csv` (type: `string`):

Opens in Excel with Arabic intact.

## `summary` (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 = {
    "productUrls": [
        "https://store.zid.sa"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("al_mansouri/product-price-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 = { "productUrls": ["https://store.zid.sa"] }

# Run the Actor and wait for it to finish
run = client.actor("al_mansouri/product-price-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 '{
  "productUrls": [
    "https://store.zid.sa"
  ]
}' |
apify call al_mansouri/product-price-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,al_mansouri/product-price-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/5QZ7YMBwwouz7UuJg/builds/Lbb5zD7iGL9UWitCX/openapi.json
