# Compare Two Webpages for Meaningful Changes (`westerly_palisade/webpage-diff-by-dravara`) Actor

Compare two public static HTTPS pages or HTML docs for price, date, and availability changes. Read the default dataset. Set maxTotalChargeUsd above $0.04.

- **URL**: https://apify.com/westerly\_palisade/webpage-diff-by-dravara.md
- **Developed by:** [Dravara LLC](https://apify.com/westerly_palisade) (community)
- **Categories:** AI, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 88.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.04 / webpages compared

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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

## Compare Two Webpages for Meaningful Changes

**Separate meaningful page changes from static HTML noise.**

Compare two public pages, two inline HTML documents, or one of each. Fixed
rules identify changed prices, dates, availability wording, calls to action,
links, and visible values. Optional formatting-only reporting is used only when
normalized visible text is unchanged but the raw HTML differs.

This Actor is self-service. Normal runs return one structured dataset item;
handled failures return one safe failure item without requiring a Dravara
account, operator, or support fulfillment.

### Pricing and charge controls

The live Apify Store pricing section is authoritative. This **Pay per event +
usage** product can charge the customer for (1) one Dravara
`apify-actor-start` event per Actor run, (2) one Dravara product result event
only after a chargeable success, and (3) customer-paid Apify platform usage.
The underlying Apify platform usage remains separate. Dravara does not reduce
its event charge to absorb the underlying Apify platform usage.
Before running, set Apify's `maxTotalChargeUsd` option to a limit you accept.

The custom result event is charged only for a `success` result. Approved
`partial` and `failed` paths do not create that custom event. Each Actor run can
still record the separate one-time `apify-actor-start` creator event, and the
underlying Apify platform usage can still apply. The
`apify-default-dataset-item` event is configured at $0 for this release.

### Input

For each side, provide exactly one source:

- `before_url` or `before_html`
- `after_url` or `after_html`

The flat input is converted into the service's before/after contract. Mixing a
URL on one side with inline HTML on the other is supported. Supplying both
sources for one side, neither source for one side, or nested and flat source
fields together is rejected.

```json
{
  "before_url": "https://example.com/old",
  "after_url": "https://example.com/new",
  "include_formatting": false,
  "include_evidence": true,
  "idempotency_key": "550e8400-e29b-41d4-a716-446655440000"
}
```

`include_evidence` remains accepted for compatibility, but evidence can no
longer be disabled: both `true` and `false` return it.

Each URL is limited to 4,096 characters. Each inline HTML document is limited
to 1,000,000 characters.

`idempotency_key` is optional. It accepts 1-128 ASCII letters, digits, periods,
underscores, tildes, or hyphens. Dravara HMAC-scopes it to the authenticated
Apify customer and this product. While the replay record remains retained,
reusing the same key with the same canonical input can return the exact durable
result in a later Actor run; reusing it with changed input is rejected. Other
customers and products have separate
namespaces. A confirmed terminal replay does not create another Dravara result
event or result artifact, but the new Actor run can still incur its separate
Actor-start event and Apify platform usage. Dravara does not store or return the
raw key. Use a unique opaque job ID, such as a UUID—not a secret or personal
identifier.

### Use cases

- Compare pricing-page versions for displayed price, plan, or call-to-action changes.
- Check product pages for availability, date, link, or visible-value differences.
- Compare saved release-pipeline HTML while separating optional formatting-only edits.

### Output and evidence

The default dataset receives one status-bearing item. Each change contains a
category, concise summary, bounded before and after text, deterministic
materiality, and mandatory non-empty source evidence. URL comparisons include distinct
`before_retrieval` and `after_retrieval` records plus final source URLs; inline
HTML sides have no fabricated retrieval metadata. An empty `changes` array is a
valid successful no-change result.

An Apify run can complete while this item has `status: failed`. Integrations
must inspect the dataset item's `status` and `errors`, not the Actor run status
alone.

### Failures and retries

Read the dataset item's `status`, `errors`, and each error's `retryable` value.
The same idempotency key and canonical input replay the same terminal failure;
changed input with that key is rejected. Inspect the original dataset and
Charged events before starting another run. Correct nonretryable input first.
For a retryable fetch or capacity failure, wait for recovery and use a new
unique key only when billing is clear. That new run can incur its Actor-start
event, Apify platform usage, and a result event after a chargeable success.

If the Actor itself fails after a dataset item appears, do not start another
run yet. Compare that run's default dataset with its Charged events first.

Evidence excerpts are whitespace-normalized, bounded to 4,000 characters, and
include selectors and source URLs when available. They are not byte-for-byte
HTML quotes. A formatting-only change has no changed visible excerpt, so its
evidence contains the distinct before and after document SHA-256 digests
instead. The comparison reports what changed; it does not verify that either
publisher's statement is factually correct.

### Complete output example

This example compares the inline HTML prefilled in the input form. URL
comparisons additionally populate source URLs and retrieval records. Actual
identifiers and timing vary.

```json
{
  "run_id": "778a1865-478c-4734-9174-6e4af36e8fc5",
  "status": "success",
  "next_action": null,
  "warnings": [
    "This result does not include the final Apify platform-usage amount. Check this run's Usage and Charged events for final billing.",
    "Evidence excerpts normalize whitespace and are bounded to 4,000 characters; use the returned source URL for verbatim review."
  ],
  "errors": [],
  "usage": {
    "duration_ms": 9,
    "bytes_processed": 142,
    "extraction_attempts": 2
  },
  "cost": {
    "currency": "USD",
    "total_cost_usd": null,
    "billable_events": []
  },
  "before_source_url": null,
  "after_source_url": null,
  "before_retrieval": null,
  "after_retrieval": null,
  "changes": [
    {
      "change_type": "price_changed",
      "summary": "Displayed price values changed.",
      "before_text": "10 USD",
      "after_text": "12 USD",
      "materiality": 0.9,
      "evidence": [
        {"text": "Price: 10 USD", "selector": "main > p", "source_url": null},
        {"text": "Price: 12 USD", "selector": "main > p", "source_url": null}
      ]
    },
    {
      "change_type": "availability_changed",
      "summary": "Availability wording changed.",
      "before_text": "In stock",
      "after_text": "Out of stock",
      "materiality": 0.8,
      "evidence": [
        {"text": "In stock", "selector": "main > p", "source_url": null},
        {"text": "Out of stock", "selector": "main > p", "source_url": null}
      ]
    },
    {
      "change_type": "value_changed",
      "summary": "Visible page content changed.",
      "before_text": "Starter plan\nPrice: 10 USD\nIn stock\nStarter plan Price: 10 USD In stock",
      "after_text": "Starter plan\nPrice: 12 USD\nOut of stock\nStarter plan Price: 12 USD Out of stock",
      "materiality": 0.5,
      "evidence": [
        {"text": "Starter plan", "selector": "main > h1", "source_url": null},
        {"text": "Starter plan", "selector": "main > h1", "source_url": null}
      ]
    }
  ],
  "missing_fields": [],
  "materiality_score": 0.9
}
```

### Expected runtime

Inline HTML comparisons usually finish in seconds. URL comparisons depend on
two sequential remote fetches and can take longer. Each fetch uses an 8-second
connect timeout and a 12-second read timeout; the Actor has a five-minute
default run timeout.

### Durable replay and retention

Paid launch fails closed unless Dravara's durable result store is enabled. For
a paid run, the validated result JSON is stored as a compressed canonical
response envelope in a private R2 bucket and becomes eligible for deletion
after 365 days. PostgreSQL stores bounded run, settlement, artifact-reference,
quota, retention-claim, cost/revenue, and replay-identity metadata; it does not
store the full fetched pages, submitted inline HTML, or raw request body. This
persistence supports exact result replay; it is not change monitoring. When
supplied, `idempotency_key` is stored by Dravara only through the namespaced
HMAC replay identity and the request digest, not as raw text.

Apify separately retains normal run input and dataset output under the runner's
storage settings. Deleting that Apify storage does not itself delete Dravara's
durable records. After a terminal run is at least 365 days old, Dravara's
retention worker can irreversibly minimize its platform identity, replay
identity, input digest, and free-form run metadata only when its artifacts have
been deleted, reconciliation is final, and no hold or failed retention claim
remains. Run status, timestamps, result-integrity
hash, economic records, and settlement evidence remain under a separate
accounting and dispute-retention policy; not all PostgreSQL data expires after
365 days. Customer-key replay is no longer available after those replay
identities and result artifacts are minimized. See
[Dravara Privacy](https://dravarahq.com/privacy) for the complete boundary.

### Version 1 limits

- URL sources are static public HTTPS only, on port 443.
- Each fetched page is limited to 2 MB of decoded HTML and up to three
  redirects; every redirect destination is revalidated.
- Each inline HTML document is limited to 1,000,000 characters.
- There is no login support, JavaScript execution, browser rendering, proxy
  support, CAPTCHA bypass, access-control bypass, or form submission.
- Exactly two single documents are compared. There is no crawl, screenshot or
  DOM rendering, semantic model analysis, alerting, or scheduled monitoring.

Do not put passwords, session tokens, API keys, or private customer data in a
URL, inline HTML, or `idempotency_key`. URL credentials and fragments are
rejected. Content is processed by Dravara, LLC's private gateway using
deterministic comparison rules. When a URL is fetched, the destination site
receives a request from that gateway and may retain it under the site's own
logging and privacy practices.

Support: <support@dravarahq.com>

# Actor input Schema

## `before_url` (type: `string`):

Optional. Use exactly one of before\_url or before\_html. URLs must be public HTTPS on port 443.

## `before_html` (type: `string`):

Optional inline static HTML. Use exactly one of before\_url or before\_html.

## `after_url` (type: `string`):

Optional. Use exactly one of after\_url or after\_html. URLs must be public HTTPS on port 443.

## `after_html` (type: `string`):

Optional inline static HTML. Use exactly one of after\_url or after\_html.

## `include_formatting` (type: `boolean`):

When visible normalized text is unchanged, report a raw-HTML formatting change.

## `include_evidence` (type: `boolean`):

Compatibility input. Source evidence is mandatory for every reported change. Both true and false return bounded before/after evidence; formatting-only changes use document SHA-256 evidence.

## `__dravara_private_gateway_canary` (type: `string`):

Signed, time-bounded internal control for a Dravara release canary. Omitted from customer runs and contains no API key or bearer token.

## `idempotency_key` (type: `string`):

Optional 1-128 character job ID. Use ASCII letters, digits, period, underscore, tilde, or hyphen. While its replay record is retained for this Apify customer and product, the same key plus the same canonical input can replay an exact terminal result across Actor runs; changed input is rejected. A replay suppresses only the duplicate Dravara result event, not a new run's Actor-start event or Apify platform usage. Use a unique opaque ID such as a UUID, not secrets or personal data. Replay is available only while the stored result and replay identity remain. After the result becomes deletion-eligible at 365 days and the gated minimization completes, this key no longer returns the old result.

## Actor input object example

```json
{
  "before_url": "https://example.com/old",
  "before_html": "<main><h1>Starter plan</h1><p>Price: 10 USD</p><p>In stock</p></main>",
  "after_url": "https://example.com/new",
  "after_html": "<main><h1>Starter plan</h1><p>Price: 12 USD</p><p>Out of stock</p></main>",
  "include_formatting": false,
  "include_evidence": true
}
```

# Actor output Schema

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

Dataset items include categorized changes, mandatory whitespace-normalized bounded before/after evidence or document digests, provenance, materiality, usage, cost telemetry, warnings, and structured errors.

# 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 = {
    "before_html": "<main><h1>Starter plan</h1><p>Price: 10 USD</p><p>In stock</p></main>",
    "after_html": "<main><h1>Starter plan</h1><p>Price: 12 USD</p><p>Out of stock</p></main>"
};

// Run the Actor and wait for it to finish
const run = await client.actor("westerly_palisade/webpage-diff-by-dravara").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 = {
    "before_html": "<main><h1>Starter plan</h1><p>Price: 10 USD</p><p>In stock</p></main>",
    "after_html": "<main><h1>Starter plan</h1><p>Price: 12 USD</p><p>Out of stock</p></main>",
}

# Run the Actor and wait for it to finish
run = client.actor("westerly_palisade/webpage-diff-by-dravara").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 '{
  "before_html": "<main><h1>Starter plan</h1><p>Price: 10 USD</p><p>In stock</p></main>",
  "after_html": "<main><h1>Starter plan</h1><p>Price: 12 USD</p><p>Out of stock</p></main>"
}' |
apify call westerly_palisade/webpage-diff-by-dravara --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,westerly_palisade/webpage-diff-by-dravara"
        }
    }
}

```

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/qKg97YE1Ybhixj37l/builds/kusHaOkUiEJYaegi4/openapi.json
