# Scraped Price & Stock Changes — Safe Snapshot Diff (`analytical_gratefulness/scraped-price-stock-changes`) Actor

Compare product scraper datasets for price drops and stock changes. Partial scrapes never imply product deletion. Select two datasets or JSON arrays; raw values omitted. $0.05/report, usage included.

- **URL**: https://apify.com/analytical\_gratefulness/scraped-price-stock-changes.md
- **Developed by:** [black cow](https://apify.com/analytical_gratefulness) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$50.00 / 1,000 completed price and stock comparisons

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

## Scraped Price & Stock Changes — Safe Snapshot Diff

Already scraping product prices? Compare two completed runs and find price drops, stock transitions and products missing from the newer snapshot. Start from the datasets you already have; no new scraper, proxy, store login or external AI service.

**$0.05 per completed comparison report, platform usage included.** Reviews, unchanged snapshots and capped-source reports are completed reports and bill once. Synthetic demo and rejected input have no custom report event. Source scraper costs and your own build/run/storage costs remain subject to your Apify plan. A new run is a new report; retrieve existing output instead of rerunning it.

### Try the result first

Run `{"mode":"demo"}`. The built-in product changes from 30 to 3 and from in stock to out of stock. Another product is absent from the partial new scrape. Expect `PRICE_DECREASE: 1`, `OUT_OF_STOCK: 1`, `NOT_OBSERVED_UNVERIFIED: 1` and `PARTIAL_SNAPSHOT: 1`. This is synthetic output, not a check of your data. Do not attach real inputs to demo mode.

### Connect existing Apify scraper results

1. Choose **Compare snapshots** and select **Previous product dataset** and **Current product dataset**. Only READ permission to these two explicitly selected datasets is requested. Sources must be from finished runs and remain unchanged during the comparison.
2. Choose **E-commerce Scraping Tool: offers object** for `url`, `offers.price`, `offers.priceCurrency` and `offers.availability`. This preset ignores the manual field settings. Choose **Price only** when stock is absent; the report explicitly marks stock as unchecked. Otherwise map the four fields manually: stable `id` or `url`, numeric price, currency, stock. Dot paths are supported; arrays, wildcard selectors and guessed mappings are not. If your scraper returns `offers[]`, transform it into one row per product/variant and select its price and currency first.
3. Leave both completeness switches off for samples, incomplete crawls or changed filters. Switch them on only when both snapshots cover the intended same product scope. Even a complete missing row is **not proof of a deletion from the website**.
4. Review **Price and stock review**, then **Changes by source row**. Rows are one-based positions in the supplied arrays or dataset order. Look up the original values in your source dataset.

The standard Apify dataset picker grants scoped READ access, not access to the rest of your account. No customer token field, new Secret, account-wide permission or outbound webhook is needed. A missing source permission rejects the run without a custom report event. Sources are never modified or deleted.

For an Actor-to-Actor integration, configure `mode=compare`, a fixed previous dataset, and the new `current_dataset_id` from the source run's `resource.defaultDatasetId`. Configure the two dataset picker fields explicitly so READ permissions can be granted. Optional integration `payload` is ignored. This version compares the two supplied snapshots; it does not advance a saved baseline automatically or send messages. A comparison itself may incur its stated fee each time it is triggered.

### JSON, n8n and AI workflows

#### E-commerce Scraping Tool price monitoring

The [official scraper's documented JSON output](https://apify.com/apify/e-commerce-scraping-tool) includes a single `offers` object and can omit stock. Use `input_layout=ecommerce_offers` and `comparison_scope=price_only` with your two completed datasets. No field-by-field remapping or new scraping run is needed. This supports that documented shape, not every site-specific output or offers array.

Free synthetic preview: `{"mode":"demo","input_layout":"ecommerce_offers","comparison_scope":"price_only"}`. It reports the price drop and partial-snapshot uncertainty, with `coverage.stock_checked=false`. A missing currency or unrecognized price still requires review. Price-only mode does not authorize price edits or treat unavailable stock as available.

For a paid comparison of inline public product rows:

```json
{
  "mode": "compare",
  "input_layout": "ecommerce_offers",
  "comparison_scope": "price_only",
  "previous_items": [{"url":"https://example.com/products/sample-a","offers":{"price":30,"priceCurrency":"USD"}}],
  "current_items": [{"url":"https://example.com/products/sample-a","offers":{"price":3,"priceCurrency":"USD"}}],
  "previous_complete": true,
  "current_complete": true
}
```

Declare complete only for the same fully collected scope. In [n8n's Apify integration](https://docs.apify.com/integrations/n8n), supply these options with your existing two snapshots, then read this run's result. Route `workflow.needs_review` to manual review before acting on `workflow.has_alerts`; retrieve the existing result instead of rerunning and paying again. There is no preinstalled n8n workflow or automatic baseline advancement.

For a workflow that already has the records, pass arrays instead of granting access to datasets:

```json
{
  "mode": "compare",
  "previous_items": [{"id":"sample-a","price":30,"currency":"USD","inStock":true}],
  "current_items": [{"id":"sample-a","price":3,"currency":"USD","inStock":false}],
  "previous_complete": true,
  "current_complete": true,
  "price_change_percent": 5,
  "delete_input_after_read": true
}
```

This request generates a paid comparison. It returns `CHANGES_FOUND`, `PRICE_DECREASE` at -90% and `OUT_OF_STOCK`. In n8n or Make, map the two dataset JSON arrays into these fields, call the Actor using your own authorized Apify connection, and read the report from the existing run. No preconfigured customer credentials or automatic notification subscriptions are included.

AI agents can find the Actor in Apify Store or connect the [specific MCP tool](https://mcp.apify.com?tools=analytical_gratefulness/scraped-price-stock-changes\&telemetry-enabled=false). Read the schema and the customer's spending limit before running. A callable tool is not evidence of a sale.

| Result | Next step |
|---|---|
| `REJECTED`, `complete=false` | No completed report. Correct the fixed input/permission error. |
| `REVIEW` | Inspect partial coverage, unavailable prices/stock, currency or ambiguous keys. Do not treat unknown values as confirmed events. |
| `CHANGES_FOUND` | Inspect identified source rows. Approve any downstream action separately. |
| `NO_ALERTS` | No threshold-level alert under the implemented checks. Smaller price changes may still be counted in `changed_matched`. |

`workflow.needs_review` and `workflow.has_alerts` are separate: a report can contain real matched price changes and incomplete overall coverage. `automatic_action_authorized` is always false. No price edits, product deletions or notification messages are performed.

### Rules and limits

- At most 5,000 rows per side and 8 MiB combined. Remote datasets request only the selected fields (three in price-only mode), in pages of at most 100 rows. Reads capped by size are rejected; a row cap or metadata change marks coverage incomplete and prevents confident missing/addition labels for that side.
- Field names use bounded dot notation. Keys must be nonblank strings or integers; string `1` and number `1` are distinct. URLs are matched exactly, never fetched or normalized. Duplicate identities are skipped rather than arbitrarily paired.
- Prices accept nonnegative plain decimals up to 18 integer and 8 fractional digits. Currency fields must have matching three-letter codes; no currency conversion or validation of a code against a current exchange table. Currency symbols, localized separators, scientific notation, negative and missing prices are not guessed.
- Stock accepts booleans and `in_stock`, `in stock`, `instock`, `out_of_stock`, `out of stock`, `outofstock`, `true`, `false` strings. Quantities, preorder states and Schema.org URLs need a prior explicit transformation.
- Up to 500 detailed findings; counts cover all inspected rows. `complete=true` means the bounded report completed, not that source coverage or website truth is complete. Check `coverage` separately.
- This product compares stable identities within one source scope. It does not match different stores' equivalent products, verify source scraper accuracy, check live websites or prove product deletion. Reordering source datasets during reads is unsupported; metadata checks reduce but cannot eliminate concurrent-write ambiguity.

### Privacy and offline use

Send public product data only. Do not submit personal, customer, payment, credential or confidential data. Recognized private-field mappings are rejected, but this is not a universal personal-data detector.

The Actor reads only selected Apify datasets or supplied arrays. It does not access your computer, private files, local environment files, browser sessions or external websites. It uses only Apify's scoped runtime authentication for selected source reads and its own output storage/charging.

Input-record deletion defaults on and happens before processing. It covers only this run's input record, not source datasets, saved task inputs, backups or other copies. Output includes counts, source row numbers, percentages and a snapshot hash; raw keys, URLs, prices, stock values and product titles are omitted. Hashes are not anonymization; protect reports and their access links.

The package also supports local processing with Python 3.12 and no third-party dependencies: `python -m scrape_change input.json`, where the file has `previous_items` and `current_items`. The local CLI makes no network requests. Hosted platform internals, backup retention and the full container OS are outside the source audit.

For **Shopify CSV edits before import**, use [CSV ChangeGuard](https://apify.com/analytical_gratefulness/shopify-catalog-changeguard). This Actor compares **scraper observations after collection**. It is independently developed and is not endorsed by Apify or Shopify.

# Actor input Schema

## `mode` (type: `string`):

Demo accepts no supplied data. Compare bills once per completed report, including reviews and unchanged snapshots.

## `input_layout` (type: `string`):

Offers layout uses url, offers.price, offers.priceCurrency and offers.availability; manual field settings are ignored. Single offers object only, not arrays or price ranges. Choose Price only when stock is absent.

## `comparison_scope` (type: `string`):

Price only skips stock reads and findings. Missing rows, ambiguous keys, unknown prices and currency mismatches still require review. Same $0.05 per completed report.

## `previous_dataset_id` (type: `string`):

Select a finished scraper dataset. Alternative to inline items on this side. Only the four mapped product fields are read. Maximum 5000 rows per side.

## `previous_items` (type: `array`):

Supply only public product data. Use either this array or the selected dataset on each side, not both. Combined snapshots at most 8 MiB.

## `current_dataset_id` (type: `string`):

Select a finished scraper dataset. Alternative to inline items on this side. Only the four mapped product fields are read. Maximum 5000 rows per side.

## `current_items` (type: `array`):

Supply only public product data. Use either this array or the selected dataset on each side, not both. Combined snapshots at most 8 MiB.

## `key_field` (type: `string`):

Exact field name or bounded dot path such as product.price. Map a stable product key, plain decimal price, matching three-letter currency and boolean/recognized stock text. No private fields.

## `price_field` (type: `string`):

Exact field name or bounded dot path such as product.price. Map a stable product key, plain decimal price, matching three-letter currency and boolean/recognized stock text. No private fields.

## `currency_field` (type: `string`):

Exact field name or bounded dot path such as product.price. Map a stable product key, plain decimal price, matching three-letter currency and boolean/recognized stock text. No private fields.

## `stock_field` (type: `string`):

Exact field name or bounded dot path such as product.price. Map a stable product key, plain decimal price, matching three-letter currency and boolean/recognized stock text. No private fields.

## `previous_complete` (type: `boolean`):

Leave false for samples, partial scrapes or changed filters. True allows added/missing-from-snapshot labels; these never verify deletion from a website. Read limits override this declaration.

## `current_complete` (type: `boolean`):

Leave false for samples, partial scrapes or changed filters. True allows added/missing-from-snapshot labels; these never verify deletion from a website. Read limits override this declaration.

## `price_change_percent` (type: `number`):

Absolute percentage change; 5% is an initial preference, not a proven optimal threshold. Currency mismatch or ambiguous prices skip numeric comparison.

## `delete_input_after_read` (type: `boolean`):

Removes only this run INPUT, not original source datasets, saved task input, platform backups or other copies. Outputs contain row references, not raw values.

## `payload` (type: `object`):

Ignored integration envelope. Explicit previous\_dataset\_id and current\_dataset\_id control which datasets are read.

## Actor input object example

```json
{
  "mode": "demo",
  "input_layout": "custom",
  "comparison_scope": "price_and_stock",
  "key_field": "id",
  "price_field": "price",
  "currency_field": "currency",
  "stock_field": "inStock",
  "previous_complete": false,
  "current_complete": false,
  "price_change_percent": 5,
  "delete_input_after_read": true
}
```

# Actor output Schema

## `reports` (type: `string`):

No description

## `report_json` (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 = {
    "mode": "demo",
    "input_layout": "custom",
    "comparison_scope": "price_and_stock",
    "key_field": "id",
    "price_field": "price",
    "currency_field": "currency",
    "stock_field": "inStock",
    "previous_complete": false,
    "current_complete": false,
    "price_change_percent": 5,
    "delete_input_after_read": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("analytical_gratefulness/scraped-price-stock-changes").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 = {
    "mode": "demo",
    "input_layout": "custom",
    "comparison_scope": "price_and_stock",
    "key_field": "id",
    "price_field": "price",
    "currency_field": "currency",
    "stock_field": "inStock",
    "previous_complete": False,
    "current_complete": False,
    "price_change_percent": 5,
    "delete_input_after_read": True,
}

# Run the Actor and wait for it to finish
run = client.actor("analytical_gratefulness/scraped-price-stock-changes").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 '{
  "mode": "demo",
  "input_layout": "custom",
  "comparison_scope": "price_and_stock",
  "key_field": "id",
  "price_field": "price",
  "currency_field": "currency",
  "stock_field": "inStock",
  "previous_complete": false,
  "current_complete": false,
  "price_change_percent": 5,
  "delete_input_after_read": true
}' |
apify call analytical_gratefulness/scraped-price-stock-changes --silent --output-dataset

```

## MCP server setup

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

```

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/u69FukyZqPojxeMEh/builds/Osf3L8nMWPboCAHIm/openapi.json
