The EU compliance scan API.
Send one GET request with a URL, get back nine technical checks for GDPR, NIS2, DORA and EAA as JSON. Any CMS, any stack. No key, no quota to buy, no account.
Make a request Try it in the browserOne call scans one site
This is the whole interface. The URL is the only parameter, and it works with or without a scheme.
# One GET request. That is the entire API.
curl "https://eucomply-scan.mahope-eeb.workers.dev/scan?url=webflow.com"
Prefer to send a body, or you need a longer URL than a query string takes? POST takes the same url field as JSON.
curl -X POST "https://eucomply-scan.mahope-eeb.workers.dev/scan" \
-H "Content-Type: application/json" \
-d '{"url":"https://webflow.com"}'
In Python, with nothing but the standard library:
import json, urllib.parse, urllib.request
API = "https://eucomply-scan.mahope-eeb.workers.dev"
def scan(url):
qs = urllib.parse.urlencode({"url": url})
with urllib.request.urlopen(f"{API}/scan?{qs}") as response:
return json.load(response)
report = scan("webflow.com")
print(report["platform"], report["score"])
for name, check in report["checks"].items():
print(("PASS" if check["pass"] else "FAIL"), name, "-", check["label"])
CORS is open, so this also works straight from a browser — no proxy needed. Verified: a preflight for GET returns Access-Control-Allow-Origin: *.
What comes back
A 200 with one JSON object. Every field, measured against the running service on 28 September 2026:
| Field | Type | What it holds |
|---|---|---|
url | string | The URL as resolved, after redirects. |
platform | string | Detected platform, e.g. Webflow, Shopify, WordPress. |
scannedAt | string | ISO 8601 timestamp of the scan. |
durationMs | number | How long the scan took. |
score | object | passed, total, pct over all nine checks. |
checks | object | Nine entries, keyed by check id. Each has pass, warn, label, detail, fix. |
disclaimer | string | Sent with every response. Keep it visible — it is the line that keeps this from being read as legal advice. |
A check that fails is the useful part, so here is one returned verbatim, trimmed to its first three fields:
"consent_mode_v2": {
"pass": false,
"warn": false,
"label": "No Google Consent Mode v2 detected",
"detail": "No Consent Mode v2 signals found. Since March 2024, Google requires Consent Mode v2 for ad personalization in the EEA. Without it, Google Ads conversion tracking may be restricted.",
One honest difference from the CLI
The JSON score divides passed checks by all nine, including the four that only apply to some sites — cookies, programmatic advertising, Google Ads, DORA. A site that did everything right for its type can still show a middling number.
The CLI is the build that splits this, and it tells you which checks it did not count and why. If the number matters more than the JSON shape, run the CLI. This API is currently served by an older build of the same engine than the one in the repository, so it does not return that split yet.
The other three endpoints
All of them are unauthenticated and CORS-open, same as /scan.
GET/stats
Returns scans and the twenty most-scanned hosts. We exclude our own smoke-test domains from the list.
Do not read this as a user count. The counter is incremented by our own automated test runs as well as by visitors — measured 28 September 2026, roughly three quarters of the total came from automated runs against reserved IP addresses. It is a liveness check, not a popularity claim.
POST/subscribe
Stores an email for compliance updates. Body: email, optionally url, score, source.
Returns 400 for a malformed address and 422 for a test or disposable domain, so test runs never land in the list.
GET/config
Public runtime configuration. The checkout URLs it carries are empty in production, which is deliberate: the site hardcodes the contract links and the payment pages are not driven from a worker variable.
Limits and errors
No key, no account, and one limit that matters: 10 requests per minute per IP, counted server-side.
| Status | When you get it | Body |
|---|---|---|
400 | No usable url parameter | {"error": "Please provide a valid public http(s) URL, e.g. ?url=example.com"} |
404 | Any path other than the four above | {"error": "Not found. Use GET /scan?url=example.com"} |
422 | /subscribe with a test address | {"error": "Test address rejected."} |
429 | More than 10 requests in a minute from one IP | {"error": "Rate limit reached. Try again in a few minutes."} |
502 | The target site could not be read | {"error": "Scan failed: ..."} |
A 502 means the site you asked about failed, not that the API did. It carries the underlying message, so read it before retrying — a host behind a bot wall will keep failing, and hammering it will not help.
What this API does not do
It is a single request for a single site at a single moment. It keeps no history, runs nothing on a schedule, sends no webhooks, and takes no payment. If you need any of those, the current paid product is the WordPress plugin: it re-scans your own site once a day, keeps the last 12 scans, emails you when a check breaks, and generates an HTML report you can send to a client. Hosted monitoring is not part of it.
The nine checks are technical signals read from the served HTML. The DORA check looks for public page-text markers and is not an assessment of anything.
Need history, scheduling and a report?
The API above stays free. Pro is a WordPress plugin licence: daily re-scans in your own WordPress, the last 12 scans on record, an email when a check breaks, and an HTML report from the latest scan. It unlocks the editable HTML document starters too.
Buy Pro — $79/year per website →