Website change monitor with AI: get alerts only when a page change matters, like prices, outages, releases and policy updates. Email, Slack, Discord or webhook, with before/after screenshots. Pay per check, built for AI agents.
The pages to check. Each entry is either a URL string, or an object: {"url": "https://example.com/pricing", "label": "Competitor pricing", "selector": "main .pricing-table", "ignoreSelectors": [".ad"], "mode": "text"}. For a product's price only, use "mode": "price", optionally with "pricePick" ("first" | "lowest" | "highest", for a selector matching a list of prices), "priceDirection" ("any" | "drop" | "rise") and "minPriceChangePercent". Only "url" is required and it must be an absolute http(s) URL. Maximum 1000 targets per run. Shorthands: a top-level {"url": "https://example.com"} or {"urls": ["...", "..."]} are accepted too, so a caller can pass one page with no structure at all.
When the thing you want to watch only appears after a click — a seat map, a size picker, a fare panel that opens over a listing without changing the URL — add "actions": a short list of steps run in a real browser before anything is compared. Steps are {"action":"waitFor"|"click"|"wait"|"fill"|"scrollTo", "selector":"...", "ms":1500, "text":"...", "optional":false}, at most 12 of them. Selectors may use Playwright syntax, so you can pick a row by what it says: ".bus:has-text("VRL Travels")". Any target with actions is always rendered in a browser. A required step that finds nothing fails the check with "action_failed" rather than quietly comparing the page you did not reach — mark a step "optional": true (a cookie banner, say) when it may legitimately be absent. Any target may also set its own "renderJs" ("never" | "auto" | "always") to override the run setting, e.g. "always" for a site that draws its interface with JavaScript so its screenshots match what visitors see.
Type:array
Example:
[
{
"url":"https://example.com/pricing",
"label":"Competitor pricing",
"mode":"structured"
}
]
Default compare mode
mode
Optional
How pages are compared unless a target overrides it. "text" compares readable prose and is the right default. "html" compares markup and catches attribute-level edits. "structured" compares JSON-LD, microdata, RDFa and OpenGraph fields such as offers.price — exact, cheapest, and never calls a model. "price" tracks only a product page's own price, currency and availability — recommendations, sponsored rows and "was" prices are ignored, and no model is called. Example target: {"url": "https://www.amazon.in/dp/…", "mode": "price"}.
Type:string
Default:text
Options:
texthtmlstructuredprice
Minimum changed characters
minChangedChars
Optional
A change smaller than this many characters is recorded as "insignificant" and never notified. Example: 20.
Type:integer
Minimum:0
Maximum:1000000
Default:20
Minimum changed percent
minChangedPercent
Optional
A change smaller than this percentage of the page is recorded as "insignificant" and never notified. Both this and the character threshold must be exceeded. Example: 1 means 1% of the page.
Type:integer
Minimum:0
Maximum:100
Default:1
Always report value changes
alwaysReportValueChanges
Optional
Keep this on so a small change to a price, stock status, version number or deadline is reported even when it is below the size thresholds above. A price moving from $49 to $59 is six characters, and it is usually the whole reason you are monitoring the page. Turn it off to apply the size thresholds strictly.
Type:boolean
Default:true
Always report status changes
alwaysReportStatusChanges
Optional
Keep this on so a status page changing service state — Operational to Degraded Performance, a new incident, an outage marked resolved — is reported even when it is below the size thresholds above. On a page listing a hundred services, one of them going down is a fraction of a percent of the text. Only the changed wording is inspected, so a status page's own "last updated" timestamp ticking over does not trigger it. Turn it off to apply the size thresholds strictly.
Type:boolean
Default:true
Always report policy and terms changes
alwaysReportPolicyChanges
Optional
Keep this on so an edit to policy or contract language — a retention period, an added carve-out, a changed effective date, a new arbitration clause — is reported even when it is below the size thresholds above. A one-clause edit is a handful of characters in a document of tens of thousands. Navigation and footer links named "Privacy Policy" or "Terms of Service" are deliberately not treated as policy language, so menu churn on a legal page stays suppressed. Turn it off to apply the size thresholds strictly.
Type:boolean
Default:true
Ignore patterns (regular expressions)
ignorePatterns
Optional
JavaScript regular expressions applied to the page text before comparison; every match is neutralised. Use this for site-specific volatile content the built-in ruleset does not know about. Example: ["Order #\d+", "Queue position: \d+"].
Type:array
Default:
[]
Example:
[
"Order #\\d+"
]
Disabled volatility rules
disabledRules
Optional
Turn off individual built-in filters when they suppress something you care about. Use a rule id such as "counter.views", or a whole class such as "timestamp". Rule ids appear in each result's appliedRules field. Example: ["timestamp.copyright_year"].
Type:array
Default:
[]
Example:
[
"timestamp.copyright_year"
]
Use AI to judge changes
useAiAdjudication
Optional
When true, changes that pass the size thresholds get a second-pass judgement that filters out cosmetic edits. When false, every change above the thresholds is reported. Structured mode never calls a model either way.
Type:boolean
Default:true
What you care about, in plain English
aiInstruction
Optional
Tell the adjudicator what counts as important for this page, and everything else is filtered out. Example: "only notify if a price changes or a plan is removed". Leave empty to use the general definition of a significant change.
Type:string
Example:
only notify if a price changes
Model provider
llmProvider
Optional
"managed" uses the built-in model and is billed per adjudication. The other options use your own API key, are not billed by this Actor, and you pay your provider directly.
Type:string
Default:managed
Options:
managedopenaianthropicgoogleopenai-compatible
Your model API key
llmApiKey
Optional
Required unless the provider is "managed". Stored encrypted, held in memory only, never logged and never written to any output. Example: an OpenAI key starting sk-.
Type:string
Example:
sk-proj-your-key-here
Model id
llmModel
Optional
Override the default model. Defaults are pinned per provider, so an upstream model swap cannot silently change your verdicts. Required for an OpenAI-compatible endpoint, because the endpoint decides what it serves. Example: "gpt-4.1-mini" for OpenAI, "claude-haiku-4-5-20251001" for Anthropic, "gemini-3.1-flash-lite" for Google.
Type:string
Example:
gpt-4.1-mini
OpenAI-compatible base URL
llmBaseUrl
Optional
Only for provider "openai-compatible". Must be https and a public address — vLLM, Ollama behind a tunnel, OpenRouter, Groq and similar. Example: https://openrouter.ai/api/v1
Type:string
Example:
https://openrouter.ai/api/v1
Structured fields to watch
structuredFields
Optional
Structured mode only. Dot paths to watch, with * wildcards. Empty means watch every extracted field. Example: ["offers.price", "offers.availability"] or ["offers.*"].
Type:array
Default:
[]
Example:
[
"offers.price",
"offers.availability"
]
Price alerts: which direction
priceDirection
Optional
Price mode only. "any" reports every price move, "drop" only decreases, "rise" only increases. Stock changes (e.g. out of stock to in stock) are always reported. Can be set per target too. Example: "drop".
Type:string
Default:any
Options:
anydroprise
Price alerts: minimum move (%)
minPriceChangePercent
Optional
Price mode only. Ignore price moves smaller than this percentage of the old price. 0 reports every move. Can be set per target too. Example: 5 ignores a ₹1,299 to ₹1,280 move.
Type:integer
Minimum:0
Maximum:100
Default:0
Price alerts: which price in a list
pricePick
Optional
Price mode only, when the selector matches several prices — a fare per bus on a route, a price per seller. "lowest" tracks the cheapest one (the route's lowest fare), "highest" the most expensive, "first" the first match. Can be set per target too. Example: "lowest".
Type:string
Default:first
Options:
firstlowesthighest
Notify on the first run
notifyOnFirstRun
Optional
The first check of a target only records a baseline. Leave false so you are not alerted about pages you just added.
Type:boolean
Default:false
Webhook URL
webhookUrl
Optional
POST target for significant changes, sent once per run. A Slack or Discord incoming-webhook URL works directly if you set the matching format below. Example: https://hooks.slack.com/services/T000/B000/XXXX
Type:string
Example:
https://hooks.slack.com/services/T000/B000/XXXX
Webhook payload format
webhookFormat
Optional
"json" sends this Actor's own signed payload. "slack" and "discord" send that platform's incoming-webhook body so no middleware is needed.
Type:string
Default:json
Options:
jsonslackdiscord
Webhook signing secret
webhookSecret
Optional
When set, every webhook carries X-Actor-Signature: sha256=. Verify it against the exact bytes you received, before parsing. Stored encrypted and never logged. Example: a long random string such as "whsec_8f2c1d9a4b7e6f30".
Up to 5 addresses to alert when a page changes. You choose recipients; the message content is fixed and cannot be customised. Example: ["alerts@yourcompany.com"].
Type:array
Max. items:5
Default:
[]
Example:
[
"alerts@yourcompany.com"
]
Email mode
emailMode
Optional
"digest" sends one message per run summarising every change. "immediate" sends one message per change, capped at 20 per run.
Type:string
Default:digest
Options:
digestimmediate
Max emails per run (immediate mode)
maxEmailsPerRun
Optional
Immediate mode only. Each recipient gets at most this many emails from one run. When more pages change, the last email summarises all the remaining changes, so none are left out. Between 1 and 20. Example: 5.
Type:integer
Minimum:1
Maximum:20
Default:10
Attach before/after screenshots
includeScreenshots
Optional
Take a picture of the page before and after each change that raises an alert, with the changed words highlighted. Shown in the Console's Changes table and attached to email alerts; email recipients are not required. Pages drawn by JavaScript are pictured in a real browser so the picture matches the live site. Charged as one screenshots-captured event ($0.002) per pictured change.
Type:boolean
Default:false
Screenshots of minor changes too
screenshotMinorChanges
Optional
Also picture changes below your alert thresholds (status "insignificant"). Off by default: minor changes are not alerts, and on a busy page this pictures almost every check. Needs includeScreenshots. Each pictured change is one screenshots-captured event ($0.002).
Type:boolean
Default:false
Volatility ruleset version
rulesetVersion
Optional
Pin the built-in filter set so verdicts stay reproducible, or leave "latest". When the ruleset changes, stored baselines are migrated offline first, so an upgrade never produces a false alert. Example: "1.0.0".
Type:string
Default:latest
JavaScript rendering
renderJs
Optional
"auto" uses a plain HTTP fetch and only starts a browser when the page turns out to be an empty JavaScript shell. "never" is cheapest. "always" is for pages you already know need a browser.
Type:string
Default:auto
Options:
neverautoalways
Parallel fetches
maxConcurrency
Optional
How many targets are fetched at once. Requests to the same host are always serialised and rate limited regardless of this value. Example: 10.
Type:integer
Minimum:1
Maximum:50
Default:10
Per-target timeout (seconds)
requestTimeoutSecs
Optional
How long a single page fetch may take before it is recorded as a timeout. Example: 45.
Type:integer
Minimum:5
Maximum:300
Default:45
Reset stored baselines
resetBaseline
Optional
Discard everything this Actor remembers about these targets and start again. The run reports baseline_created for every target and no changes.
Type:boolean
Default:false
Load targets from a dataset
targetsDatasetId
Optional
Id or name of an Apify dataset whose items are targets, so a large list does not have to be pasted into this form. Each item needs at least a "url" field. Example: "my-monitored-urls".
Type:string
Example:
my-target-list
Load targets from a key-value store
targetsKeyValueStoreId
Optional
Id or name of the key-value store holding the target list. Used together with the record key below. Example: "monitoring-config".
Type:string
Example:
my-store
Target list record key
targetsRecordKey
Optional
Key of the record holding a JSON array of targets. Example: "targets".
Type:string
Example:
targets
State store name
stateStoreName
Optional
Named key-value store where baselines are remembered between runs. Change it to keep two independent monitoring setups apart. Example: "content-change-monitor-state".
Type:string
Default:content-change-monitor-state
Example:
content-change-monitor-state
Proxy
proxyConfiguration
Optional
Apify proxy settings. Datacenter is the default and is right for almost every page. Residential costs materially more per request, so turn it on only for sites that block datacenter IPs.
Type:object
Default:
{
"useApifyProxy":true
}
Verbose logging
verboseLog
Optional
Log full URLs and every rule that fired. Off by default so URLs and page content stay out of the log.