# Target Site Terms Automation Clause Change Watcher (`kingii98/target-site-terms-automation-clause-change-watcher`) Actor

Watches the legal documents of the sites that you scrape and reports every change to the clauses that govern automated access, with the date on which the new clause binds. One record for each clause change, one for each document and one summary. HTTP only

- **URL**: https://apify.com/kingii98/target-site-terms-automation-clause-change-watcher.md
- **Developed by:** [kingii98](https://apify.com/kingii98) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 run starteds

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

## Target Site Terms Automation Clause Change Watcher

Watches the legal documents of the sites that your scrapers and your agents call, and reports
every change to the clause that governs automated access, together with the date on which the
new clause binds.

A general page change monitor gives you a raw page diff. This Actor gives you the clause. It
keeps a text window around each term of an automation taxonomy, it compares only those windows
against the stored baseline, and it reads the effective date or the binding date out of the
changed text.

**This Actor reports text changes. It gives no legal advice.**

### What the buyer gets

- A weekly answer to one question: did a target site change the rule that governs my access?
- The previous wording and the new wording, side by side, for each clause that moved.
- The date on which the new clause binds, when the document states one.
- A feasibility signal: the count and the ratio of documents that a plain HTTP client cannot read.

### How it works

1. One GET for each document. HTTP only: no browser, no proxy, no CAPTCHA service, no paid API,
   no external database and no language model.
2. HTML to text normalisation. Scripts, style sheets, comments and white space are removed, so a
   new style sheet does not look like a new clause. Only the main region of the document is kept:
   the navigation, the header and the footer carry taxonomy words, for example a "Robot Access"
   menu entry, and a site edits them for reasons that have nothing to do with the terms.
3. Clause segmentation. For each term of the taxonomy, the Actor keeps a text window of
   `contextChars` characters around each match.
4. Comparison against the baseline that the previous run stored in a named key-value store.
5. Date extraction from the changed window.

#### State: the baseline

A change is a difference against a stored version, so the Actor cannot report a change without
state. The previous normalised text and the previous clause windows live in the key-value store
named by `stateStoreName`, one record for each document URL.

**The first run of a document creates the baseline and reports no change.** This is correct, not
a fault. Run the Actor a second time to get a comparison. Keep the same `stateStoreName` over
every run of one watch list.

A baseline that was taken with another window size is not compared. The Actor writes a fresh
baseline instead, so that a changed `contextChars` value does not produce false changes.

### Input

Every field has a default, so a run with an empty input `{}` succeeds.

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `documents` | array | two public policy pages | 1 to 30 records, each with a `label` and a `url` |
| `clauseTerms` | array | 22 terms | The automation taxonomy. Maximum 50 terms |
| `contextChars` | integer | `600` | Size of the text window around each matched term |
| `reportUnchanged` | boolean | `false` | Also write one record for each clause window that is still there word for word |
| `stateStoreName` | string | `tos-clause-baselines` | Name of the key-value store that holds the baselines |
| `timeoutSeconds` | integer | `20` | Timeout for one GET |
| `maxResponseBytes` | integer | `1000000` | Hard cap on the bytes read from one document |
| `maxRedirects` | integer | `3` | Redirect hops. Every hop passes the public-address check |
| `concurrency` | integer | `4` | Documents fetched at the same time |
| `userAgent` | string | `TosClauseWatcher/0.1 (+https://apify.com)` | Identifies this Actor in the logs of the watched site |

The default taxonomy holds: automated means, automated, scrape, scraping, crawler, crawl,
spider, bot, robot, robots.txt, data mining, text and data mining, harvest, AI agent, artificial
intelligence, machine learning, LLM, large language model, rate limit, API key, resale, resell.

#### Example

```json
{
  "documents": [
    { "label": "Marketplace", "url": "https://example.com/legal/terms-of-use" },
    { "label": "Marketplace API", "url": "https://example.com/legal/developer-agreement" }
  ],
  "clauseTerms": ["automated means", "scrape", "crawler", "bot", "AI agent", "LLM", "rate limit"],
  "contextChars": 600
}
```

### Output

The dataset holds three record types. Read them by `recordType`.

#### `clause-change`, one for each clause window that changed

| Field | Meaning |
| --- | --- |
| `siteLabel`, `documentUrl` | Which document changed |
| `clauseTerm` | The term of the taxonomy that the window is built around |
| `changeClass` | `added`, `removed` or `reworded` |
| `previousWindow` | The text window as the last run stored it. Empty for `added` |
| `newWindow` | The text window as this run read it. Empty for `removed` |
| `dateText`, `dateIso`, `dateKind` | The date near the change. `dateKind` is `binding`, `effective`, `updated` or `mentioned` |
| `previousCapturedAt`, `detectedAt` | When the two versions were read |

A window pair above a similarity threshold is one clause after a rewrite, so it is reported as
`reworded` with both versions. Two windows that are not similar are reported as one `removed`
and one `added`.

#### `document`, one for each document

`fetchStatus` (`OK`, `BASELINE_CREATED`, `FETCH_FAILED`, `UNREADABLE` or `REFUSED`),
`httpStatus`, `textHash` (SHA-256 of the normalised text), `changed`, `clauseChanges`,
`clauseTermsPresent`, `contentBytes`, `truncated`, `error` and `checkedAt`.

`UNREADABLE` means that the body holds no readable terms text. The site probably renders the
document with JavaScript or blocks a plain HTTP client. Watch `unreadableRatio` in the summary:
a high ratio means that the watch list is not feasible for an HTTP-only reader.

#### `summary`, one for each run

`documentsRequested`, `documentsCompared`, `documentsChanged`, `documentsBaselined`,
`documentsUnreadable`, `documentsFailed`, `clauseChanges`, `newAutomationClauses`,
`clauseChangesWithDate`, `unreadableRatio`, `status` and `finishedAt`.

`status` is `CHANGES_FOUND`, `NO_CHANGE`, `BASELINE_CREATED` or `NO_DOCUMENT_READ`.

#### `clause-unchanged`, only when `reportUnchanged` is true

The evidence that a clause window is still there word for word. Use it for an audit. It is not a
clause change and it is never charged as one. The count is bounded for each document.

### Runs never fail on a business verdict

A refused URL, a document that the HTTP-only fetch cannot read, and a run with zero clause
changes are all results. They are written to the dataset and to the run status message, and the
run ends SUCCEEDED. A run ends FAILED only when the input itself cannot be read.

### Pricing: pay per event

| Event | Unit | Charged |
| --- | --- | --- |
| `run-started` | One Actor run | Once for each run, after the input is validated |
| `document-checked` | One legal document fetched, normalised and compared against the baseline | Once for each document that was read and compared. A document that could not be read is not charged |
| `clause-change-reported` | One clause window that changed since the last run | Once for each `clause-change` record. A `clause-unchanged` record is not charged |

A quiet week stays cheap, so you can keep the schedule. The change carries the higher price,
because the change is the product.

### Safety

- Only HTTP and HTTPS URLs. Credentials in a URL are refused.
- Loopback, private, link-local and reserved addresses are refused, and every redirect hop is
  checked again.
- The read stops at `maxResponseBytes`. Input, concurrency, redirects and timeouts are bounded.
- The Actor sends one GET for each document and it identifies itself with `userAgent`.

### Development

```bash
uv sync
uv run pytest
uv run ruff check .
```

# Actor input Schema

## `documents` (type: `array`):

1 to 30 records. Each record holds a site label and the URL of one legal document, for example a terms of use, an acceptable use policy or a developer agreement. Private, loopback and reserved addresses are refused.

## `clauseTerms` (type: `array`):

The taxonomy of terms that govern automated access. The Actor keeps a text window around each term and compares only those windows, so a change to an address or to a price table is not reported. Maximum 50 terms.

## `contextChars` (type: `integer`):

Size of the text window that the Actor keeps around each matched term. A larger window shows more of the clause and reports a change more often.

## `reportUnchanged` (type: `boolean`):

Also write one record for each clause window that is still there word for word. Use this for an audit. An unchanged window is not charged as a clause change.

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

Name of the key-value store that holds the previous normalised text and the previous clause windows. Keep the same name over every run of one watch list, because a change is a difference against the stored version.

## `timeoutSeconds` (type: `integer`):

Timeout for one GET of one document.

## `maxResponseBytes` (type: `integer`):

Hard cap on the bytes read from one document. A longer body is cut at this size and the record shows truncated.

## `maxRedirects` (type: `integer`):

Redirect hops followed for one document. Every hop passes the public-address check.

## `concurrency` (type: `integer`):

Number of documents fetched at the same time. Keep it low, because the watch list holds sites that you must not overload.

## `userAgent` (type: `string`):

User-Agent header sent with every request, so the watched site can identify this Actor in its logs. ASCII characters only.

## Actor input object example

```json
{
  "documents": [
    {
      "label": "Project Gutenberg",
      "url": "https://www.gutenberg.org/policy/robot_access.html"
    },
    {
      "label": "Project Gutenberg terms of use",
      "url": "https://www.gutenberg.org/policy/terms_of_use.html"
    }
  ],
  "clauseTerms": [
    "automated means",
    "automated",
    "scrape",
    "scraping",
    "crawler",
    "crawl",
    "spider",
    "bot",
    "robot",
    "robots.txt",
    "data mining",
    "text and data mining",
    "harvest",
    "AI agent",
    "artificial intelligence",
    "machine learning",
    "LLM",
    "large language model",
    "rate limit",
    "API key",
    "resale",
    "resell"
  ],
  "contextChars": 600,
  "reportUnchanged": false,
  "stateStoreName": "tos-clause-baselines",
  "timeoutSeconds": 20,
  "maxResponseBytes": 1000000,
  "maxRedirects": 3,
  "concurrency": 4,
  "userAgent": "TosClauseWatcher/0.1 (+https://apify.com)"
}
```

# Actor output Schema

## `dataset` (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 = {
    "documents": [
        {
            "label": "Project Gutenberg",
            "url": "https://www.gutenberg.org/policy/robot_access.html"
        },
        {
            "label": "Project Gutenberg terms of use",
            "url": "https://www.gutenberg.org/policy/terms_of_use.html"
        }
    ],
    "clauseTerms": [
        "automated means",
        "automated",
        "scrape",
        "scraping",
        "crawler",
        "crawl",
        "spider",
        "bot",
        "robot",
        "robots.txt",
        "data mining",
        "text and data mining",
        "harvest",
        "AI agent",
        "artificial intelligence",
        "machine learning",
        "LLM",
        "large language model",
        "rate limit",
        "API key",
        "resale",
        "resell"
    ],
    "contextChars": 600,
    "reportUnchanged": false,
    "stateStoreName": "tos-clause-baselines",
    "timeoutSeconds": 20,
    "maxResponseBytes": 1000000,
    "maxRedirects": 3,
    "concurrency": 4,
    "userAgent": "TosClauseWatcher/0.1 (+https://apify.com)"
};

