# SEO Audit + EU Compliance Risk (`readystack/seo-compliance-audit`) Actor

One real-browser visit per page: on-page SEO audit (12 checks, 0-100 score) plus EU compliance risk - cookies and trackers set before consent (24 rules), WCAG 2.1 AA markup errors, security headers, and the domain's SPF/DMARC and security.txt. One row per page, with lead signals.

- **URL**: https://apify.com/readystack/seo-compliance-audit.md
- **Developed by:** [ReadyStack](https://apify.com/readystack) (community)
- **Categories:** SEO tools, Lead generation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 page auditeds

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

## SEO Audit + EU Compliance Risk

Give it a list of pages. Each page is opened once in a fresh real browser (no stored consent, nothing clicked), and one dataset row comes back with an on-page SEO audit **and** the EU compliance risk the same page carries: which cookies and trackers were already there before the visitor agreed to anything, which WCAG 2.1 AA markup errors it has, which security headers are missing, and whether its domain publishes SPF, DMARC and security.txt. Every finding names the rule and the text behind it.

**Who it is for:** SEO and web agencies that already sell site audits and want the compliance findings in the same report; lead-generation teams qualifying lists of company websites (the `lead_signals` column); site owners with EU visitors.

### What one row gives you

| Layer | What is checked | Rule set (version, dated) | Basis |
|---|---|---|---|
| SEO | 12 on-page checks (17 finding types) and a 0-100 score: title, meta description, h1, canonical, meta robots / X-Robots-Tag noindex, viewport, Open Graph, JSON-LD, thin content, redirect chain, HTTPS, Sitemap line in robots.txt or /sitemap.xml | this actor | Google Search Central documentation; sitemaps.org; ogp.me |
| Cookies before consent | 24 pre-consent rules (Google Tag Manager, Meta pixel, Hotjar, Clarity, LinkedIn, TikTok, Google Fonts, YouTube and Maps embeds, chat widgets, session replay ...) over the rendered page and every request the browser made; cookies set before any click, third-party cookies, cookies past the 13-month cap, and the fine ceiling for France, Spain or Italy | `consent-audit-eu` 0.1.4 (24 rules, 2026-09-20) + `pre-consent-cookie-audit-2026` 1.0.1 (2026-09-21) | ePrivacy Directive art. 5(3); CNIL cookie recommendation; Spain LSSI art. 39; Italy Garante |
| Accessibility | 24 WCAG 2.1 AA markup checks over the rendered page (page language, alt text, frame titles, blocked zoom, icon-only links and buttons, click handlers the keyboard cannot reach, autocomplete on identity fields ...), each with its occurrence count | `wcag21-aa-legal-baseline-audit` 0.1.5 (24 rules, 2026-09-25) | WCAG 2.1 AA as named by EN 301 549 (European Accessibility Act, applies since 2025-06-28) |
| Security headers | 4 presence checks (HSTS, Content-Security-Policy, X-Content-Type-Options, frame protection) plus 28 rules for header lines that do nothing or open a hole (retired headers, bare CSP keywords, script-src \* or data:) | `security-headers-csp-lint` 0.1.5 (28 rules, 2026-09-20) | OWASP Secure Headers Project; RFC 6797; W3C CSP Level 3 |
| Mail authentication (domain) | published SPF, DMARC (with tree walk) and optional DKIM records against 19 record rules, plus 3 presence rules (SPF, DMARC, DKIM) from bulk-sender-lint; once per domain per run | `spf-dmarc-record-lint` 1.1.4 (19 rules, 2026-09-20) | RFC 7208; RFC 9989/9990 (DMARCbis); Gmail, Yahoo and Outlook.com bulk-sender requirements |
| security.txt (domain) | /.well-known/security.txt fetched as RFC 9116 says a reader must and checked against 13 rules; a missing file is reported as info on an ordinary website | `security-txt-cra-lint` 1.0.6 (13 rules, 2026-09-20) | RFC 9116; EU Cyber Resilience Act Annex I Part II(5) where the CRA applies |

### Columns worth knowing

- `summary`: one line per page, ready for a spreadsheet.
- `seo_score`: 100 minus 12 per SEO error, 5 per SEO warning and 1 per SEO info finding (floor 0). It scores the SEO layer only.
- `compliance_risk`: `high` when trackers or tracker cookies were present before consent, or the domain receives mail without SPF or DMARC; `medium` when another compliance layer has an error; otherwise `low`. It is a triage label computed from the findings, not a legal assessment.
- `lead_signals`: short flags such as `trackers_before_consent`, `no_dmarc`, `dmarc_not_enforced`, `accessibility_errors`, `no_hsts`, `seo_noindex`, `seo_no_sitemap`, for filtering a list of sites.
- `issues`: every finding with `layer`, `rule_id`, `severity`, `message`, `basis` and, where useful, `evidence`, `occurrences` and `fix`. The dataset has a second view, *All findings*, with one finding per line.

### Tips

- Sites show EU visitors a different banner and set different cookies. For an EU view, set the proxy to Apify Proxy with an EU country.
- Ten pages of the same site cost ten page events but only one DNS and security.txt lookup.
- The cookie layer does not click the banner. It reports what exists before any choice is made, which is the state the law regulates.

### Real run (2026-09-25, from a US server, no proxy)

| Page | SEO score | Compliance risk | Cookies before consent | Tracker sources before consent | WCAG error rules | DMARC | Signals |
|---|---|---|---|---|---|---|---|
| https://www.bbc.com/ | 89 | high | 18 | 2 | 0 | published | trackers\_before\_consent, no\_csp |
| https://www.lemonde.fr/ | 100 | medium | 9 | 0 | 0 | published | no\_security\_txt |
| https://www.spiegel.de/ | 95 | medium | 3 | 0 | 2 | published | accessibility\_errors, no\_security\_txt |

### Input

```json
{
  "urls": [
    "https://www.bbc.com/",
    "https://www.lemonde.fr/"
  ],
  "domainChecks": true,
  "country": "France (CNIL)"
}
```

### Pricing

Pay per event: **$0.01 per page audited** (`page-audited`), plus Apify's small run-start fee. You pay only for pages that loaded. A page that fails to load, times out or answers HTTP 4xx/5xx comes back as a row with `status: error` and is not billed. The domain checks are included in the page price. If you set a maximum cost per run, the actor stops before it.

### What it does not do

- Whether the banner has a reject-all button on the first layer, names every purpose, and actually stops trackers after a refusal (listed per row in `not_measured`)
- Keyboard and screen-reader testing: the accessibility layer reads markup only, so a clean result is not a WCAG conformance claim
- Page speed and Core Web Vitals
- Backlinks, rankings, keyword research and content quality
- Pages behind a login

### Disclaimer

Automated check of what is publicly served, against the rule text cited in each finding. It is not legal advice and not a compliance certification.

Made by [ReadyStack](https://getreadystack.com/?ref=apify). Free web versions of each check: https://getreadystack.com/tools?ref=apify

# Actor input Schema

## `urls` (type: `array`):

Pages to audit, one per line (https://example.com/ or example.com). Each page is opened once in a fresh browser with no stored consent and nothing is clicked. Each audited page is one billable event; a page that fails to load or answers with HTTP 4xx/5xx is not billed.

## `domainChecks` (type: `boolean`):

Looks up the SPF and DMARC records of each page's domain and reads its security.txt. Runs once per domain per run, so ten pages of one site cost one lookup. Included in the page price.

## `country` (type: `string`):

Selects the fine ceiling reported next to the cookie findings (France EUR 20,000 CNIL simplified procedure; Spain EUR 150,000 LSSI art. 39; Italy EUR 300,000 Garante).

## `dkimSelectors` (type: `array`):

DKIM keys can only be found by selector name (for example google, selector1, s1). Leave empty to skip DKIM.

## `waitSecs` (type: `integer`):

How long to leave the page open without clicking, so late tags and pixels can fire.

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

How long to wait for the page to start rendering.

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

Each page gets its own clean browser context.

## `dnsServers` (type: `array`):

Resolver IPs to query instead of the system resolver, e.g. 1.1.1.1 and 8.8.8.8.

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

Optional. Sites show EU visitors a different banner and set different cookies. Pick Apify Proxy with an EU country to audit what an EU visitor gets.

## Actor input object example

```json
{
  "urls": [
    "https://www.bbc.com/",
    "https://www.lemonde.fr/",
    "https://www.spiegel.de/"
  ],
  "domainChecks": true,
  "country": "France (CNIL)",
  "waitSecs": 5,
  "navigationTimeoutSecs": 45,
  "maxConcurrency": 2,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (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 = {
    "urls": [
        "https://www.bbc.com/",
        "https://www.lemonde.fr/",
        "https://www.spiegel.de/"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("readystack/seo-compliance-audit").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 = {
    "urls": [
        "https://www.bbc.com/",
        "https://www.lemonde.fr/",
        "https://www.spiegel.de/",
    ],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("readystack/seo-compliance-audit").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 '{
  "urls": [
    "https://www.bbc.com/",
    "https://www.lemonde.fr/",
    "https://www.spiegel.de/"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call readystack/seo-compliance-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,readystack/seo-compliance-audit"
        }
    }
}
```

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/IRbmKA3h63BDmIaaW/builds/4ujr182kdHhj09zmr/openapi.json
