GDPR & ePrivacy Consent Scanner
Pricing
from $25.50 / 1,000 site scanneds
GDPR & ePrivacy Consent Scanner
One row per site with status, score, findings, the cookie banner, cookies and trackers before consent, and privacy-policy / imprint signals. An indicator, not legal advice. Only a row with status ok is charged.
Pricing
from $25.50 / 1,000 site scanneds
Rating
0.0
(0)
Developer
Mike Berckmoes
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
14 hours ago
Last modified
Categories
Share
An indicator of how a site behaves before anyone clicks Accept. For each URL the Actor opens the page in one headless browser, does not click Accept, and reports cookies and known trackers, the cookie banner, and whether a privacy policy and an imprint look reachable.
This is an automated indicator, not legal advice. It does not decide whether a site is lawful. Every finding is worded as a possible issue to verify.
What you get (one row per site)
| Field | What it means |
|---|---|
status | ok, partial, fetch_error, or invalid. Only ok is charged |
score | 0–100 indicator. Empty when the page never loaded |
findings[] | Each message starts with "Possible issue, verify" |
disclaimer | States that the row is not legal advice |
banner | Banner visible, CMP name, reject-all on the first layer, pre-ticked non-essential box |
cookiesBefore | Name, domain, and category only. Cookie values are never stored |
cookiesAfterReject | The same three fields after a first-layer reject click. Empty when reject was not clicked |
trackerRequests | Known tracker calls. Query strings are removed |
policy | Link, reachable or not, language, and heuristic GDPR elements |
processorsMentioned, dpaMentioned | A processor list or a data-processing agreement was mentioned |
imprint | Legal notice. Scored only for EU country domains and .eu |
pages | Extra same-site pages compared before consent |
CMPs recognised from page signals: Cookiebot, OneTrust, Didomi, Usercentrics, CookieYes, Iubenda, Complianz, and Quantcast. A TCF signal (__tcfapi or euconsent-v2) is recorded too.
Privacy-policy heuristics look for controller identity and contact, purposes, legal bases, retention, data-subject rights, a DPO, international transfers, and a complaint to a supervisory authority. The scan checks English, German, French, Dutch, Italian, and Spanish phrases. A missing phrase is a possible gap, not proof the policy fails.
How to use it
- Paste your own page URLs into Website URLs.
- Leave Pages per site at 1 unless you want up to two more same-site links compared before consent.
- Click reject-all is on by default. Accept, OK, and the banner background are never clicked.
- Turn on pre-ticked boxes only when you want the settings layer opened. The before-consent snapshot is taken first.
- Run, then export the dataset. The key-value store holds
RUN_SUMMARYonly. Banner screenshots and page HTML are not stored.
{"startUrls": ["https://example.de"],"maxPagesPerSite": 1,"clickReject": true,"inspectPreferences": false}
How a run stays small
- One Chromium for the whole run, and a fresh browser context for each site. The context is closed before the next site.
- Images, video, and fonts are blocked. Scripts stay on, because they set the cookies.
- At most 3 pages per site, plus one privacy page and one imprint page.
- The banner poll stops when a banner is visible. It does not wait for a quiet network.
- Each site is cut off at 90 seconds. The run timeout is 900 seconds. 60 seconds are reserved for the browser, so one run holds at most 9 sites: 60 + 9 × 90 = 870. The default is 5 sites.
- Each page load times out at 20 seconds and is tried at most 3 times. The navigation worst case is 9 sites × 3 tries × 20 seconds = 540 seconds, inside the 900 second run.
- Memory is 1024 MB by default and cannot be set above 2048 MB.
- A site that still does not load after those tries is a
fetch_errorrow. The run continues. Between tries the Actor waits 1 second, then 2 seconds.
A URL that is not a public http(s) hostname is stored as invalid and is not fetched. That includes any IP literal, a private, loopback, link-local, CGNAT, multicast or reserved address, localhost, a name with userinfo (@), a .local or .internal name, a hostname whose last label has no letters, and odd numeric forms such as 0x7f.0.0.1. A redirect to any of those is refused. Only a row with status ok is charged. partial, fetch_error, and invalid are free. If the buyer charge cap cannot take another site, that site is left out of the dataset. The key-value store is the run summary only: no banner PNG and no page HTML. A cookie on the row is the name, the domain, and the category. The cookie value is not stored.
Pricing
Prices are prepared only. They are in .actor/deploy.json under pricingPrepared and are not live. There is no .actor/pricing.json, so a publish does not apply them.
Proposed event: site-scanned at $0.03 (primary). That is the only event in the prepared price list. One charge per site whose status is ok, including up to 3 pages and a reject click. A site that fails to load is not charged.
Keep the platform's apify-actor-start event at its default $0.00005. Do not charge that event in code. It is what makes Apify cover the first 5 seconds of compute. The prepared list does not include apify-default-dataset-item. Remove that platform event when you turn pricing on, or every row is billed twice. Set the run's minimum charge cap (minimalMaxTotalChargeUsd) to $0.03 so a buyer cannot cap a run below one site.
Apify's cut is 20%. Profit on a paid plan is (0.8 × event revenue) − platform cost. Compute is $0.20 per CU on the Bronze tier (1 CU = 1 GB for 1 hour). Free-plan runs are not in this sum: Apify covers their compute and they pay you nothing.
Measured on this machine
python3 actors/gdpr-consent-scan/tests/measure_cost.py opened local fixture pages in Playwright Chromium (headless shell), with media blocked, on 27 Oct 2026. Each figure includes browser launch and is one site. This is wall-clock converted at the Actor's memory, not an Apify invoice.
| Shape | Wall clock | At 1024 MB, after the free 5s | Compute cost |
|---|---|---|---|
| Banner found, 1 page, reject, policy, imprint | 3.60s | inside the free 5s | $0 |
| No banner (full 8s poll), then policy and imprint | 10.55s | 5.55s | $0.0003 |
| 3-page cap, reject, screenshot | 5.43s | 0.43s | $0.00002 |
A 5 MB transfer at $0.20/GB is about $0.001. Dataset writes are under $0.0001.
Why $0.03 still works for a single slow site
The same event has to cover a site that uses the whole cap: 90s at 2048 MB.
- Billable time after the free 5s: 85s
- CU:
2 × 85 / 3600 = 0.0472 - Compute:
0.0472 × $0.20 = $0.0094 - With about $0.001 of transfer: $0.0104 platform cost
- Received from $0.03:
0.8 × 0.03 = $0.024 - Profit about $0.014 on that 1-site run
Break-even at that cap is about $0.013. The measured runs above are far under the cap, so the margin there is about $0.023 after the small transfer estimate.
Score
The score starts at 100. It is an indicator, with the same disclaimer as the findings.
| Signal | Points |
|---|---|
| Analytics or ad cookie, or a tracker request, before consent | −25 |
| No banner visible | −15 |
| Banner with no reject-all on the first layer | −20 |
| Pre-ticked non-essential box (only if you turn that check on) | −20 |
| Privacy policy missing or not reachable | −15 |
| Tracker still present after a reject click | −15 |
| Each missing policy element, and only when a policy was reached | −3, capped at −24 |
| No processor list and no DPA mention, when a policy was reached | −5 |
| Monitoring call before consent (Sentry, New Relic) | −5 |
No reachable imprint on an EU ccTLD or .eu | −8 |
.uk is reported and is not scored as an EU imprint. The score does not go below 0.
Limitations
- A banner is often shown only to visitors in the EU. "No banner" means no banner from this run's IP. The Actor does not use a residential proxy.
- Chromium drops many cross-site cookies. A missing third-party cookie is not a clean site. The request, and a
Set-Cookiethe jar did not keep, are the evidence. - Reject and pre-ticked detection can miss a custom banner that does not use a known CMP or a clear reject label.
- Policy elements are phrase checks. They miss a policy that explains the same point in other words, and they can match a sentence that is not really that element.
- The Actor does not click Accept, does not create an account, and does not submit forms.
- Do not treat the score, or any finding, as a compliance certificate.
Pricing status
Not published. Not priced live. Copy pricingPrepared into .actor/pricing.json only when you decide to charge, then apply it with the repo's deploy helper.
FAQ
What does GDPR & ePrivacy Consent Scanner check? For each URL it opens the page in a headless browser without clicking Accept, then reports cookies and known trackers set before consent, the cookie banner, and whether a privacy policy and an imprint look reachable.
Is the result legal advice? No. It is an automated indicator. Each finding starts with 'Possible issue, verify' and every row carries a disclaimer. It does not decide whether a site is lawful.
Which rows are charged? Only rows with status ok. partial, fetch_error and invalid rows are not charged. The price is $0.03 per site on the Free plan.
Are cookie values stored? No. Cookie names, domains and categories are stored; values never are. Tracker request query strings are removed.
Can it test the reject button? Yes, with clickReject on. The row then lists cookies after a first-layer reject click.
How many sites per run? Up to 9 sites per run (maxSites), one site per URL, with maxPagesPerSite extra same-site pages compared before consent.
Related Actors
- EU Impressum Lookup: Extracts company details from the legal notice the scan only checks for.
- Domain to Company Resolver: Resolves the same domain to its company, register match and VAT check.