Dataset Diff & Alert Monitor
Pricing
from $1.00 / 1,000 row compareds
Dataset Diff & Alert Monitor
Run any Actor or read any dataset, diff it against the last run by key, and push added / changed / removed rows to a webhook, Telegram, Discord or Slack. Pay per row compared and per notification.
Pricing
from $1.00 / 1,000 row compareds
Rating
0.0
(0)
Developer
Matthew Curtis
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 hours ago
Last modified
Categories
Share
Turn any Apify Actor or dataset into a change monitor. Run it on a schedule: it runs your source Actor (or reads a dataset you already have), compares the result with the last run by a key you choose, and sends the added, changed and removed rows to a webhook, Slack, Discord or Telegram. Every change is also written to the run's dataset, so n8n, Make, Zapier or your own code can pick it up.
No scraping of its own, no browser, no proxies. You pay per row compared and per message sent; the source Actor is billed at its own price.
What it is for
- New job postings — run a LinkedIn, Indeed, Dice or Greenhouse jobs Actor daily and get only the new postings in Slack.
- Price and stock watch — run a product scraper twice a week;
watchFields: ["price", "inStock"]reports only those changes, with the previous value. - New tenders, permits, listings, grants — any registry scraper becomes an alert feed.
- Review and rating watch — new reviews on Google Maps, Trustpilot or the app stores.
- Compare two datasets once — pass
previousDatasetIdto diff any two datasets without a schedule.
How it works
- Source — set
sourceActorId(username/actor-name) plussourceInput, orsourceTaskId, or an existingdatasetId. - Key —
keyFieldsidentify an item across runs:["url"],["id"],["title","company"]. Nested paths likeproduct.skuwork. - Diff — items are hashed on their watched fields.
ignoreFieldsdrops fields that change every run (scrapedAt,rank);watchFieldsrestricts changes to the fields you care about. - Snapshot — the current state is saved in a named key-value store (
snapshotStoreName, defaultdataset-diff-monitor) undersnapshotKey. Leave the key empty and one is derived from the source and its input, so each schedule gets its own. - Notify — a digest goes to every configured channel; the generic webhook gets a JSON body with the summary and the rows.
The first run stores the baseline silently (firstRunBehavior: "snapshot") or reports every item as added ("notify").
Input example
{"sourceActorId": "curious_coder/linkedin-jobs-scraper","sourceInput": { "title": "data engineer", "location": "Chicago", "rows": 100 },"keyFields": ["id"],"ignoreFields": ["scrapedAt", "postedAt"],"notifyOn": ["added"],"labelField": "title","urlField": "link","slackWebhookUrl": "https://hooks.slack.com/services/…"}
Schedule this Actor, not the source Actor: the monitor runs the source for you.
Worked examples
Each of these pairs the monitor with one of the author's source Actors; swap in any Store Actor the same way.
| Alert you want | Source Actor | Monitor input (beyond your channel) |
|---|---|---|
| New stories for a keyword, hourly — a Google Alerts you control | google-news-scraper | "sourceInput": {"queries": ["\"Acme Corp\""], "timeRange": "1d", "maxItems": 100}, "keyFields": ["id"], "notifyOn": ["added"], "labelField": "title", "urlField": "url" |
| A competitor app ships a version, moves rating or changes price | google-play-app-scraper | "sourceInput": {"queries": ["com.duolingo"], "locale": "en-US", "maxItems": 1}, "keyFields": ["id"], "watchFields": ["version", "score", "price", "updatedAt"], "notifyOn": ["changed"], "labelField": "title", "urlField": "url" |
| New or changed Washington DNR timber sales before the auction | wa-dnr-timber-sales | "sourceInput": {"listings": ["upcoming", "unsold"]}, "keyFields": ["id"], "notifyOn": ["added", "changed"], "labelField": "saleName", "urlField": "packetUrl" |
| New Minnesota DNR auctions and posted results | mn-dnr-timber-sales | "sourceInput": {"includeCalendar": true}, "keyFields": ["id"], "notifyOn": ["added", "changed"], "labelField": "saleName", "urlField": "detailUrl" |
| New or repriced kleinanzeigen.de ads for a search | kleinanzeigen-scraper | "sourceInput": {"queries": ["rennrad"], "location": "Köln", "fetchDetails": false, "maxItems": 100}, "keyFields": ["id"], "watchFields": ["price"], "notifyOn": ["added", "changed"], "labelField": "title", "urlField": "url" |
Output
Dataset — one row per change the run reports:
{"changeType": "changed","key": "https://example.com/item/42","label": "Item 42","url": "https://example.com/item/42","changedFields": ["price"],"item": { "url": "https://example.com/item/42", "price": 19.99 },"previous": { "url": "https://example.com/item/42", "price": 24.99 },"detectedAt": "2026-10-24T19:47:51.899Z","snapshotKey": "snap-3f1c9a2b7e4d1c0a","sourceDatasetId": "…","sourceRunId": "…"}
Key-value store OUTPUT — the run summary: counts, what it was compared against, which channels were notified, and the snapshot key.
Webhook body — { title, detectedAt, summary: {added, changed, removed, unchanged, compared}, added: [...], changed: [...], removed: [...], truncated }. Rows per type are capped at maxItemsPerMessage (default 20); the dataset always holds all of them.
Chat digest (Slack, Discord, Telegram):
Diff monitor: curious_coder/linkedin-jobs-scraper: 4 added, 2 changed, 3 removed (44 items compared)ADDED (4)• Senior Data Engineer https://……CHANGED (2)• Data Engineer II — salary https://…
Discord messages are split at 2,000 characters, Telegram at 4,000.
Pricing
Pay per event, no platform usage charges on top:
| Event | Price |
|---|---|
| Actor start | $0.02 |
| Row compared (current items + removed items) | $0.001 |
| Notification message sent | $0.01 |
A daily monitor over 500 items with one Slack digest costs about $0.53 per run plus the source Actor's own price. Reading an existing dataset instead of running a source Actor costs only the comparison.
Use from n8n, Make, MCP or code
- n8n / Make — add the Apify node, pick this Actor, paste the input JSON. Or let the Actor call your workflow's webhook URL directly through
webhookUrl. - MCP —
https://mcp.apify.com/?tools=mdcurt/dataset-diff-monitor - API —
POST https://api.apify.com/v2/acts/mdcurt~dataset-diff-monitor/runs?token=…with the input as the body.
Limits and notes
- Items with no value in any key field are skipped and counted in
OUTPUT.counts.skippedNoKey. - Duplicate keys keep the last item; the count is reported.
- Snapshots above ~8 MB keep hashes only, so
previousis empty in changed rows for very large datasets. UsewatchFieldsto shrink what is compared. - A failing channel does not block the others; failures are listed in
OUTPUT.notifications.failed. dryRun: truecomputes and writes the diff but sends nothing and leaves the snapshot untouched.
Why this exists
Freelance boards in October 2026 kept asking for the same thing with different sources: a multi-site stock alert bot, a daily digest of CIO job postings across four boards, a procurement watcher that alerts on new solicitations. Each of those is a source Actor plus this monitor.