# SiteVerdict — Cookie & Privacy Policy Checker + Draft Generator (`siteverdict/siteverdict-privacy-policy-checker`) Actor

Find cookies & trackers your site sets (no click / Reject / Accept) and check them against your cookie & privacy policies: missing cookies, UK GDPR Art 13/14 gaps, ICO/PECR consent issues. Generates a DRAFT cookie table & cookie policy. Not legal advice.

- **URL**: https://apify.com/siteverdict/siteverdict-privacy-policy-checker.md
- **Developed by:** [Aman Thakran](https://apify.com/siteverdict) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## SiteVerdict — Cookie & Privacy Policy Checker + Draft Generator

**Does your cookie policy match what your website actually sets? Get the gaps, the evidence and a draft fix in about a minute per site.**

The checker loads your site three times in real Chrome: no click, after **Reject** and after **Accept**. It builds the real cookie and tracker inventory, then reads your privacy and cookie policies and shows:

- which cookies and providers the policy is **missing**;
- which UK GDPR Art 13/14 elements the privacy notice lacks, with quotes as evidence;
- whether anything non-essential runs before consent or after Reject.

It then drafts the missing cookie table and policy wording for you to review.

> **Not legal advice.** These are automated, heuristic *risk indicators* and *DRAFT templates*. We are not lawyers. Drafts must be reviewed by the site owner and a qualified adviser before use.

### Why this one

| | SiteVerdict | Typical cookie / privacy scanner |
|---|---|---|
| Inventory from **3 consent states** (no click / Reject / Accept) | ✅ | ❌ one load, or "accept" only |
| **Reject confirmed** by reading the consent tool's own record before reporting "after Reject" issues | ✅ (`confidence` on every finding) | ❌ |
| **Inventory vs policy text** (cookie names, `_ga_*` patterns, providers) | ✅ with quotes | ❌ "policy link found: yes/no" |
| **Art 13/14 checklist** with evidence quote + section | ✅ 15 elements | ❌ or presence only |
| **DRAFT cookie table + cookie policy** from observed cookies | ✅ Markdown / HTML / CSV | ❌ |
| **Client report PDF** (white-label) | ✅ | ❌ |
| **Monitoring**: policy text changed, new cookies not in the policy, new pre-consent cookies → Slack / webhook | ✅ | ❌ |
| **AI-drafted wording** for the gaps, with your own key | ✅ optional | ❌ |
| Bot-protection pages | `blocked`, **not charged** | often reported as "no cookies" |

### Three ways customers use it

1. **Web agencies:** hand clients a branded PDF that says what the site sets, what the policy is missing, and includes a draft cookie table to paste in.
2. **DPOs and privacy consultants:** run a monthly monitor across clients. You get an alert when a new cookie or tracker appears that the policy does not mention, or when the policy text changes.
3. **Small businesses:** after adding GA4, Meta Pixel, Hotjar or a chat widget, run the check and get the exact rows to add to the cookie table.

### Before / after

**A typical scanner:**

```text
privacy policy: found   cookie policy: found   cookies: 31   banner: yes
```

**SiteVerdict** (shortened):

```json
{
  "riskLevel": "high",
  "consentBehaviour": { "rejectVerification": "confirmed", "nonEssentialAfterReject": ["_fbp (.shop.example.co.uk, Meta (Facebook), marketing)"] },
  "mismatches": { "cookiesNotInPolicy": ["_clck (.shop.example.co.uk, Microsoft Clarity, analytics)"], "vendorsNotInPolicy": ["Microsoft Clarity"], "vendorsInPolicyNotObserved": ["Hotjar"] },
  "privacyNoticeCompleteness": 73,
  "privacyNoticeChecks": [{ "label": "Right to complain to the ICO", "status": "partial", "evidence": [{ "quote": "You can lodge a complaint with a supervisory authority.", "section": "Your rights" }] }],
  "drafts": { "generated": true, "cookieTableMarkdownUrl": "…/draft-cookie-table-shop-example-co-uk.md" },
  "clientReport": { "pdfUrl": "…/client-report-shop-example-co-uk.pdf" }
}
```

### Benchmark

⟦PPC_BENCH⟧

The method, the site list and caveats are in `BENCHMARK.md`. Competitor Actors were compared on their documented outputs only; they were not run.

### Pricing (pay per event)

| Event | When | Price |
|---|---|---|
| `site-checked` | A site was checked: 3 consent states + policy pages | **$0.15** |
| `draft-generated` | DRAFT cookie policy + cookie table produced; only when at least one cookie or storage item was observed | **$0.20** |
| `client-report` | Client PDF + HTML produced (`clientReport`) | $0.05 |
| `ai-fix-generated` | AI-drafted wording produced with **your own** key (off by default) | $0.04 |
| `alert-sent` | Slack / webhook digest delivered | $0.01 |

**Examples:**

| Job | Cost |
|---|---|
| 1 site + draft + PDF | **$0.40** |
| 50 client sites, check + PDF | **$10** |
| Monthly monitor of 30 sites with alerts | **≈ $4.51/month** |

Blocked or failed sites are not charged. Set a maximum total charge per run and the Actor stops cleanly when it is reached.

### Input

```json
{
  "startUrls": [{ "url": "https://www.example.co.uk" }],
  "generateDrafts": true,
  "clientReport": true,
  "reportAgencyName": "Your Agency Ltd",
  "maxInternalPages": 1,
  "monitoringMode": false,
  "aiFix": false
}
```

If your policies are not found automatically, set `privacyPolicyUrl` / `cookiePolicyUrl`. These only apply when you check a single site.

**Monitoring:** set `monitoringMode: true` with a `monitorName` and schedule the Actor. Each item gets a `change` object, for example:

```json
"change": { "changed": true, "policyTextChanged": false, "newCookiesNotInPolicy": ["_ttp"], "headline": "1 new cookie(s) not in the policy: _ttp · new trackers: TikTok Pixel" }
```

Only a hash of the policy text is stored, never the text itself. Add `slackWebhookUrl` / `webhookUrl` (secret) for alerts.

**AI drafting (optional):** set `aiFix: true`, `aiProvider` and `aiApiKey` (your key, secret). It drafts the cookie-table rows, the privacy-notice wording for missing elements and the consent-tool changes. Anything not observed is left as a `[PLACEHOLDER]`.

### Output

- **Dataset views:**
  - Overview
  - Cookie inventory (CSV export)
  - Risk indicators
  - Privacy notice checks (Art 13/14)
  - Mismatches
  - Changes (monitoring)
- **Key-value store, per site:**
  - `draft-cookie-policy-<site>.md` / `.html`
  - `draft-cookie-table-<site>.md`
  - `cookie-inventory-<site>.csv`
  - `client-report-<site>.pdf` / `.html`
  - `ai-fix-<site>.md`
- **Key-value store, run level:** `REPORT` (HTML) and `SUMMARY` (JSON).

### How accuracy is protected

- "After Reject" counts only cookies and storage that are **new** after the click. Items already set before consent are reported once, as "before consent".
- The Reject click is **read back** from the consent tool: OneTrust, Cookiebot, CookieYes, Didomi, Usercentrics, Osano, TrustArc, iubenda, Complianz, Transcend, IAB TCF, the Google CMP, GOV.UK/NHS-style JSON cookies, and Google Consent Mode. If the tool recorded the opposite of the click, it is a "check manually" item and is not scored.
- These do not count as trackers firing:
  - script downloads;
  - cookieless Consent Mode / UET pings;
  - Google limited-ads requests;
  - embedded video players. Their cookies are still classified individually.
- Generic cookie names (`sb`, `SM`, `UID`, `MUID`…) are matched only on the vendor's domain. Google `AEC`/`SOCS` are strictly necessary, and YouTube `YSC` is functional.
- Unknown cookies are left **unclassified** for you to confirm. They are never guessed.

### FAQ

**Is this a compliance certificate?**
No. It is an indicator tool, not legal advice. Keyword checks can miss wording that is present, or accept wording that is weak. A person must review the results.

**Why might the inventory miss cookies?**
Only the pages visited are scanned. Logged-in areas, checkouts, geo-targeted tags and A/B tests can be missed. If Accept cannot be clicked, cookies that load after consent are missing, and this is flagged.

**Does it work for non-English policies?**
The policy-text checks are English heuristics. The inventory and consent tests work in any language. Button detection covers EN/FR/DE/ES/IT/PT/NL.

**Does it bypass bot protection?**
No. It never solves CAPTCHAs or bypasses protection. Blocked sites are reported as `blocked` and not charged. Policy pages respect `robots.txt` for the token `SiteVerdict`.

### Limits

- One location per run (an Apify Proxy country is optional), and one snapshot in time.
- Policies that are PDFs, sit inside iframes, or use JavaScript-only accordions that do not render text may not be extracted.
- About 1–2 minutes per site. The default is 2 sites in parallel at 2 GB.

### Compliance & sources

- **UK GDPR Art 13/14.** ICO, *Right to be informed*.
- **PECR reg 6.** ICO, *storage and access technologies* guidance.
- **ICO consent-or-pay guidance (2025)** and the ICO's work on making "Reject all" as easy as "Accept all".
- **EU ePrivacy Directive Art 5(3).** EDPB Cookie Banner Taskforce report (Jan 2023); EDPB Guidelines 2/2023.
- **CNIL cookie guidelines (2020).**
- **Data (Use and Access) Act 2025 (UK).**

References were checked against the public sources as of October 2026.

**Personal data:**

- No personal data about individuals is collected.
- Only cookie *names*, domains and expiry are recorded; values are never recorded.
- Consent-cookie values are parsed in memory and only the yes/no result is kept.
- Identifiers are redacted from evidence URLs.

*Built by SiteVerdict (BharatFare Travel and Technologies Ltd, UK). Pairs with the **SiteVerdict Cookie Consent & Consent Mode v2 Auditor**, which adds the PDF evidence pack, the request timeline and the Consent Mode v2 checklist. Contact: aman.thakran@bharatfare.com.*

# Changelog

This Actor's version history is a separate document: https://apify.com/siteverdict/siteverdict-privacy-policy-checker/changelog.md

# Actor input Schema

## `startUrls` (type: `array`):

Home pages of the websites to check. One result (and one 'site-checked' charge) per URL. A missing https:// is added automatically. Each site is loaded 3 times in a real browser (no click, Reject, Accept) and its privacy / cookie policy pages are read.

## `generateDrafts` (type: `boolean`):

Create a DRAFT cookie policy (Markdown + HTML) and cookie table from the observed cookies, saved in the key-value store. Billed as 'draft-generated' (only when at least one cookie/storage item was observed). Drafts are templates for review by you and a legal adviser - not legal advice.

## `clientReport` (type: `boolean`):

Save a client-ready single-site report (HTML + PDF) with a first-visit screenshot, findings, Art 13/14 checklist, cookie inventory and policy mismatches. Billed as 'client-report' only when the PDF is produced.

## `reportAgencyName` (type: `string`):

Shown at the top of the client report. Leave empty for SiteVerdict branding.

## `reportClientName` (type: `string`):

Optional client name for the report title.

## `privacyPolicyUrl` (type: `string`):

Use this privacy policy page instead of auto-discovery. Only used when exactly one start URL is given.

## `cookiePolicyUrl` (type: `string`):

Use this cookie policy page instead of auto-discovery. Only used when exactly one start URL is given.

## `maxInternalPages` (type: `integer`):

Also visit up to this many internal pages (same host, linked from the home page) in each consent scenario, to find cookies set on deeper pages. 0 = home page only. Included in the per-site price.

## `maxConcurrency` (type: `integer`):

How many sites are checked in parallel. 2 is good for 2 GB memory.

## `settleTimeSecs` (type: `integer`):

Extra wait after each page load and banner click so delayed tags and banners can appear.

## `bannerWaitSecs` (type: `integer`):

If no banner is visible after the page settles, keep checking for up to this long before concluding there is none.

## `navigationTimeoutSecs` (type: `integer`):

Maximum time to wait for each page to load.

## `locale` (type: `string`):

Browser locale (Accept-Language). Policy checks are English-language heuristics.

## `timezoneId` (type: `string`):

IANA time zone, e.g. Europe/London.

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

Leave empty for a standard desktop Chrome user agent (some consent tools hide the banner from crawler-like agents, which would distort the consent test). robots.txt checks for policy pages always use the token 'SiteVerdict'.

## `proxyConfiguration` (type: `object`):

Optional. Banners and tags can be geo-targeted: use Apify Proxy with country GB (or DE, FR...) to see what visitors from that country get. Not used to evade blocking.

## `monitoringMode` (type: `boolean`):

Keep a snapshot per site in a named key-value store and report what changed since the previous run: policy text or URL changed, new cookies not in the policy, new cookies before consent, new / resolved issues. The policy text itself is not stored (only a hash). Schedule the Actor to monitor continuously.

## `monitorName` (type: `string`):

Snapshots are stored in the key-value store 'siteverdict-policy-monitor-<name>' in your account.

## `onlyChanged` (type: `boolean`):

In monitoring mode, push a dataset item only for sites that changed (and on the first run). Every site is still checked and billed.

## `alertOn` (type: `string`):

'changes' (default in monitoring mode) or 'issues' (critical/high findings). One digest per run and destination.

## `slackWebhookUrl` (type: `string`):

https://hooks.slack.com/... URL. Secret, never logged. Billed as 'alert-sent' per delivered message.

## `webhookUrl` (type: `string`):

Any public HTTPS endpoint; receives a JSON digest. Secret, never logged. Billed as 'alert-sent' per delivered message.

## `aiFix` (type: `boolean`):

Off by default. Uses YOUR Anthropic or OpenAI key to draft the missing cookie-policy rows, privacy-notice wording and consent-tool changes (DRAFT, placeholders for anything not observed). Only the check results are sent. Billed as 'ai-fix-generated' only when a draft is produced; the LLM cost is on your key.

## `aiProvider` (type: `string`):

Which API your key belongs to.

## `aiApiKey` (type: `string`):

Your own Anthropic or OpenAI API key. Secret, never logged or output.

## `aiModel` (type: `string`):

Leave empty for the default (Anthropic: claude-sonnet-5-5, mid-priced; OpenAI: gpt-4.1-mini). Any model your key can use, e.g. claude-sonnet-5-5 or claude-haiku-5-5.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.example.com"
    }
  ],
  "generateDrafts": true,
  "clientReport": false,
  "maxInternalPages": 1,
  "maxConcurrency": 2,
  "settleTimeSecs": 3,
  "bannerWaitSecs": 6,
  "navigationTimeoutSecs": 35,
  "locale": "en-GB",
  "timezoneId": "Europe/London",
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "monitoringMode": false,
  "monitorName": "default",
  "onlyChanged": false,
  "aiFix": false,
  "aiProvider": "anthropic"
}
```

# Actor output Schema

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

No description

## `report` (type: `string`):

No description

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

No description

## `files` (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 = {
    "startUrls": [
        {
            "url": "https://www.example.com"
        }
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("siteverdict/siteverdict-privacy-policy-checker").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 = {
    "startUrls": [{ "url": "https://www.example.com" }],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("siteverdict/siteverdict-privacy-policy-checker").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 '{
  "startUrls": [
    {
      "url": "https://www.example.com"
    }
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call siteverdict/siteverdict-privacy-policy-checker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,siteverdict/siteverdict-privacy-policy-checker"
        }
    }
}
```

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/p5eYwyhIzyOiBa1pK/builds/DN7QBsqrpiKaCyca9/openapi.json
