# Global Price & Stock Intelligence (`privatex/global-price-stock-intelligence`) Actor

Track price and stock of product pages you are allowed to check. Reads schema.org/JSON-LD first, reports PRICE_UP, PRICE_DOWN, BACK_IN_STOCK, OUT_OF_STOCK with evidence, keeps price history and matches the same product across shops.

- **URL**: https://apify.com/privatex/global-price-stock-intelligence.md
- **Developed by:** [PRIVATEX](https://apify.com/privatex) (community)
- **Categories:** E-commerce, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 product checkeds

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

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

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Global Price & Stock Intelligence

Monitor the price and stock of product pages on any shop you are allowed to check. Each run tells you **what changed since your previous run**: `PRICE_UP`, `PRICE_DOWN`, `BACK_IN_STOCK`, `OUT_OF_STOCK`, `MIXED`, `UNCHANGED`. Every value shows where it came from.

### Who needs it

- **E-commerce teams** watching competitor prices and stock on a schedule.
- **Agencies** running price checks for several clients, one monitor per client.
- **Procurement analysts** tracking supplier list prices and availability over time.

### What it does

- **Reads structured data first.** It reads schema.org JSON-LD first, including `@graph`, `ProductGroup` variants, WooCommerce `priceSpecification` and `AggregateOffer`. Then it tries microdata, then meta tags. Only when none of these exist does it read the page HTML, at lower confidence.
- **Shows its evidence.** Every row has an `evidence` object per field: the source (`json-ld`, `microdata`, `meta` or `html`), the path, the raw value and a confidence.
- **Compares with your previous run** of the same `monitorName` and keeps a **price history** (default: 30 observations), with the lowest and highest price seen.
- **Matches the same product across shops** by a valid GTIN, by brand + SKU, or by a `productKey` you set. Prices are compared **per currency only**.
- **Parses prices in many formats:** `1.234,56 €`, `$1,234.56`, `3.499 TL`, `1 299,00 zł`, `CHF 1'299.50`. Currencies are reported as ISO codes and are **never converted**.

### What it does NOT do

- **It never treats a failed check as out of stock.** A timeout, block, 404 or missing data gives `UNAVAILABLE`, and your last good price and stock are kept.
- **It never bypasses blocking.** It does not get around anti-bot challenges, CAPTCHAs or logins, and does not use proxies to evade blocks. A site that shows a challenge is skipped for the rest of the run, and the row says so.
- **It does not guess.** It never guesses stock counts from an "in stock" badge, invents a currency, or converts between currencies.
- **It only reads pages.** No checkout, no cart, no account pages, no POST requests.

### Start in 60 seconds

1. Press **Start** with the prefilled input. It checks three products on public scraping-practice sites (books.toscrape.com, scrapeme.live, webscraper.io).
2. Run it again later with the same **monitor name**. The second run shows `UNCHANGED` or the changes.
3. Replace the URLs with product pages you are allowed to monitor.

To see every status without fetching anything, empty the URL list (Demo mode is ignored whenever product URLs are given). The demo uses fictional shops, is labelled `SYNTHETIC_DEMO` and charges no product events (only Apify's automatic $0.00005 start event applies to every run).

#### Example input

```json
{
    "products": [
        { "url": "https://scrapeme.live/shop/Bulbasaur/" },
        { "url": "https://shop.example.com/p/kettle", "productKey": "KETTLE-17" }
    ],
    "monitorName": "competitor-kettles",
    "perHostDelaySecs": 2
}
```

#### Example output row

This row is real: cloud run `l7Xm4faI9YaIDvxXP`, 2026-10-30, a public scraping-practice site.

```json
{
    "status": "FIRST_SEEN",
    "url": "https://scrapeme.live/shop/Bulbasaur/",
    "title": "Bulbasaur",
    "price": 63,
    "currency": "GBP",
    "availability": "IN_STOCK",
    "sku": "4391",
    "matchBasis": "URL_ONLY",
    "evidence": {
        "price": { "source": "json-ld", "path": "offers.price", "raw": "63.00", "confidence": 0.95 },
        "currency": { "source": "json-ld", "path": "offers.priceCurrency", "raw": "GBP", "confidence": 0.95 },
        "availability": { "source": "json-ld", "path": "offers.availability", "raw": "https://schema.org/InStock", "confidence": 0.95 }
    },
    "errorCode": null,
    "dataOrigin": "LIVE_FETCH"
}
```

On the second run the same row had `"status": "UNCHANGED"`, `"previousPrice": 63` and a price history of 2 observations.

### Statuses

| Status | Meaning |
|---|---|
| `FIRST_SEEN` | First successful check in this monitor; it is stored as the baseline. |
| `UNCHANGED` | Same price, currency and stock as the last successful check. |
| `PRICE_UP` / `PRICE_DOWN` | The price changed. See `previousPrice`, `priceChange` and `priceChangePct`. |
| `BACK_IN_STOCK` / `OUT_OF_STOCK` | Stock changed between in-stock and out-of-stock or discontinued. |
| `CURRENCY_CHANGED` | The currency differs; prices are not compared. |
| `PRICE_MISSING` | The page no longer shows a price. The old price stays in the history. |
| `MIXED` | More than one change at once. `changes` lists them. |
| `UNAVAILABLE` | This check failed (`errorCode` says why). The previous good data is kept. |

**Error codes:**

| Code | Meaning |
|---|---|
| `NOT_FOUND` | The page doesn't exist (404). |
| `ACCESS_DENIED` | The site refused access. |
| `ANTI_BOT_CHALLENGE` | The site showed an anti-bot challenge; it is not bypassed. |
| `RATE_LIMITED` | The site asked us to slow down. |
| `HOST_SKIPPED_AFTER_BLOCK` / `HOST_SKIPPED_AFTER_RATE_LIMIT` | Skipped because the same site blocked or rate-limited an earlier request in this run. |
| `ROBOTS_DISALLOWED` | The site's robots.txt disallows this URL. |
| `ROBOTS_UNREACHABLE` | robots.txt couldn't be read, so the site is treated as disallowed. |
| `TIMEOUT` | No answer in time. |
| `UNSUPPORTED_FORMAT` | Not an HTML page. |
| `PAGE_TOO_LARGE` | The page exceeds the size limit. |
| `EXTRACTION_UNCERTAIN` | The page loaded but no price or availability was found. |
| `INPUT_INVALID` | Not a valid public http(s) URL. |
| `RUN_LIMIT_REACHED` | The run's time or charge limit was reached before this URL. |

### Output

| Where | What |
|---|---|
| Dataset | One row per product URL, with every field above plus `priceHistory`, `priceHistoryStats`, `matchKey`, `confidence`, `evidence` and `diagnostics`. |
| `RESULTS_CSV` | The main columns as CSV, with a UTF-8 BOM and formula-injection protection. |
| `MATCH_GROUPS` | Products found on several shops, with the lowest price per currency. |
| `OUTPUT` | Run summary: counts by status and error code, duplicates, invalid inputs and billing. |

**Run statuses** (`OUTPUT.status`):

- `SUCCEEDED`: every product was checked.
- `PARTIAL_RESULT`: some failed or were skipped.
- `FAILED`: nothing could be checked. The run is marked failed and your monitor history is untouched.

### Pricing

Pay per event:

| Event | Price | When it is charged |
|---|---|---|
| `product-checked` | **$0.002** | Once per product page that loaded and yielded a price or availability. |
| `apify-actor-start` | $0.00005 | Once per run, by Apify. |

Examples: 100 products cost $0.20 per run; 1,000 products cost $2.00 per run. Platform usage is included.

These are **not charged**:

- failed, blocked, robots-disallowed and rate-limited checks;
- duplicate URLs;
- invalid inputs;
- demo runs.

The Actor respects your **maximum cost per run**. It only fetches as many products as the limit allows; the rest are listed in `OUTPUT.counts.skippedForChargeLimit`.

### Politeness and limits

| Behaviour | Detail |
|---|---|
| robots.txt | Always respected (RFC 9309). This cannot be turned off. |
| Requests to one site | One at a time, with a delay (default 2 s). The site's `Crawl-delay` wins if it is longer. |
| Sites in parallel | Up to 3 by default. |
| 429 responses | A `Retry-After` of 30 s or less is honoured once. Otherwise the site is skipped for the rest of the run. |
| Page timeout | 30 s by default. |
| Page size | 8 MB maximum. |
| Products per run | 1,000 maximum. |

### Privacy and security

- Only the public product pages you list are fetched.
- Private, internal, loopback and cloud-metadata addresses are refused, on the first request and after every redirect and DNS resolution. Oversized and compressed-bomb responses are cut off.
- Monitor history (prices, stock, timestamps; no page content) is stored in a key-value store named `price-stock-intelligence-state` in **your** Apify account.
- The Actor runs with **limited permissions**.

### Scheduling and API

Create a task with your URLs and a fixed `monitorName`, then schedule it, for example daily. Use `onlyChanges: true` to get only changed products. Connect the dataset or `OUTPUT` to Slack, Sheets, Make, Zapier or your API client, and alert on `PRICE_DOWN` or `BACK_IN_STOCK`.

### Known limitations

- Prices that appear only after JavaScript runs are not seen; no browser is used. These rows show `EXTRACTION_UNCERTAIN`.
- With several variants on one page, the **lowest** offer price is reported and `priceRange` is filled.
- The HTML fallback takes the first price-styled element after the page title. On pages without structured data this can be wrong; such values have confidence 0.6 or lower and a `HTML_FALLBACK_PRICE` diagnostic.
- `$`, `¥` and `kr` are ambiguous. They are mapped to USD, JPY or SEK only when the page states no currency, and are flagged `CURRENCY_AMBIGUOUS`.

### Support

Use the **Issues** tab on this Actor's page.

# Actor input Schema

## `products` (type: `array`):

One product page per line. Leave empty for a synthetic demo (no product charges). Optional per-row JSON: {"url": "...", "productKey": "YOUR-SKU"} to group the same product across shops.

## `monitorName` (type: `string`):

Runs with the same monitor name are compared with each other. Use different names for different watch lists. Stored in a key-value store in your own account.

## `onlyChanges` (type: `boolean`):

Skip UNCHANGED rows in the dataset (they are still checked, billed and kept in history).

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

Run on fictional shops to see every status without fetching anything. Never charged; never touches your saved monitors. Ignored when product URLs are given.

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

Extra URLs are reported and skipped.

## `perHostDelaySecs` (type: `integer`):

Requests to one site are sent one at a time with this pause. A longer Crawl-delay in the site's robots.txt always wins.

## `maxConcurrency` (type: `integer`):

Different sites are checked in parallel; one site never gets parallel requests.

## `timeoutSeconds` (type: `integer`):

A page that does not answer in time is reported as TIMEOUT (UNAVAILABLE), never as out of stock.

## `historyLength` (type: `integer`):

Observations kept per product in your monitor.

## Actor input object example

```json
{
  "products": [
    {
      "url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html"
    },
    {
      "url": "https://scrapeme.live/shop/Bulbasaur/"
    },
    {
      "url": "https://webscraper.io/test-sites/e-commerce/static/product/31"
    }
  ],
  "monitorName": "default",
  "onlyChanges": false,
  "demoMode": false,
  "maxProducts": 100,
  "perHostDelaySecs": 2,
  "maxConcurrency": 3,
  "timeoutSeconds": 30,
  "historyLength": 30
}
```

# Actor output Schema

## `results` (type: `string`):

No description

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

No description

## `matchGroups` (type: `string`):

No description

## `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 = {
    "products": [
        {
            "url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html"
        },
        {
            "url": "https://scrapeme.live/shop/Bulbasaur/"
        },
        {
            "url": "https://webscraper.io/test-sites/e-commerce/static/product/31"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("privatex/global-price-stock-intelligence").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 = { "products": [
        { "url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html" },
        { "url": "https://scrapeme.live/shop/Bulbasaur/" },
        { "url": "https://webscraper.io/test-sites/e-commerce/static/product/31" },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("privatex/global-price-stock-intelligence").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 '{
  "products": [
    {
      "url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html"
    },
    {
      "url": "https://scrapeme.live/shop/Bulbasaur/"
    },
    {
      "url": "https://webscraper.io/test-sites/e-commerce/static/product/31"
    }
  ]
}' |
apify call privatex/global-price-stock-intelligence --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,privatex/global-price-stock-intelligence"
        }
    }
}
```

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/iQmbc4G9lsQYHxhEw/builds/JOK5kwrhcxYfl6SES/openapi.json
