# Terms of Service (TOS) Watchdog (`woundless_vehicle/tos-watchdog`) Actor

An Actor that compares two Terms of Services (updated or current versus previous or old) and uses an LLM to analyze the risk of changes

- **URL**: https://apify.com/woundless\_vehicle/tos-watchdog.md
- **Developed by:** [Bolzyefx](https://apify.com/woundless_vehicle) (community)
- **Categories:** AI, Agents, Other
- **Stats:** 4 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.10 / 1,000 results

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

## Terms of Service Watchdog

An Apify Actor that compares two Terms of Service URLs (old vs new) and uses AI to analyze legal risk of changes.

### Overview

This Actor performs a one-time comparison between two Terms of Service URLs (old version vs new version). It crawls both URLs using Crawlee's BeautifulSoupCrawler, compares their content, and if differences are found, uses GPT-4o-mini to produce a structured risk assessment.

### Features

- **Crawlee-Powered Fetching**: Uses BeautifulSoupCrawler with built-in retries and request management
- **One-Time Analysis**: No stateful storage - perfect for ad-hoc comparisons
- **Change Detection**: Identifies differences between versions
- **AI-Powered Analysis**: Uses GPT-4o-mini to identify high-risk changes
- **Structured Output**: Returns status, risk level (with category), and detailed analysis

### Tech Stack

- **Python 3.12+**
- **Crawlee (BeautifulSoupCrawler)**: Web crawling with automatic retries and request management
- **BeautifulSoup4**: HTML parsing and text extraction
- **LangChain**: LLM orchestration
- **OpenAI GPT-4o-mini**: Legal risk analysis
- **Apify SDK**: Actor framework and storage

### Input Schema

```json
{
  "old_url": "https://example.com/terms/old",
  "new_url": "https://example.com/terms/new",
  "openai_api_key": "sk-..."
}
```

**Required Fields:**

- `old_url`: URL of the old/previous version to compare
- `new_url`: URL of the new/current version to compare
- `openai_api_key`: OpenAI API key for GPT-4o-mini analysis

### Output Schema

Results are saved to the default Dataset with the following structure:

```json
{
  "old_url": "https://example.com/terms/old",
  "new_url": "https://example.com/terms/new",
  "status": "CHANGED",
  "risk_level": "HIGH (Data Privacy)",
  "analysis": "The new terms explicitly introduce \"content analysis\" and \"machine learning\" as permitted uses for customer data...",
  "timestamp": "2024-01-01T12:00:00+00:00"
}
```

| Field | Type | Description |
|-------|------|-------------|
| `status` | string | `CHANGED`, `UNCHANGED`, or `ERROR` |
| `risk_level` | string | Risk level with category, e.g. `HIGH (Data Privacy)`, `MEDIUM (Billing)`, `LOW (General)`, `NONE` |
| `analysis` | string | AI-generated plain-language analysis of legal implications |
| `old_url` | string | Old ToS URL |
| `new_url` | string | New ToS URL |
| `timestamp` | string | ISO 8601 UTC timestamp |

### Installation

#### For Local Development/Testing

**Yes, you need to install dependencies before running locally:**

1. Create a virtual environment (recommended):

```bash
python3 -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
```

2. Install dependencies:

```bash
pip install -r requirements.txt
```

3. Set up input file for local testing:

```bash
mkdir -p storage/key_value_stores/default
## Create storage/key_value_stores/default/INPUT.json with your configuration
```

Example `INPUT.json`:

```json
{
  "old_url": "https://example.com/terms/old",
  "new_url": "https://example.com/terms/new",
  "openai_api_key": "sk-your-openai-api-key-here"
}
```

**Note:** The `INPUT.json` file is already created in the project. Edit it with your actual URLs and API key.

#### For Apify Platform

**No manual installation needed** - Dependencies are automatically installed during the Actor build process on Apify.

### Usage

#### Local Development

After installing dependencies, you can run the Actor locally:

```bash
## Using Python directly
python src/main.py

## Or using Apify CLI (if installed)
apify run
```

**Note:** For local testing, you'll need:

- Python 3.12 or higher
- All dependencies from `requirements.txt` installed
- OpenAI API key (passed via input)
- Optional: Apify token if you want to use Apify storage locally

#### Apify Platform

1. Push the Actor to Apify (dependencies install automatically)
2. Configure input with your old and new ToS URLs and OpenAI API key
3. Run the Actor to perform the comparison

### How It Works

1. **Crawl Both URLs**: BeautifulSoupCrawler fetches both URLs concurrently with automatic retry handling
2. **Text Extraction**: HTML is parsed; script, style, nav, header, and footer elements are stripped to produce clean text
3. **Text Comparison**: The two cleaned texts are compared directly
4. **LLM Analysis**: If texts differ, both versions are sent to GPT-4o-mini for structured analysis
5. **Risk Assessment**: The LLM returns a JSON response with:
   - **Status**: `CHANGED` or `UNCHANGED`
   - **Risk Level**: severity + category (e.g. `HIGH (Data Privacy)`)
   - **Analysis**: plain-language explanation of what changed and why it matters
6. **Output**: Results are saved to the Apify Dataset

### File Structure

```
tos_watchdog/
├── .actor/
│   ├── actor.json          # Actor manifest (name, version, schemas)
│   ├── INPUT_SCHEMA.json   # Input schema
│   └── output_schema.json  # Output schema
├── src/
│   ├── __init__.py         # Package marker
│   ├── main.py             # Entry point - Crawlee crawler + Actor lifecycle
│   └── analysis.py         # LangChain/OpenAI LLM integration
├── Dockerfile              # Container build (apify/actor-python:3.12)
├── requirements.txt        # Python dependencies
├── INPUT_SAMPLE.json       # Example input payload
└── storage/                # Local dev storage (key-value store, datasets)
```

### Notes

- **Stateless**: No persistent storage - each run is independent
- **Crawlee Retries**: BeautifulSoupCrawler handles retries automatically (3 attempts by default)
- **Text Extraction**: Removes scripts, styles, and navigation elements for cleaner comparison
- **Token Limits**: Both texts are limited to 15,000 characters each to stay within GPT-4o-mini token limits
- **Structured LLM Output**: The LLM is prompted to return JSON with status, risk\_level, and analysis fields
- **Error Handling**: The Actor handles errors gracefully and reports them in the output

# Actor input Schema

## `old_url` (type: `string`):

URL of the old/previous version of the Terms of Service to compare

## `new_url` (type: `string`):

URL of the new/current version of the Terms of Service to compare

## `openai_api_key` (type: `string`):

OpenAI API key for GPT-4o-mini analysis. This key is used to analyze legal risks in the Terms of Service changes. Keep this key secure and do not share it publicly.

## Actor input object example

```json
{
  "old_url": "https://web.archive.org/web/20230727142055/https://explore.zoom.us/en/terms/",
  "new_url": "https://explore.zoom.us/en/terms/",
  "openai_api_key": "sk-placeholder-for-testing"
}
```

# Actor output Schema

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

Complete comparison results including risk analysis

## `overview` (type: `string`):

Overview view of comparison results

# 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 = {
    "old_url": "https://web.archive.org/web/20230727142055/https://explore.zoom.us/en/terms/",
    "new_url": "https://explore.zoom.us/en/terms/",
    "openai_api_key": "sk-placeholder-for-testing"
};

// Run the Actor and wait for it to finish
const run = await client.actor("woundless_vehicle/tos-watchdog").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 = {
    "old_url": "https://web.archive.org/web/20230727142055/https://explore.zoom.us/en/terms/",
    "new_url": "https://explore.zoom.us/en/terms/",
    "openai_api_key": "sk-placeholder-for-testing",
}

# Run the Actor and wait for it to finish
run = client.actor("woundless_vehicle/tos-watchdog").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "old_url": "https://web.archive.org/web/20230727142055/https://explore.zoom.us/en/terms/",
  "new_url": "https://explore.zoom.us/en/terms/",
  "openai_api_key": "sk-placeholder-for-testing"
}' |
apify call woundless_vehicle/tos-watchdog --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=woundless_vehicle/tos-watchdog",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/ryhvkM85D9QLgLcLG/builds/8sBsiCQr2bi3LrKSO/openapi.json
