{
  "openapi": "3.0.1",
  "info": {
    "title": "Content Change Monitor: Website Change Detection & AI Alerts",
    "description": "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.",
    "version": "1.4",
    "x-build-id": "ZlU2gRwUDGbnP5dnT"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/pardha.s~content-change-monitor/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-pardha.s-content-change-monitor",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/acts/pardha.s~content-change-monitor/runs": {
      "post": {
        "operationId": "runs-sync-pardha.s-content-change-monitor",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor and returns information about the initiated run in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/runsResponseSchema"
                }
              }
            }
          }
        }
      }
    },
    "/acts/pardha.s~content-change-monitor/run-sync": {
      "post": {
        "operationId": "run-sync-pardha.s-content-change-monitor",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "inputSchema": {
        "type": "object",
        "properties": {
          "targets": {
            "title": "Targets to monitor",
            "type": "array",
            "description": "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.\n\nWhen 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."
          },
          "mode": {
            "title": "Default compare mode",
            "enum": [
              "text",
              "html",
              "structured",
              "price"
            ],
            "type": "string",
            "description": "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\"}.",
            "default": "text"
          },
          "minChangedChars": {
            "title": "Minimum changed characters",
            "minimum": 0,
            "maximum": 1000000,
            "type": "integer",
            "description": "A change smaller than this many characters is recorded as \"insignificant\" and never notified. Example: 20.",
            "default": 20
          },
          "minChangedPercent": {
            "title": "Minimum changed percent",
            "minimum": 0,
            "maximum": 100,
            "type": "integer",
            "description": "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.",
            "default": 1
          },
          "alwaysReportValueChanges": {
            "title": "Always report value changes",
            "type": "boolean",
            "description": "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.",
            "default": true
          },
          "alwaysReportStatusChanges": {
            "title": "Always report status changes",
            "type": "boolean",
            "description": "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.",
            "default": true
          },
          "alwaysReportPolicyChanges": {
            "title": "Always report policy and terms changes",
            "type": "boolean",
            "description": "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.",
            "default": true
          },
          "ignorePatterns": {
            "title": "Ignore patterns (regular expressions)",
            "type": "array",
            "description": "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+\"].",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "disabledRules": {
            "title": "Disabled volatility rules",
            "type": "array",
            "description": "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\"].",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "useAiAdjudication": {
            "title": "Use AI to judge changes",
            "type": "boolean",
            "description": "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.",
            "default": true
          },
          "aiInstruction": {
            "title": "What you care about, in plain English",
            "type": "string",
            "description": "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."
          },
          "llmProvider": {
            "title": "Model provider",
            "enum": [
              "managed",
              "openai",
              "anthropic",
              "google",
              "openai-compatible"
            ],
            "type": "string",
            "description": "\"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.",
            "default": "managed"
          },
          "llmApiKey": {
            "title": "Your model API key",
            "type": "string",
            "description": "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-."
          },
          "llmModel": {
            "title": "Model id",
            "type": "string",
            "description": "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."
          },
          "llmBaseUrl": {
            "title": "OpenAI-compatible base URL",
            "type": "string",
            "description": "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"
          },
          "structuredFields": {
            "title": "Structured fields to watch",
            "type": "array",
            "description": "Structured mode only. Dot paths to watch, with * wildcards. Empty means watch every extracted field. Example: [\"offers.price\", \"offers.availability\"] or [\"offers.*\"].",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "priceDirection": {
            "title": "Price alerts: which direction",
            "enum": [
              "any",
              "drop",
              "rise"
            ],
            "type": "string",
            "description": "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\".",
            "default": "any"
          },
          "minPriceChangePercent": {
            "title": "Price alerts: minimum move (%)",
            "minimum": 0,
            "maximum": 100,
            "type": "integer",
            "description": "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.",
            "default": 0
          },
          "pricePick": {
            "title": "Price alerts: which price in a list",
            "enum": [
              "first",
              "lowest",
              "highest"
            ],
            "type": "string",
            "description": "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\".",
            "default": "first"
          },
          "notifyOnFirstRun": {
            "title": "Notify on the first run",
            "type": "boolean",
            "description": "The first check of a target only records a baseline. Leave false so you are not alerted about pages you just added.",
            "default": false
          },
          "webhookUrl": {
            "title": "Webhook URL",
            "type": "string",
            "description": "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"
          },
          "webhookFormat": {
            "title": "Webhook payload format",
            "enum": [
              "json",
              "slack",
              "discord"
            ],
            "type": "string",
            "description": "\"json\" sends this Actor's own signed payload. \"slack\" and \"discord\" send that platform's incoming-webhook body so no middleware is needed.",
            "default": "json"
          },
          "webhookSecret": {
            "title": "Webhook signing secret",
            "type": "string",
            "description": "When set, every webhook carries X-Actor-Signature: sha256=<HMAC-SHA256 of the raw body>. Verify it against the exact bytes you received, before parsing. Stored encrypted and never logged. Example: a long random string such as \"whsec_8f2c1d9a4b7e6f30\"."
          },
          "emailTo": {
            "title": "Email recipients",
            "maxItems": 5,
            "type": "array",
            "description": "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\"].",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "emailMode": {
            "title": "Email mode",
            "enum": [
              "digest",
              "immediate"
            ],
            "type": "string",
            "description": "\"digest\" sends one message per run summarising every change. \"immediate\" sends one message per change, capped at 20 per run.",
            "default": "digest"
          },
          "maxEmailsPerRun": {
            "title": "Max emails per run (immediate mode)",
            "minimum": 1,
            "maximum": 20,
            "type": "integer",
            "description": "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.",
            "default": 10
          },
          "includeScreenshots": {
            "title": "Attach before/after screenshots",
            "type": "boolean",
            "description": "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.",
            "default": false
          },
          "screenshotMinorChanges": {
            "title": "Screenshots of minor changes too",
            "type": "boolean",
            "description": "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).",
            "default": false
          },
          "rulesetVersion": {
            "title": "Volatility ruleset version",
            "type": "string",
            "description": "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\".",
            "default": "latest"
          },
          "renderJs": {
            "title": "JavaScript rendering",
            "enum": [
              "never",
              "auto",
              "always"
            ],
            "type": "string",
            "description": "\"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.",
            "default": "auto"
          },
          "maxConcurrency": {
            "title": "Parallel fetches",
            "minimum": 1,
            "maximum": 50,
            "type": "integer",
            "description": "How many targets are fetched at once. Requests to the same host are always serialised and rate limited regardless of this value. Example: 10.",
            "default": 10
          },
          "requestTimeoutSecs": {
            "title": "Per-target timeout (seconds)",
            "minimum": 5,
            "maximum": 300,
            "type": "integer",
            "description": "How long a single page fetch may take before it is recorded as a timeout. Example: 45.",
            "default": 45
          },
          "resetBaseline": {
            "title": "Reset stored baselines",
            "type": "boolean",
            "description": "Discard everything this Actor remembers about these targets and start again. The run reports baseline_created for every target and no changes.",
            "default": false
          },
          "targetsDatasetId": {
            "title": "Load targets from a dataset",
            "type": "string",
            "description": "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\"."
          },
          "targetsKeyValueStoreId": {
            "title": "Load targets from a key-value store",
            "type": "string",
            "description": "Id or name of the key-value store holding the target list. Used together with the record key below. Example: \"monitoring-config\"."
          },
          "targetsRecordKey": {
            "title": "Target list record key",
            "type": "string",
            "description": "Key of the record holding a JSON array of targets. Example: \"targets\"."
          },
          "stateStoreName": {
            "title": "State store name",
            "type": "string",
            "description": "Named key-value store where baselines are remembered between runs. Change it to keep two independent monitoring setups apart. Example: \"content-change-monitor-state\".",
            "default": "content-change-monitor-state"
          },
          "proxyConfiguration": {
            "title": "Proxy",
            "type": "object",
            "description": "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.",
            "default": {
              "useApifyProxy": true
            }
          },
          "verboseLog": {
            "title": "Verbose logging",
            "type": "boolean",
            "description": "Log full URLs and every rule that fired. Off by default so URLs and page content stay out of the log.",
            "default": false
          }
        }
      },
      "runsResponseSchema": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "actId": {
                "type": "string"
              },
              "userId": {
                "type": "string"
              },
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "finishedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "status": {
                "type": "string",
                "example": "READY"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "origin": {
                    "type": "string",
                    "example": "API"
                  },
                  "userAgent": {
                    "type": "string"
                  }
                }
              },
              "stats": {
                "type": "object",
                "properties": {
                  "inputBodyLen": {
                    "type": "integer",
                    "example": 2000
                  },
                  "rebootCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "restartCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "resurrectCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "computeUnits": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "options": {
                "type": "object",
                "properties": {
                  "build": {
                    "type": "string",
                    "example": "latest"
                  },
                  "timeoutSecs": {
                    "type": "integer",
                    "example": 300
                  },
                  "memoryMbytes": {
                    "type": "integer",
                    "example": 1024
                  },
                  "diskMbytes": {
                    "type": "integer",
                    "example": 2048
                  }
                }
              },
              "buildId": {
                "type": "string"
              },
              "defaultKeyValueStoreId": {
                "type": "string"
              },
              "defaultDatasetId": {
                "type": "string"
              },
              "defaultRequestQueueId": {
                "type": "string"
              },
              "buildNumber": {
                "type": "string",
                "example": "1.0.0"
              },
              "containerUrl": {
                "type": "string"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "integer",
                    "example": 1
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "usageTotalUsd": {
                "type": "number",
                "example": 0.00005
              },
              "usageUsd": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "number",
                    "example": 0.00005
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}