// Run the Actor and wait for it to finish
const run = await client.actor("kingii98/target-site-terms-automation-clause-change-watcher").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 = {
    "documents": [
        {
            "label": "Project Gutenberg",
            "url": "https://www.gutenberg.org/policy/robot_access.html",
        },
        {
            "label": "Project Gutenberg terms of use",
            "url": "https://www.gutenberg.org/policy/terms_of_use.html",
        },
    ],
    "clauseTerms": [
        "automated means",
        "automated",
        "scrape",
        "scraping",
        "crawler",
        "crawl",
        "spider",
        "bot",
        "robot",
        "robots.txt",
        "data mining",
        "text and data mining",
        "harvest",
        "AI agent",
        "artificial intelligence",
        "machine learning",
        "LLM",
        "large language model",
        "rate limit",
        "API key",
        "resale",
        "resell",
    ],
    "contextChars": 600,
    "reportUnchanged": False,
    "stateStoreName": "tos-clause-baselines",
    "timeoutSeconds": 20,
    "maxResponseBytes": 1000000,
    "maxRedirects": 3,
    "concurrency": 4,
    "userAgent": "TosClauseWatcher/0.1 (+https://apify.com)",
}

# Run the Actor and wait for it to finish
run = client.actor("kingii98/target-site-terms-automation-clause-change-watcher").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 '{
  "documents": [
    {
      "label": "Project Gutenberg",
      "url": "https://www.gutenberg.org/policy/robot_access.html"
    },
    {
      "label": "Project Gutenberg terms of use",
      "url": "https://www.gutenberg.org/policy/terms_of_use.html"
    }
  ],
  "clauseTerms": [
    "automated means",
    "automated",
    "scrape",
    "scraping",
    "crawler",
    "crawl",
    "spider",
    "bot",
    "robot",
    "robots.txt",
    "data mining",
    "text and data mining",
    "harvest",
    "AI agent",
    "artificial intelligence",
    "machine learning",
    "LLM",
    "large language model",
    "rate limit",
    "API key",
    "resale",
    "resell"
  ],
  "contextChars": 600,
  "reportUnchanged": false,
  "stateStoreName": "tos-clause-baselines",
  "timeoutSeconds": 20,
  "maxResponseBytes": 1000000,
  "maxRedirects": 3,
  "concurrency": 4,
  "userAgent": "TosClauseWatcher/0.1 (+https://apify.com)"
}' |
apify call kingii98/target-site-terms-automation-clause-change-watcher --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kingii98/target-site-terms-automation-clause-change-watcher"
        }
    }
}

```

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/cLlXKCB9hVXB5CM1B/builds/RCyeFEliCmSAMg1JY/openapi.json
