# Dataset Change Detector — Prices, Stock & Records (`abdulwhab95/dataset-change-detector`) Actor

Compare two JSON snapshots or Apify datasets. Export NEW, UPDATED and optional REMOVED records with field-level before/after values. Composite keys, duplicate validation, and no timestamp noise.

- **URL**: https://apify.com/abdulwhab95/dataset-change-detector.md
- **Developed by:** [ABDULWAHAB NASER RASHED ALQARAWI](https://apify.com/abdulwhab95) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 change records

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

## Dataset Change Detector — Prices, Stock & Records

Provide two snapshots or a completed WooCommerce dataset/report, and choose the fields that identify each product.
Get NEW and UPDATED records with before/after values; enable removed records only when both snapshots are complete.

### Automatic monitoring and useful alerts

Use `stateStoreName` to retain the last completely delivered snapshot. Remove the `previous` example and its dataset field. On the first run, products are NEW; an identical second snapshot produces no rows. Set `maxResults` high enough for the first snapshot, or use the seed-only option below. State is never advanced if result delivery stops at a result/spend limit or an error.

For this author's WooCommerce monitoring release, supply `currentDatasetId` and `currentReportStoreId` from the same **completed** source run, plus `keyFields: ["storeUrl", "id"]`. Remove the `current` example array. The Actor reads only those selected storages, verifies the completion report, counts and source scope, and derives its baseline time. Optional `currentRunId` checks the expected run identifier. Trigger through `ACTOR.RUN.SUCCEEDED`; the report is a completeness contract, not an independent audit of the source website.

For Salla or another manual snapshot, set `currentSnapshotComplete: true` only after checking the source report, `snapshotScope` to a stable collection identity, and `snapshotTakenAt` to its actual ISO timestamp with timezone. Use Salla keys `["store", "productId"]` and stock fields `["isOutOfStock", "isAvailable"]`. An arbitrary successful Salla/YouCan run is **not automatically assumed complete**. Capped test datasets are unsuitable for a full-catalog baseline.

Optional alert filters:

- `minPriceChangePercent: 5` suppresses price-only changes below 5% relative to the previous complete snapshot. This is a per-comparison threshold, not cumulative price drift. Zero keeps the original behavior.
- `onlyPriceOrStockChanges: true` excludes other-only UPDATED events. NEW and enabled REMOVED events still pass.
- `priceField`, `currencyField`, and `stockFields` map the source columns. Include all monitored columns in `compareFields` if you restrict it. A stock or currency change passes; unknown, invalid or zero prior prices are handled conservatively.

`RUN_REPORT` separates total, selected and filtered changes. Deliberately filtered changes still advance the baseline. State mode adds deterministic `eventId` values for downstream deduplication if a run crashes after delivering events but before committing its baseline.

Limits: 10,000 rows and 5 MB UTF-8 per snapshot, with the baseline envelope also within 5 MB. Do not edit source datasets during reads. Older snapshots, corrupt state and configuration/scope changes fail without overwriting the baseline; use a new store name for a new monitor. Empty replacements require `allowEmptySnapshot: true`. Run one monitor at a time per state store: the conflict check is not an atomic distributed lock. Storage retention follows your account. No schedule is enabled automatically.

### Safe first baseline and batches

Use `seedBaselineOnly: true` with a NEW `stateStoreName` to save the validated complete snapshot without emitting or charging result events. The normal Actor start fee still applies. This supports up to 10,000 rows / 5 MB even if `maxResults` is smaller. Set the option false on subsequent runs. An existing baseline cannot be overwritten using seed mode.

In state mode, the Actor checks the entire selected comparison against the remaining result/spending allowance before delivering anything. If it does not fit, the run fails with an actionable message and no result events. Increase the allowance or deliberately seed a new monitor. Complete snapshots are never committed after partial delivery. Results are saved in batches of up to 100 using native Apify per-event charging.

### Real output example

The comparison input is a deliberately small synthetic example; the output below was produced by the real Actor run. Captured on 2026-09-14 from run `utCLfobECOWRLFdkg` using the input below. Selected fields are shown; long values are shortened with an ellipsis. Live source data can change.

| event | key | changes |
| --- | --- | --- |
| UPDATED | {"id":"SKU-1"} | {"price":{"before":10,"after":12,"beforeMissing":false,"afterMissing":false},"inStock":{"before":true,"after":false,"bef… |
| NEW | {"id":"SKU-3"} | {} |
| REMOVED | {"id":"SKU-2"} | {} |

### Quick start

1. Leave the prefilled example in place and click **Start** for a small first run. Replace it with your own source when ready.
2. Set **Maximum delivered results** and Apify's **Maximum cost per run**.
3. Run the Actor. Export the dataset as JSON, CSV or Excel. Open **Run report** for errors, skipped items and limits.

```json
{
  "previous": [
    {
      "id": "SKU-1",
      "price": 10,
      "inStock": true
    },
    {
      "id": "SKU-2",
      "price": 5
    }
  ],
  "current": [
    {
      "id": "SKU-1",
      "price": 12,
      "inStock": false
    },
    {
      "id": "SKU-3",
      "price": 8
    }
  ],
  "keyFields": [
    "id"
  ],
  "allowRemoved": true,
  "maxResults": 3
}
```

### What it supports

Compare up to 10,000 objects per snapshot. Use previous/current arrays OR previousDatasetId/currentDatasetId accessible to your Apify account. Keys are type-sensitive: 1 and "1" are different. Missing fields and null are different. Timestamp fields observedAt, scrapedAt and fetchedAt are ignored by default. REMOVED events require allowRemoved=true: enable only when both snapshots cover the same complete population. maxResults can truncate changes; inspect RUN\_REPORT.changeCount. This Actor compares supplied snapshots; it does not fetch store catalogs on its own.

### Price

**$0.0010 per change record ($1 per 1,000)**, plus **$0.001 per run start** at the supported 512 MB–1 GB memory sizes. The custom `result` event is charged only for a delivered dataset row. There is no separate automatic dataset-row fee. A run that returns zero rows still incurs the start fee. Your account's displayed pricing and platform terms apply. Actor authors pay their own platform resource costs during development and testing.

Set the maximum cost in Apify to cap spending. maxResults also limits output. A limit may truncate a catalog or comparison; inspect RUN\_REPORT rather than assuming completeness.

### Output and reliability

Main fields: `event`, `key`, `before`, `after`, `changes`. Each row includes `observedAt`. Nested values remain JSON; choose JSON export to preserve all detail.

RUN\_REPORT contains result count, HTTP request/download totals, source errors, skipped unchanged items and final status. PARTIAL means at least one source failed while other rows were delivered. FAILED means no successful result and at least one error. Successful empty feeds or no-change comparisons can legitimately return zero rows. A 240-second processing deadline and Apify's run timeout bound execution. Downloads are limited to 40 MB per run, with smaller per-response caps; robots checks count as requests. No proxy or external AI API subscription is required.

Only public pages and documented public APIs are supported. Access restrictions are reported. The crawler does not send your Apify token to target websites or connect to private network addresses. Use sources you are entitled to access. Static HTML tools do not execute website JavaScript.

### Repeated runs

For Actors with `stateStoreName`, use a distinct name for each collection and keep the same name for later runs. Apify creates that store in your account; storage retention and access follow your account settings. Do not start overlapping runs that share a store. `onlyChanges` emits new/updated items and retains at most 10,000 recent fingerprints. It does not infer removed pages/jobs. Other Actors without a state option are stateless; Dataset Change Detector now supports the explicit baseline mode described above.

Apify tasks, schedules and integrations can reuse this input. No recurring schedule or customer notification is created automatically.

### عربي

أداة جاهزة للتشغيل من واجهة Apify أو API. أدخل الروابط أو البيانات، وحدد عدد النتائج والحد الأعلى للتكلفة. النتائج بصيغ JSON وCSV وExcel، والتفاصيل والأخطاء في RUN\_REPORT. البيانات العامة فقط؛ الحقول غير المتوفرة تبقى فارغة. راجع حدود الأداة أعلاه قبل اعتماد النتائج.

# Actor input Schema

## `previous` (type: `array`):

Array of up to 10,000 JSON objects. Alternatively use the dataset ID field.

## `previousDatasetId` (type: `string`):

Apify dataset ID accessible to your account; read access only. Do not combine with the corresponding array.

## `current` (type: `array`):

Array of up to 10,000 JSON objects. Alternatively use the dataset ID field.

## `currentDatasetId` (type: `string`):

Apify dataset ID accessible to your account; read access only. Do not combine with the corresponding array.

## `keyFields` (type: `array`):

Non-empty list of fields that uniquely identify each record.

## `compareFields` (type: `array`):

Optional explicit fields; omit to compare all fields except keys and volatile timestamps.

## `allowRemoved` (type: `boolean`):

Both snapshots are complete: emit removed records

## `currentRunId` (type: `string`):

Optional check when using a WooCommerce report; webhook can supply this ID.

## `currentReportStoreId` (type: `string`):

Select the completed source run key-value store together with currentDatasetId. Validates coverage/counts and derives scope/time. Remove both snapshot arrays; use keyFields \[storeUrl,id].

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

Optional persistent baseline. Remove previous inputs. Use a unique 3–61 character name; run this monitor sequentially, never concurrently.

## `currentSnapshotComplete` (type: `boolean`):

I verified the entire manual snapshot is complete

## `snapshotScope` (type: `string`):

Required with state and manual input: a stable store URL or collection identity. A different collection needs a new state store.

## `snapshotTakenAt` (type: `string`):

Required with state and manual input. ISO timestamp with timezone, for example 2026-09-14T08:00:00+03:00. Older snapshots are rejected.

## `allowEmptySnapshot` (type: `boolean`):

Allow empty snapshot to replace a non-empty baseline

## `seedBaselineOnly` (type: `boolean`):

Initialize a new baseline without result events

## `minPriceChangePercent` (type: `number`):

Filter smaller price-only updates relative to the previous complete snapshot. Stock, currency and other field changes still pass. Zero preserves all changes.

## `onlyPriceOrStockChanges` (type: `boolean`):

Only price, currency or stock updates

## `priceField` (type: `string`):

Numeric non-negative price or plain decimal string.

## `currencyField` (type: `string`):

Currency changes always pass the filter.

## `stockFields` (type: `array`):

Any change in these fields passes the filter. For Salla use isOutOfStock and isAvailable.

## `maxResults` (type: `integer`):

Integer from 1 to 5000.

## `maxRequests` (type: `integer`):

Integer from 1 to 300.

## Actor input object example

```json
{
  "previous": [
    {
      "id": "SKU-1",
      "price": 10,
      "inStock": true
    },
    {
      "id": "SKU-2",
      "price": 5
    }
  ],
  "current": [
    {
      "id": "SKU-1",
      "price": 12,
      "inStock": false
    },
    {
      "id": "SKU-3",
      "price": 8
    }
  ],
  "keyFields": [
    "id"
  ],
  "allowRemoved": true,
  "currentSnapshotComplete": false,
  "allowEmptySnapshot": false,
  "seedBaselineOnly": false,
  "minPriceChangePercent": 0,
  "onlyPriceOrStockChanges": false,
  "priceField": "price",
  "currencyField": "currency",
  "stockFields": [
    "inStock"
  ],
  "maxResults": 3,
  "maxRequests": 100
}
```

# Actor output Schema

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

No description

## `runReport` (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 = {
    "previous": [
        {
            "id": "SKU-1",
            "price": 10,
            "inStock": true
        },
        {
            "id": "SKU-2",
            "price": 5
        }
    ],
    "current": [
        {
            "id": "SKU-1",
            "price": 12,
            "inStock": false
        },
        {
            "id": "SKU-3",
            "price": 8
        }
    ],
    "keyFields": [
        "id"
    ],
    "allowRemoved": true,
    "maxResults": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("abdulwhab95/dataset-change-detector").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 = {
    "previous": [
        {
            "id": "SKU-1",
            "price": 10,
            "inStock": True,
        },
        {
            "id": "SKU-2",
            "price": 5,
        },
    ],
    "current": [
        {
            "id": "SKU-1",
            "price": 12,
            "inStock": False,
        },
        {
            "id": "SKU-3",
            "price": 8,
        },
    ],
    "keyFields": ["id"],
    "allowRemoved": True,
    "maxResults": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("abdulwhab95/dataset-change-detector").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 '{
  "previous": [
    {
      "id": "SKU-1",
      "price": 10,
      "inStock": true
    },
    {
      "id": "SKU-2",
      "price": 5
    }
  ],
  "current": [
    {
      "id": "SKU-1",
      "price": 12,
      "inStock": false
    },
    {
      "id": "SKU-3",
      "price": 8
    }
  ],
  "keyFields": [
    "id"
  ],
  "allowRemoved": true,
  "maxResults": 3
}' |
apify call abdulwhab95/dataset-change-detector --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abdulwhab95/dataset-change-detector"
        }
    }
}
```

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/7vg7UPuDdysVkCxuO/builds/tXRMbzM0A6kdB2l6u/openapi.json
