# Vendor Policy and Subprocessor Change Watch (`fractionalhqforyou/vendor-policy-change-watch`) Actor

Watches the terms, privacy, DPA, subprocessor list, security and pricing pages your suppliers publish, and reports what changed since the last run: added and removed subprocessors by name, added and removed clauses, and a plain-English summary. No browser, no proxy.

- **URL**: https://apify.com/fractionalhqforyou/vendor-policy-change-watch.md
- **Developed by:** [Jessy Mariau](https://apify.com/fractionalhqforyou) (community)
- **Categories:** Automation, Integrations, Developer tools
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 page checkeds

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/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

## Vendor Policy and Subprocessor Change Watch

Someone at your company signed a data processing agreement with a vendor two years ago. Last month that vendor added a new company to its subprocessor list. Nobody told you, and the first time anyone asks about it is when an auditor or a customer says: were we notified?

GDPR Article 28(2) says a processor must not engage another processor without the controller's authorisation. Most SaaS contracts give that authorisation in general terms. Where they do, the same paragraph obliges the processor to "inform the controller of any intended changes concerning the addition or replacement of other processors, thereby giving the controller the opportunity to object to such changes". In practice a vendor discharges that by publishing a page and expecting you to watch it. Almost nobody watches it.

I built this because checking twenty vendor pages by hand is the sort of job that gets done once, thoroughly, and then never again. Give it your vendors' pages and it tells you what moved since last time: which subprocessors appeared, which disappeared, which clauses were added or taken out, and a plain sentence on what changed.

### What it does

Point it at pages, or point it at vendors.

With pages, you give one URL per line: terms of service, privacy policy, DPA, subprocessor list, security page, pricing page, whatever you care about. With vendors, you give bare domains and the Actor fetches each homepage and follows the links whose text or address mentions privacy, terms, DPA, data processing, subprocessor, security or trust. If it does not find the subprocessor page from the homepage, it takes one more hop through the privacy or terms page, which is usually where that link actually lives.

Every page then gets fetched, stripped down to its readable text, and compared against the snapshot saved the last time you ran with the same watch key. You get back whether it changed, how much of the text survived, the lines that appeared and the lines that went away.

The part I care most about is the subprocessor extraction. A supplier register on the page gets parsed into rows. Each row carries the supplier's name, what it does for the vendor, and where the processing happens. It reads a register laid out as a table and a register laid out as a bulleted list, because vendors do both. Those rows are then compared against last time by name rather than by position, so a vendor reordering the table alphabetically does not show up as fifteen changes. What you get is the short answer: these two are new, that one is gone.

Everything runs on plain HTTP requests. No browser and no proxy.

### What you need

Nothing. Give it URLs and run it.

There is one optional extra. If you paste an OpenAI API key, every changed page also gets labelled material or cosmetic with one sentence on what the change touches. It is off by default and the Actor is fully useful without it. Apify stores the key as a secret.

Run it with no input at all and you get a demo: three sample pages, one unchanged, one DPA whose notice period moved, one subprocessor list with a name added and a name removed. Nothing is fetched and nothing is charged.

### How to run it

Pages you already know, watched under a name you choose:

```json
{
  "pages": "https://sentry.io/legal/subprocessors/\nhttps://n8n.io/legal/sub-processors/\nhttps://www.datadoghq.com/legal/subprocessors/\nhttps://buttondown.com/legal/subprocessors",
  "watchKey": "core-vendors"
}
```

Vendors you want the pages found for:

```json
{
  "vendors": "sentry.io\nbuttondown.com",
  "watchKey": "core-vendors"
}
```

Use the same `watchKey` every run. That key names the snapshot the run compares against and writes back to, so the second run is the first one that can tell you anything. Run it on a schedule, weekly or monthly, and point the run at Slack or email when `pages_changed` comes back above zero.

The `OUTPUT` record in the key-value store holds the run summary: pages checked, pages changed, which vendors were affected, every subprocessor added or removed across the whole run, and one line you can paste into a message.

### Output fields

| Field | What it holds |
|---|---|
| `vendor` | your vendor value, or the page's domain. |
| `label` | your label, or the detected page type. |
| `url` | the page as fetched, after redirects. |
| `page_type` | `terms`, `privacy`, `dpa`, `subprocessors`, `security`, `pricing` or `other`. |
| `changed` | true when the readable text is not what it was last run. |
| `first_run` | true when no snapshot existed, so this run set the baseline. |
| `similarity` | 0 to 1: how much text survived. Null on a first run. |
| `added_lines` | new lines, capped at 40. |
| `removed_lines` | vanished lines, capped at 40. |
| `added_lines_total`, `removed_lines_total` | the real counts behind the cap. |
| `subprocessors` | every supplier parsed off the page: `name`, `purpose`, `location`, `entity`. |
| `subprocessors_added` | suppliers on the page now, absent last run. |
| `subprocessors_removed` | suppliers on the page last run, absent now. |
| `change_summary` | one sentence in plain English. |
| `content_hash` | the hash of the readable text, for spotting a change yourself. |
| `previous_checked_at` | when the snapshot it compared against was taken. |
| `checked_at` | when this run happened. |
| `error` | why a page could not be checked, or null. |

### Pricing

Pay per event: one `page-checked` charge per page that was fetched and compared, at $0.004 a page. A page that failed to load, a domain that resolved to nothing and a demo run cost nothing. Discovering pages from a vendor domain is not charged either, only the pages that end up being checked.

Watching thirty pages once a week is about fifty cents a month.

### Limits

- **This reports what a page says and how it changed. It is not legal advice.** A change it finds is not a finding that anyone breached anything. Whether a change matters, and whether you were properly notified, is a question for whoever owns the contract.
- **The first run has nothing to compare against.** It saves the baseline and says so. The second run is the one that tells you something.
- **Discovery is best effort.** A vendor can rename a page, move it behind a trust portal, split it across a paginated table or drop the link from its footer. Check what came back, then pin the URLs in `pages` once you know them.
- **Some pages need a browser and this Actor does not use one.** A page rendered entirely by JavaScript, or fronted by a bot check, comes back empty or blocked and lands in the row as an error rather than a silent pass. I hit this on three well known vendor pages while testing, so expect it on a few of yours.
- **Subprocessor parsing needs a register that reads like one.** A table works when its first column is headed something like Vendor, Name, Company or Subprocessor. A bulleted list works when each entry says what the supplier does or where it is. A bare list of names with nothing else is skipped on purpose, because that is also what a page's link menu looks like.
- **If a site's robots.txt disallows the path, the page is skipped** and the row says so rather than the Actor pushing through.
- Names are matched with common company suffixes ignored, so Acme Ltd and Acme Limited are the same supplier. A vendor that renames an entry outright shows as one removed and one added, which is the honest answer when you cannot tell the two apart.

### Pair it with a workflow

If you run n8n, my free templates on https://n8n.io/creators/fractional-hq take a list like this Actor's output and turn it into lead scoring, outreach drafts and follow-ups without another tool in the chain.

How the diffing works, and where it falls down, is written up at https://fractionalhq.uk/use-cases/vendor-policy-watch

### Built by Fractional HQ

I run Fractional HQ, a one-man studio for design and automation. I do not write code by hand: I architect, review and hold the standard while AI agents do the typing. That method runs my own estate: seven brands, a Postgres operations system, more than 100 scheduled jobs, published n8n templates, Zapier Silver certified.

Want this wired into your CRM and outreach, or built and handed over working? Get in touch: https://fractionalhq.uk/brief

# Actor input Schema

## `pages` (type: `string`):

One URL per line: terms, privacy, DPA, subprocessor list, security or pricing pages. Leave empty and use the vendor domains below to have the Actor find them for you. Leave both empty for a demo run that charges nothing.

## `vendors` (type: `string`):

One domain per line, for example example.com. The homepage is fetched and the links whose text or address mentions privacy, terms, DPA, data processing, subprocessor, security or trust are followed. Best effort: a vendor can bury or rename a page, so check what came back and pin the URLs in the field above once you know them.

## `watchKey` (type: `string`):

Names the snapshot this run compares against and writes to. Use the same key every run for the same set of pages, and the run after next will show what moved. Leave empty and a key is derived from the URL list, which works as long as the list does not change.

## `openaiApiKey` (type: `string`):

Optional. With a key, every changed page also gets labelled material or cosmetic with one sentence on what it affects. Everything else works without it. Apify stores it as a secret.

## Actor input object example

```json
{}
```

# Actor output Schema

## `pages` (type: `string`):

One row per page: whether it changed, how similar it is to last time, the added and removed lines, the subprocessors parsed off the page, the ones added and removed since the last run, and a plain-English summary.

## `summary` (type: `string`):

The OUTPUT record: pages checked, pages changed, vendors affected, every subprocessor added or removed across the run, and one plain line.

# 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 = {
    "pages": "",
    "vendors": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("fractionalhqforyou/vendor-policy-change-watch").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 = {
    "pages": "",
    "vendors": "",
}

# Run the Actor and wait for it to finish
run = client.actor("fractionalhqforyou/vendor-policy-change-watch").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 '{
  "pages": "",
  "vendors": ""
}' |
apify call fractionalhqforyou/vendor-policy-change-watch --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fractionalhqforyou/vendor-policy-change-watch"
        }
    }
}

```

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/XOCiejdS0Q2Hfum9e/builds/HBGrcs3d7nxwr6uhf/openapi.json
