# Website Audit: SEO, GEO, Security, WordPress & Speed (`neocraft/website-audit-all-in-one`) Actor

Passive website audit for humans and AI agents: page-by-page SEO, AI visibility (GEO), HTTPS & security headers, SPF/DMARC/DKIM, broken links, PageSpeed, accessibility, WordPress/Shopify/CMS vulnerabilities. JSON + HTML/PDF report, EN/FR.

- **URL**: https://apify.com/neocraft/website-audit-all-in-one.md
- **Developed by:** [Neocraft](https://apify.com/neocraft) (community)
- **Categories:** SEO tools, AI, Developer tools
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $490.00 / 1,000 site audits

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

## Website Audit: SEO, GEO, Security, WordPress & Speed

**One run, one report:** SEO page by page, AI visibility (GEO), security and e-mail authentication, WordPress / Shopify /
CMS vulnerabilities, accessibility and speed — passive, explained in plain language, with a stable JSON schema for AI agents.

Audit any website in **one run** and get **one clear report**: security (HTTPS, certificate, security headers, cookies,
mixed content), e-mail authentication (SPF, DMARC, DKIM, MTA-STS), SEO, **accessibility**, **AI visibility (GEO)**,
broken links, **performance on mobile and desktop**, technology stack, **outdated or vulnerable WordPress core, plugins
and themes** (CVE, CVSS, fixed-in version, **actively exploited** flag from CISA KEV), and **Shopify store checks**
(theme, apps, product structured data, consent, speed cost of apps). **Other CMS and frameworks (v6):** Drupal, Joomla,
PrestaShop (+ modules), Magento 1/2 (Adobe Commerce), TYPO3, Symfony, Laravel, Next.js, Nuxt and hosted builders (Wix,
Squarespace, Webflow): detection, version when the platform shows it, **end-of-life** branches, **known vulnerabilities**
(with fixed-in version and actively-exploited flag) and **exposed debug tools** (Symfony profiler, Laravel
Ignition/Debugbar, Magento developer mode). Every finding has an **effort** and **impact**
estimate, and the report starts with **quick wins**.

Built for **humans and AI agents**: every run returns one JSON item per site with a stable, documented schema
(`summary`, `topPriorities`, `quickWins`, `scores` with breakdowns, `findings` with fix steps, effort and impact) plus a
simple HTML report.

**Monitoring & agencies (v5):** each audit is compared with the previous one (**what changed: new, fixed, worse,
improved**, score deltas), many sites per run give a **portfolio overview + CSV**, reports can be **white-labelled**
(your agency name, logo, colour, footer), exported as **PDF**, and **alerts** go to your webhook or Slack when something
new and serious appears or a domain/certificate is about to expire.

Every problem comes with a **plain-language explanation** and a **step-by-step fix guide written for
non-technical site owners** (with WordPress-specific steps when relevant), in **English or French**.

> Passive and polite: the Actor only reads what the website shows publicly. It never exploits, brute-forces,
> fuzzes or "tests" vulnerabilities, never logs in, never enumerates users. The only exception is the **opt-in**
> `exposedFilesCheck` (off by default, for sites you own or are authorized to test): 7 fixed, single requests for
> files that must never be public, validated by content signature, contents never stored.
> Only public web addresses are contacted: URLs, links, redirects, webhooks or logo URLs that point to private,
> local or cloud-metadata addresses (or to non-web ports and schemes) are refused.

### Quick start

1. Paste one or more website addresses in **Start URLs** and press **Start** (defaults are fine).
2. Open the **Output** tab: the HTML report (`OUTPUT`), the dataset (one `site` item per website + one `page` item per
   crawled page) and, if asked, `OUTPUT.pdf`.

Minimal input (API / AI agents):

```json
{ "startUrls": [{ "url": "https://example.com" }] }
```

Typical input — French report, 10 pages (no extra-page charge), PDF, JavaScript rendering only where needed:

```json
{
  "startUrls": [{ "url": "https://example.com" }],
  "language": "fr",
  "maxPagesPerSite": 10,
  "renderJs": "auto",
  "pdf": true
}
```

Agency / monitoring input — several sites, history and alerts, white-label report:

```json
{
  "startUrls": [{ "url": "https://client-a.com" }, { "url": "https://client-b.fr" }],
  "maxPagesPerSite": 30,
  "monitorId": "weekly-clients",
  "alerts": { "slackWebhookUrl": "https://hooks.slack.com/services/...", "minSeverity": "high" },
  "agencyName": "My Agency",
  "brandColor": "#0a7cff",
  "pdf": true
}
```

**Main output fields** (`type: "site"`): `summary`, `scores` (overall + per category with breakdown), `topPriorities`,
`quickWins`, `findings[]` (id, severity, explanation, step-by-step fix, effort, impact), `pageAudit` (per-page scores,
top issues, duplicates), `changes` (vs. previous run), `rendering`, `limitations`, `billing`. Per-page items
(`type: "page"`): `pageUrl`, `overallScore`, `categoryScores`, `issues`, `meta`, `headings`, `content`, `technical`,
`links`, `images`, `accessibility`, `performance`, `rendering`. Full schema in the *Output* section below.

### What it checks

| Category | Checks |
|---|---|
| **Security** | HTTPS availability, HTTP→HTTPS redirect, redirect chains, SSL/TLS certificate validity/expiry/protocol, HSTS, CSP, clickjacking protection (X-Frame-Options / frame-ancestors), X-Content-Type-Options, Referrer-Policy, server version disclosure, **cookie flags (Secure, HttpOnly, SameSite) on every crawled response** (names and flags only, never values), **mixed content** split into active (scripts, styles, frames) and passive (images, media), **forms posting to http://**, **security.txt** (RFC 9116, info only), **outdated front-end JavaScript libraries** (jQuery, jQuery UI, Bootstrap, Lodash, Moment, AngularJS, Handlebars, Underscore, DOMPurify) with known vulnerabilities from [OSV.dev](https://osv.dev) |
| **Exposed files (opt-in)** | `exposedFilesCheck` = true only: `/.git/HEAD`, `/.env`, `/wp-config.php.bak`, `/backup.zip` (HEAD only), `/.DS_Store`, `/phpinfo.php`, `/wp-content/debug.log` (WordPress). Each hit must match a content signature (e.g. `.git/HEAD` starts with `ref:`, `.env` is mostly `KEY=VALUE` lines, `.DS_Store` has the `Bud1` magic; HTML soft-404 pages never count; a random `.zip` name is requested to rule out catch-all routes). Output: a redacted one-line proof, never the content |
| **PHP** | PHP version read from `X-Powered-By` / `Server` headers, compared with php.net branch support dates at run time: end-of-life, end-of-life within 6 months, missing patch release; exposing the version is flagged as a low-severity information leak with a fix guide (`expose_php = Off`, hosting panel, `.htaccess`, nginx) |
| **Domain (RDAP)** | Registry data via RDAP (IANA bootstrap, e.g. AFNIC for .fr): expiry date (alert < 60 / 30 / 15 days), registrar, creation date, transfer lock, DNSSEC (DS lookup as fallback), name servers. **No registrant personal data is ever output.** TLDs without RDAP are reported as a limitation, without penalty. Shown with the certificate and PHP support dates in an **"Expiry risks"** block of the HTML report |
| **E-mail (DNS)** | MX (incl. null MX), SPF (presence, multiple records, `+all`/`?all`, **10-DNS-lookup limit counted recursively = permerror**, includes pointing to domains without SPF), DMARC (presence, policy, reports, inheritance for sub-domains), DKIM on 20 common selectors (with wildcard detection; "not found" is reported as *inconclusive*), **MTA-STS** (`_mta-sts` TXT + HTTPS policy: mode, MX patterns vs real MX), **TLS-RPT** (`_smtp._tls`), **BIMI** (`default._bimi`, info only) |
| **SEO** | Titles, meta descriptions (missing/length/duplicates), H1, canonical, noindex, `lang`, mobile viewport, Lighthouse mobile usability (viewport, touch-target size), image alt texts, OpenGraph, hreflang, robots.txt, XML sitemap, pages returning errors. Problems repeated on almost every page (missing H1, skipped heading levels) are reported **once as a template problem** |
| **AI visibility (GEO)** | robots.txt rules for AI crawlers (GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, Claude-SearchBot, PerplexityBot, Google-Extended…); **Content Signals** (`search` / `ai-input` / `ai-train`, incl. Cloudflare's managed robots.txt) explained; **CDN AI-blocking advice** (Cloudflare & co. can block AI bots before robots.txt matters — we never spoof bot user agents, so this is advice); **snippet controls** (`nosnippet`, `max-snippet`, `data-nosnippet`, `noarchive`, `X-Robots-Tag`); **JSON-LD quality per type** (Organization/LocalBusiness name/logo/sameAs, WebSite, Article/BlogPosting author + dates, Product/Offer price/currency/availability, FAQPage, HowTo, BreadcrumbList); **trust signals** (about/contact links, authors, visible dates, outbound sources); **freshness** (sitemap `lastmod`, page dates); **answer-friendly structure** (question headings, lists/tables, summary near the top); **Bing signals** (verification meta, `BingSiteAuth.xml`, IndexNow hints — Bing feeds Copilot and ChatGPT search); `llms.txt` (low weight, honestly labelled as not officially used yet); content visible without JavaScript. Scored with a **pillar breakdown** |
| **Links** | Broken internal and external links on a limited crawl (anti-bot answers are reported as *inconclusive*, not broken) |
| **Performance** | Google PageSpeed Insights for **mobile and desktop in parallel**: lab score, LCP/CLS/TBT/FCP/SI, real-user Core Web Vitals (LCP, INP, CLS at p75, page or origin), side by side; third-party cost; server response time, compression |
| **Shopify** | Passive detection (headers, cookies, `Shopify.theme`, CDN), theme name/version (custom vs Theme Store, Dawn copies that no longer get updates), **apps detected from scripts** (own rule list of ~60 apps + theme app embeds), **Product/Offer structured data** on a few product pages crawled first, product/collection SEO basics, canonical of `/collections/…/products/…`, cookie-consent presence, **speed cost of apps** (Lighthouse third-party main-thread time and size). The platform itself is patched by Shopify (no core vulnerability list); headless Shopify storefronts are recognised and not judged with theme checks |
| **Technologies** | ~70 hand-written rules: CMS, e-commerce, site builders, JS frameworks, analytics, consent tools, CDN/hosting, web servers |
| **WordPress** | Core version (meta generator, RSS feed, `wp-emoji` script, `wp-login.php` asset versions, old-style `readme.html`, `wp-includes` `?ver=`), plugins and themes from public assets, their versions (HTML comments/generators, `readme.txt`, theme `style.css`, `?ver=`), latest versions, plugins closed on WordPress.org, and **known vulnerabilities** from a nightly database. Each vulnerability carries its **CVE id(s), CVSS score and severity, fixed-in version and whether it is in the CISA Known Exploited Vulnerabilities catalog (KEV)**; an actively exploited flaw on a confirmed/probable version becomes a **critical "fix today"** finding. Findings say **"update to X or newer"**. Premium/custom components (not on WordPress.org) are listed as *latest version unknown — check your vendor account* |
| **Accessibility** | Lightweight, no browser: per crawled page, form fields without label, links and buttons without accessible name, vague link texts ("click here", "read more", "lire la suite"…), iframes without title, skip link / `<main>` landmark, zoom blocked in the viewport, valid `lang`; plus **Lighthouse accessibility audits (axe-core)** from PageSpeed when available, incl. **colour contrast**. Image alt texts, missing `lang` and heading order are reported once in SEO/GEO and counted in this score too. Online shops get an information note on the **European Accessibility Act** (applies since 28 June 2025 to many consumer e-commerce services in the EU; micro-enterprises exempt for services) — *information, not legal advice; not a WCAG conformity audit* |
| **CMS / frameworks (v6)** | Own passive rules (no GPL fingerprint list) on headers, cookie **names**, generator tags and HTML of every crawled page: **Drupal** (X-Generator, `drupal-settings-json`, `/core/misc/`, Drupal 7 `Drupal.settings`, `SESS…` cookie), **Joomla** (generator, `/media/system/js/`, `joomla-script-options`, `/media/jui/` = Joomla 3 hint), **PrestaShop** (`prestashop` JS object, `/modules/ps_*`, `PrestaShop-…` cookie, 1.6 theme variables) **and the modules referenced by the pages**, **Magento 2 / Adobe Commerce** (`/static/version…/frontend/`, `x-magento-init`, `mage-cache-*` cookies, `X-Magento-*` headers), **Magento 1** (`/skin/frontend/`, `Mage.Cookies`), **TYPO3** (generator, "powered by TYPO3" comment, `typo3temp`), **Symfony**, **Laravel** (`laravel_session`, Laravel's encrypted-cookie format — tested, never stored), **Next.js** (`__NEXT_DATA__` + buildId, `self.__next_f`, `x-nextjs-*`), **Nuxt** (`__NUXT__`, `__NUXT_DATA__`; Nuxt 2 markup), **Wix / Squarespace / Webflow**. **Version** only when the platform itself shows it (generator with version, Drupal core `?v=`, Laravel debug page), plus **one** request per platform for its well-known version file **only if robots.txt allows it** (Drupal 7 `CHANGELOG.txt`, Joomla `administrator/manifests/files/joomla.xml`, Magento `/magento_version` = branch only); HTML soft-404 answers never count. Then: **known vulnerabilities** for that exact version (CVE, severity, **fixed-in version**, CISA KEV → critical "fix today"), **end of life** of the branch (endoflife.date; Drupal 7, Joomla 3/4, Magento 1 and 2.3, Nuxt 2, PrestaShop 1.6…), outdated patch level, "version not visible" with the **recent high/critical advisories worth checking**, PrestaShop modules with published advisories (**no fix → high**, otherwise "check your version"). **Debug exposure** seen during the normal crawl (no debug URL is ever requested): Symfony `X-Debug-Token`/toolbar/`sf_redirect`/`dump()`, Laravel Ignition/Whoops/stack traces on error pages, Laravel Debugbar, Magento `X-Magento-Cache-Debug` (developer mode). Hosted builders get a positive "platform patched by the vendor" note and the usual SEO/GEO/speed checks |
| **WordPress hardening** | A **positive finding** when versions are well hidden; otherwise a list of where versions leak (generator, RSS, `?ver=`, `readme.html`, plugin `readme.txt`, `X-Powered-By`) with a beginner guide (security-plugin option, `.htaccess` and nginx snippets). Always stated: hiding is a bonus, **updating is the real fix** |

#### Fewer false alarms by design

Each finding carries a **confidence level**: `confirmed`, `probable`, `guessed` or `inconclusive`.
A vulnerability is reported as such **only when the component version is confirmed or probable**.
When the version is only guessed from a `?ver=` parameter, the report says *"may be vulnerable — please check
the version in your dashboard"* with a capped severity. Implausible guesses (e.g. a bundled library version) are discarded.

### Per-page SEO audit (one record per page)

Besides the site item, every crawled page gets its own dataset item (`"type": "page"`, input `pageRecords`, on by
default): an overall score 0-100, nine category scores and a ranked issue list with fix hints.

| Per-page feature | This Actor | Typical SEO auditors |
|---|---|---|
| One record per page with overall score + category scores (meta tags, headings, content, technical, schema, performance, links, images, accessibility) | Yes | Yes |
| Non-indexable pages (4xx/5xx, noindex meta or `X-Robots-Tag`, canonicalized) capped at 39/100 | Yes | Often |
| Issues as sentences (critical / warnings / info) + structured list (id, priority 1-5, estimated impact, fix hint, pages affected) | Yes | Yes |
| Title / description length in characters **and** approximate SERP pixel width (Arial width table) | Yes | Some |
| Multiple `<title>` / description, Twitter card (with Open Graph fallback), charset, canonical self-reference, canonical outside `<head>` | Yes | Some |
| `<head>` closed early (an element such as `<img>` before meta/link tags, which browsers then move to `<body>`) | Yes | Rare |
| Robots: every `robots`/`googlebot` meta + `X-Robots-Tag` combined, most restrictive wins | Yes | Some |
| Full H1-H6 tree with texts, missing / multiple H1, skipped levels | Yes | Yes |
| Main-content words (nav, header, footer, aside, cookie banners removed), sentences, readability per language (Flesch EN, Kandel-Moles FR, Amstad DE, Fernández-Huerta ES, Franchina-Vacca IT, Douma NL, Martins PT), keyword density, title/H1 keyword consistency | Yes | Some |
| Links per page: internal/external, unique, nav/content/footer, nofollow; first 10 links checked (HEAD then GET), redirect targets, 401/403/429/challenge reported as *unverified*, not broken | Yes | Yes |
| Images: alt coverage (decorative `alt=""` / `role=presentation` counted apart), real file size (HEAD, ranged-GET fallback), > 200 KB, format, broken images | Yes | Yes |
| Site aggregates: average score, category averages, top 20 issues by pages hit, score distribution, crawl coverage (indexable, non-indexable breakdown, turned away by anti-bot/login/429), duplicate titles and descriptions (indexable pages only) | Yes | Some |
| Security, e-mail (SPF/DMARC), domain, AI-visibility (GEO), WordPress/Shopify/CMS vulnerabilities in the same run | Yes | Rarely |
| JavaScript rendering (headless Chromium): `renderJs` auto/always/never, rendered vs raw content share (content invisible to most AI crawlers), rendered links/images in the page checks, lab LCP/CLS, consent-banner detection, optional full-page screenshots | **Yes** (v3.5) | Often |
| Intentional noindex (account, cart, checkout, login, search, thank-you… in EN/FR/DE/ES/IT/PT/NL, WooCommerce/Shopify/PrestaShop/Magento paths): not penalised, excluded from averages; "in sitemap but noindex" flagged as a conflict | Yes | Rarely |
| HTML report: one collapsible block per page (issues + fix, meta with chars/pixels, canonical, robots, H1-H6 tree, content, links, images, performance, rendering); PDF: compact per-page section | Yes | Some |

**Scoring.** Overall = average of the enabled category scores, links excluded (a broken outbound link should not sink
a page); pages that cannot be indexed are capped at 39, except pages where noindex is appropriate (account, cart, checkout,
login, search, thank-you…): those are not capped, are excluded from the site averages and are counted as
`crawlCoverage.intentionallyExcluded`. A noindex on the home page, a product/category/blog page or a page listed in the
sitemap is critical; other noindex pages get a "check it is intended" warning. Each module can be turned off (`auditMetaTags`,
`auditHeadings`, `auditContent`, `auditTechnical`, `auditPerformance`, `auditSchema`, `auditLinks`, `auditImages`,
`auditAccessibility`): its score becomes `null` and its issues are not reported.

**Performance per page.** Google PageSpeed Insights (mobile + desktop) runs on the home page and, with `pagespeedPages`
(1-10), on the first N crawled pages; other pages get a lightweight score from the HTML response time and document size.

**JavaScript rendering (v3.5).** `renderJs` = `auto` (default) renders in headless Chromium only the pages whose raw
HTML looks JavaScript-dependent (empty app root such as `<div id="root"></div>`, Next/Nuxt/Angular shell, almost no
text and almost no links with many scripts) — up to `maxRenderedPages` per site (default 5, max 20). `always` renders
the first pages, `never` keeps the raw-HTML audit only. For a rendered page the rendered DOM is audited (content,
headings, meta, links and images, which then go through the page checks), and the report adds the share of the text and
internal links already present in the raw HTML (below 50 % → GEO finding *content invisible to non-JS crawlers (most AI
crawlers)*), lab LCP/CLS (PerformanceObserver, indicative only: all browser traffic goes through the audit's guarded
client), consent-banner detection, and with `screenshots: true` a full-page PNG (max 8000 px) in the key-value store.
Security: the browser never connects by itself — every request is checked (http/https only, public IPs after DNS
resolution) and fetched by the same SSRF-guarded client as the crawl; video/WebSocket requests are refused and service
workers blocked. Pages are rendered one at a time with the same delay as the crawl. Memory: 1024 MB is enough for
`auto`; 2048 MB is recommended for `always` with many pages or screenshots.

**Politeness.** Link and image checks go through the same guarded client as the crawl (public addresses only, every
redirect re-checked, one request per second per host by default), are cached per URL and capped: 10 links and 20
images per page, 120 links and 80 images per site, 600 links and 400 images per run.

### Input

| Field | Default | Description |
|---|---|---|
| `startUrls` | – | Websites to audit (one entry per site). A sub-path such as `https://site.com/blog/` limits the crawl to that section. |
| `language` | `en` | `en` or `fr` — language of explanations and fix guides |
| `maxPagesPerSite` | 20 | Pages crawled per site (1–200) |
| `maxLinkChecks` | 30 | Extra links checked per site |
| `pagespeed` | true | Query Google PageSpeed Insights |
| `pagespeedStrategies` | `["mobile","desktop"]` | Devices tested in parallel; performance score = 60 % mobile + 40 % desktop |
| `pagespeedApiKey` | – | Optional free Google API key (secret field, never logged). Used first when given; otherwise the Actor's shared key covers up to 50 sites per run and further sites skip PageSpeed with a `pagespeed-cap` note |
| `checkDomain` | true | Domain registration checks via RDAP (needs `checkSecurity`) |
| `checkSecurity`, `checkEmail`, `checkSeo`, `checkGeo`, `checkLinks`, `checkPerformance`, `checkTechnologies`, `checkWordpress`, `checkShopify`, `checkAccessibility` | true | Enable/disable each module |
| `checkPlatforms` | true | CMS / framework checks (v6, see above). At most one extra request per detected platform (robots.txt respected) |
| `exposedFilesCheck` | **false** | **Opt-in, only for sites you own or are authorized to test**: 7 fixed requests for files that must never be public (see above). Needs `checkSecurity`. Your opt-in takes precedence over robots.txt for these few paths; the rate limit still applies |
| `wpReadmeLookup` | true | Read `readme.txt` of plugins already referenced by the pages (max 15 requests) |
| `liveVulnLookup` | true | Look up components missing from the nightly DB on api.wordpress.org / wpvulnerability.net, JS library vulns on OSV.dev, and the CISA KEV feed if the DB has no KEV data (off = small built-in JS table only) |
| `requestIntervalSecs` | 1 | Delay between two requests to the same site (min 0.5) |
| `maxConcurrentSites` | 2 | Sites audited in parallel |
| `compareWithPrevious` | true | Keep a compact snapshot per site in the named key-value store `site-audit-history` **of your account** and add a `changes` block + a "What changed since <date>" section. `false` = no history read or written |
| `monitorId` | – | Label that keeps a separate history per client/project (agencies: one per customer) |
| `historyStoreName` | `site-audit-history` | Named store used for the history (change only to start a fully separate history) |
| `reportDetail` | `simple` | `simple` hides raw technical JSON in the HTML/PDF but keeps one line per finding: **how it was found + confidence + sources**; `technical` also shows the raw evidence. The dataset always has everything |
| `renderJs` | auto | `auto` / `always` / `never` — headless Chromium rendering (see above) |
| `maxRenderedPages` | 5 | Max pages rendered per site (1–20) |
| `screenshots` | false | Full-page PNG of each rendered page in the key-value store (linked from the report) |
| `pdf` | false | Also save `OUTPUT.pdf` (no browser; plain layout, Latin characters) |
| `agencyName`, `agencyLogoUrl`, `brandColor`, `contactEmail`, `footerText` | – | **White-label**: shown in the HTML/PDF reports; setting any of them removes our "generated by" line. Logo: HTTPS PNG/JPEG |
| `alerts` | – | `{"webhookUrl", "slackWebhookUrl", "minSeverity": "high", "onlyNew": true, "domainExpiryDays": 30, "sslExpiryDays": 14}` — see *Alerts* |

Example:

```json
{
  "startUrls": [{ "url": "https://www.example.com" }],
  "language": "en",
  "maxPagesPerSite": 20
}
```

### Monitoring: history and "what changed"

Schedule the Actor (Apify Console > **Schedules**, e.g. weekly) with the same `startUrls` (and `monitorId`): every run
compares each site with its previous audit and fills `changes`:

- `status`: `first-audit`, `compared`, or `not-comparable` (site unreachable in one of the two runs: nothing is
  compared and the stored audit is kept, so an outage does not look like "everything fixed").
- `new`, `fixed`, `worsened`, `improved`: findings (stable `id` + component/library/device key), `scoreDelta`,
  `categoryDeltas` (only categories evaluated in both runs), `trend` (last 30 scores), `notes` (caveats).
- Honest comparisons: findings from PageSpeed/Lighthouse are compared only when both runs had PageSpeed data; a
  per-page problem reported once as a "template problem" is the same problem; a note says when the number of crawled
  pages differs; lab-score changes are flagged `volatile` (Lighthouse varies 10-15 points between runs).

**Privacy:** the history lives in **your own Apify account** (runs always write to the account that runs the Actor),
and every record key also contains your Apify user id and the `monitorId`, so customers and clients never share data.
It stores scores, finding ids/titles/severities and expiry dates only — no page content. Delete the
`site-audit-history` store at any time to start over.

### Agencies: bulk mode, portfolio and white-label

Put all client sites in `startUrls` (one audit per site). With 2+ sites (or any white-label field) the run also
saves to the key-value store:

- `PORTFOLIO.json` — `{sites, averageScore, sitesWithCritical, sitesWithHigh, unreachable, rows[]}`, worst sites first;
- `PORTFOLIO.csv` — one row per site: score, grade, score delta, critical/high/medium/low counts, new/fixed findings,
  top issue, platform, domain/SSL days left (cells starting with `=+-@` are neutralised for spreadsheets);
- `PORTFOLIO.html` — the portfolio table alone (also shown at the top of `OUTPUT`).

Use one `monitorId` per client to keep histories apart. White-label fields replace our branding in HTML and PDF.

### Alerts (webhook, Slack, e-mail)

With `alerts.webhookUrl` and/or `alerts.slackWebhookUrl`, **one message per destination per run** is sent when at least
one site has: findings at or above `minSeverity` (with `onlyNew: true`, only those **new or worse since the previous
audit**; on a first audit, all current ones; Lighthouse lab-score changes are never alerted as "new"), or a domain
expiring within `domainExpiryDays` / a certificate within `sslExpiryDays`. Nothing is sent when nothing qualifies.

- Webhook: `POST` JSON `{"type": "website-audit.alert", "version": 1, "monitorId", "runId", "minSeverity", "onlyNew",
  "reportUrl", "sites": [{"domain", "url", "score", "grade", "scoreDelta", "reasons": [...]}]}`.
- Slack: an *Incoming Webhook* URL (`https://hooks.slack.com/...`, others are refused).
- Messages go **only to the URLs you give**, redirects are not followed, URLs are never logged (host only). Each item
  carries an `alerts` block and the run saves `ALERTS.json` (payload + delivery status).
- **E-mail:** use Apify **Integrations** on the Actor or on a saved task (Console > Integrations): e.g. the
  "Send email" integration / `apify/send-mail` Actor on *Run succeeded*, or Zapier / Make / n8n with the webhook
  payload. Combined with a **Schedule**, this is a weekly monitoring e-mail.

### PDF export

`pdf: true` saves `OUTPUT.pdf` (same content as the HTML report). It is drawn directly with
[fpdf2](https://github.com/py-pdf/fpdf2) — no browser, no system libraries (~55 MB of Python wheels in the image, about
0.5 s and up to ~50 MB of RAM for 10 sites locally; ~8-10 s on a 512 MB Apify run because of its small CPU share).
Plain layout; built-in PDF fonts cover Latin-1 only, so other characters are transliterated (`œ` → `oe`, `→` → `->`)
or replaced with `?`. Off by default because most callers are programs.

### Output (for programs and AI agents)

**One dataset item per site.** The format is described field by field in `.actor/dataset_schema.json`
(`schemaVersion: "3.3"`: 3.0 plus additive v4, v5 and v6 fields; field names and finding ids are stable, texts follow `language`).
Read in this order:

1. `summary` — score, grade, platform, counts per severity, one-sentence `headline`.
2. `topPriorities` — up to 5 objects `{rank, id, category, severity, confidence, device, title, firstStep, whoCanFix}`.
3. `quickWins` — up to 8 objects `{rank, id, category, severity, impact, effort, effortMinutes, title, firstStep, whoCanFix}`:
   high/medium impact, about 5 or 30 minutes of work (may be empty).
4. `scores` — `global`, `grade`, `categories` (only evaluated ones) and `breakdown` (`geo` pillars, `performance` per device,
   `accessibility` = passive vs Lighthouse).
5. `findings` — every finding with stable `id`, `severity`, `confidence`, `explanation`, `fixSteps`, `whoCanFixCode`
   (`you` / `host` / `developer`), `device` (PageSpeed findings), `effort` (`5min` / `30min` / `half-day` / `dev`),
   `effortMinutes`, `impact` (`high` / `medium` / `low`, null for positives) and raw `evidence`.
   Each finding also has `howDetected` (one sentence: data source/method) and `sources` (`[{name, url}]`: data providers,
   RFCs and up to 3 primary advisories).
6. `changes` (see *Monitoring*), `alerts` (if configured), `monitorId`.
7. `wordpress`, `shopify`, `platforms` (v6), `technologies`, `details` (raw facts per module), `pages`, `limitations`.
   `platforms.detected[]` = `{key, name, kind, confidence, evidence[], version, versionSource, versionConfidence, branch,
   eol {cycle, eol, latest, isEol}, latestInBranch, updateTo, vulnerabilities[] {cve[], severity, fixedIn[], kev, source,
   refs[]}, modules[] (PrestaShop), buildId (Next.js)}`, plus `primary`, `debugExposure[]` and `attribution[]` (licence lines
   of the data used — keep them if you republish). Finding ids: `platform_core_vuln`, `platform_core_vuln_exploited`,
   `platform_eol`, `platform_eol_soon`, `platform_outdated`, `platform_version_hidden`, `platform_version_exposed`,
   `platform_module_unfixed`, `platform_module_advisories`, `platform_hosted_managed`, `debug_symfony`, `debug_laravel`,
   `debug_magento_dev_mode`.

**Tips for AI agents:** read `summary` + `topPriorities` first; for monitoring read `changes.status`, `changes.new`
and `changes.scoreDelta`; for several sites read the `PORTFOLIO.json` record instead of every item; treat
`confidence` `guessed`/`inconclusive` as "ask the owner to verify"; never present a `volatile` change as progress.
For CMS/frameworks, `platforms.detected[].version == null` means the version is hidden: do not infer one; use
`platform_version_hidden` (advisories to check) and `eol` instead.

```json
{
  "schemaVersion": "3.0",
  "url": "https://www.example-store.com/", "finalUrl": "https://www.example-store.com/", "domain": "www.example-store.com",
  "summary": { "score": 83, "grade": "B", "platform": "Shopify", "pagesCrawled": 8, "critical": 0, "high": 0, "medium": 7,
               "low": 8, "info": 6, "positives": 1, "headline": "www.example-store.com: 83/100 (B). 0 critical, 0 high, 7 medium issue(s). ..." },
  "topPriorities": [{ "rank": 1, "id": "psi_score_low", "category": "performance", "severity": "medium", "confidence": "confirmed",
                      "device": "mobile", "title": "Mobile performance score: 50/100", "firstStep": "Compress and resize images...", "whoCanFix": "developer" }],
  "scores": { "global": 83, "grade": "B",
              "categories": { "security": 92, "email": 100, "seo": 68, "geo": 93, "links": 100, "performance": 53, "shopify": 86 },
              "breakdown": { "performance": { "devices": { "mobile": 50, "desktop": 57 }, "weights": { "mobile": 0.6, "desktop": 0.4 } },
                             "geo": [{ "pillar": "access", "weight": 30, "score": 100, "evaluated": true, "explanation": "...", "findings": [] }] } },
  "findings": [{ "id": "shopify_apps_heavy", "category": "shopify", "severity": "medium", "confidence": "confirmed", "device": null,
                 "title": "...", "explanation": "...", "fixSteps": ["..."], "whoCanFix": "You can do it yourself", "whoCanFixCode": "you",
                 "evidence": { "thirdPartyMainThreadMs": 744, "thirdPartyTransferKB": 1621 } }],
  "shopify": { "detected": true, "theme": { "schemaName": "Dawn", "schemaVersion": "10.0.0", "custom": true },
               "apps": [{ "name": "Klaviyo", "category": "E-mail & SMS marketing" }], "productSchema": { "pages": 3, "withProductSchema": 3 },
               "cookieConsent": ["Shopify privacy banner"], "speedImpact": { "source": "lighthouse-mobile", "top": [] } },
  "details": { "performance": { "devices": { "mobile": { "score": 50, "lab": { "LCP": "10.4 s" }, "field": { "INP": "150 ms (fast)" } }, "desktop": {} } },
               "geo": { "contentSignals": {}, "jsonLdProblems": [], "trust": {}, "freshness": {}, "answerStructure": {}, "bing": {} } },
  "limitations": [], "disclaimer": "..."
}
```

Dataset views: `overview` (one row per site), `findings` (one row per finding) and `changes` (one row per site).
Key-value store records: `OUTPUT` (self-contained HTML report for all sites), `OUTPUT.pdf` (if `pdf`),
`PORTFOLIO.json` / `PORTFOLIO.csv` / `PORTFOLIO.html` (2+ sites or white-label), `ALERTS.json` (if alerts).
`.actor/output_schema.json` links them.

### Scoring

Each category starts at 100 and loses points per finding (critical 40, high 20, medium 10, low 4, info 0), except:

- **Accessibility** = passive score (its own findings + image alts, `lang`, heading order) averaged 50/50 with the
  Lighthouse accessibility score when PageSpeed ran (Lighthouse-derived findings are then not penalized twice).

- **Performance** = 60 % mobile + 40 % desktop; per device the lower of the Lighthouse lab score and 100 minus the
  penalties of field Core Web Vitals (LCP, INP, CLS only) and server findings. Not scored without PageSpeed data.

- **AI visibility (GEO)** = weighted pillars: access for AI crawlers 30, structured data 20, answer-friendly structure 15,
  trust 15, freshness 10, Bing signals 5, llms.txt 5. Pillar penalties: critical 100, high 60, medium 35, low 15;
  advice-only items (missing llms.txt, no visible Bing verification) lower only their small pillar. Pillars without
  data (e.g. fewer than 2 content pages) are marked `evaluated: false` and left out.

The **CMS / framework** category (`platform`) is scored only when something was really assessed (an exact version, a
known end-of-life branch, or PrestaShop modules with advisories); a supported major with a hidden patch level stays
unscored. Debug-tool exposure counts in **security**.

The global score is a weighted average of the evaluated categories (security 25, WordPress 20, CMS/framework 20, SEO 20, Shopify 15,
e-mail 10, GEO 10, performance 10, accessibility 10, links 5), capped at 49 when a critical problem exists (e.g. an
actively exploited plugin vulnerability or an exposed `.env`).

**Effort / impact** are coarse planning estimates (time for a non-technical owner or their usual provider, including a
backup), not a quote: `5min` = a toggle or one DNS field, `30min` = a few settings or one update, `half-day` = several
items or a careful change, `dev` = needs a developer. Impact follows severity, with a few explicit rules (exposed
secrets, expiring domain, DMARC missing = high).

### Pricing

Pay per event, no subscription, platform usage included:

| Event | Price | Charged when |
|---|---|---|
| `site-audit` | **$0.49** | Once per website in `startUrls`, after its result is saved. Includes the first **10 pages**, every module, JavaScript rendering (up to `maxRenderedPages`), link/image checks, HTML + PDF reports. |
| `extra-page` | **$0.01** | Each crawled page beyond 10 (`maxPagesPerSite`). |

Examples: 10 pages = **$0.49** · default 20 pages = **$0.59** · 50 pages = **$0.89** · 200 pages = **$2.39**.
Your *maximum cost per run* is always respected: sites that would exceed it are not started. A site that cannot be
reached (or is refused as a private/internal address) is reported but **not charged**.

### Limitations (please read)

- Hard safety limits (not changeable by input): 200 pages per site, 20 rendered pages per site, 120 page links and 80
  images checked per site, 250 requests / 40 MB / 60 s per rendered page, 3 MB per HTML page, 0.5 s minimum delay
  between requests to the same host. JavaScript rendering runs in an isolated headless Chromium whose every request
  goes through the same public-address guard (no downloads, popups closed, WebSockets and service workers blocked).
- External, passive audit: it cannot see your server, database or files and **cannot prove a site is secure**.
- WordPress versions are detected from public files and can be wrong; always check your dashboard before acting.
  Sites that hide versions get an *inconclusive* note instead of an alarm.
- DKIM can only be checked on common selector names.
- PageSpeed scores vary between runs (10-15 points on mobile is common); without your own API key Google may refuse the request.
- CDN-level AI-bot blocking (Cloudflare "Block AI bots", AI Crawl Control...) cannot be verified without pretending to be
  those bots, which this Actor never does: it is reported as advice to check the dashboard.
- Bing verification by DNS or Search Console import and IndexNow keys are invisible from outside (advice only).
- GEO trust/structure/freshness signals are heuristics on the crawled sample (header/nav/footer excluded); they are
  hints, not a ranking guarantee.
- Shopify apps are recognised only when they load code on the storefront and match our rule list; apps without
  storefront code (back-office) are invisible. Cookie banners loaded later by JavaScript may be missed (inconclusive).
- Accessibility: automated checks find only part of the problems (roughly a third); no keyboard/screen-reader test,
  colour contrast only through Lighthouse (home page, one device). This is not a WCAG/RGAA conformity audit and the
  European Accessibility Act note is not legal advice.
- KEV ("actively exploited") is matched by CVE id: vulnerabilities without a CVE cannot be flagged. JavaScript library
  versions are read from file URLs (CDN paths, WordPress `?ver=`); bundled or renamed copies are invisible.
- Premium plugins: their `readme.txt` is often stale; when an asset `?ver=` is newer, that version is used.
- Cookies set by JavaScript (analytics, consent tools) are not visible to a passive audit; only `Set-Cookie` headers are.
- MTA-STS/TLS-RPT/BIMI are informational: missing records cost no points; a broken MTA-STS policy does.
- Sub-domains are audited as given (no public-suffix list yet for e-mail DNS).
- History compares runs of the **same start URL** in the same account and `monitorId`; changing options between runs
  (pages crawled, modules, PageSpeed on/off) can make findings appear or disappear — the `notes` say so when detectable.
- PDF: plain layout, Latin characters only; very long reports make long PDFs (no page limit).
- CMS / frameworks: most well-run sites hide their exact version (Drupal aggregation, Joomla/TYPO3 generator, Next.js,
  Nuxt, Symfony, Laravel never show it): the report then says *version not visible* and lists the important recent
  advisories to check, without guessing. Next.js and Nuxt versions are never read (they would need many JavaScript
  downloads). PrestaShop module versions are not visible from outside: modules with published advisories are listed
  for checking, and only advisories **without any fix** are reported as a problem. Modules are recognised only when the
  pages load files from `/modules/<name>/` (CCC-combined assets hide them). Debug exposure is only seen when a crawled
  page or a naturally linked 404/500 page shows it. Magento's `/magento_version` gives the branch only (e.g. 2.4).

### FAQ

**Is it legal / safe to audit a site I do not own?** The audit reads only public pages and DNS records, like a search
engine, at a polite pace with an identifiable User-Agent, and respects `robots.txt` for crawling. It never logs in,
exploits or fuzzes anything. The only active option (`exposedFilesCheck`) is off by default and meant for sites you own
or are authorised to test.

**Can an AI agent use it?** Yes: input is plain JSON, output items have a stable documented schema with machine-readable
ids, severities, scores and fix steps, available through the Apify API and MCP server.

**Why is a page marked "noindex ✓" without a penalty?** Account, cart, checkout, login and search pages *should* be
excluded from search results; they are listed but do not lower the site score. A noindex on an important page (home,
listed in the sitemap, product/service pages) is reported as critical.

**Do I need JavaScript rendering?** Usually not. `renderJs: "auto"` (default) renders only pages whose raw HTML looks
empty or app-like and tells you how much of the content non-JavaScript crawlers (most AI crawlers) can see.
`always` renders up to `maxRenderedPages` pages; `never` disables Chromium.

**Where are screenshots stored?** In the run's own key-value store, in your Apify account only (never shared between
users). Anyone with the store's URL can open them, like any other run output.

**Why does my score differ from tool X?** Each tool weights checks differently; every point lost here is explained in
`scores.*.breakdown` and the per-page `issues`.

**What if PageSpeed fails?** Google sometimes refuses anonymous requests; add your own free `pagespeedApiKey`.

### Data sources

Domain data: registry RDAP servers listed in the [IANA RDAP bootstrap](https://data.iana.org/rdap/dns.json).
Vulnerability data: [WPVulnerability](https://www.wpvulnerability.net/) (CC0; aggregated from public sources,
each vulnerability links to its original advisory), [CISA KEV](https://www.cisa.gov/known-exploited-vulnerabilities-catalog)
(public domain), [OSV.dev](https://osv.dev/) for JavaScript libraries, and [WordPress.org](https://api.wordpress.org/) version data;
PHP support dates from [php.net](https://www.php.net/). The WPScan API is **not** used.
CMS / framework data (v6, nightly, attribution kept per record): [OSV.dev](https://osv.dev/) bulk Packagist export and
npm queries, i.e. the [GitHub Advisory Database](https://github.com/advisories) (**CC-BY 4.0**) and the
[Drupal security advisories](https://www.drupal.org/security) (drupal-advisory-database, MIT);
[Friends-of-Presta](https://security.friendsofpresta.org/) PrestaShop advisories (MIT); Joomla core CVEs from the
[NVD](https://nvd.nist.gov/) — *this product uses data from the NVD API but is not endorsed or certified by the NVD*;
release cycles and end-of-life dates from [endoflife.date](https://endoflife.date/) (MIT); CISA KEV (public domain).
Each item repeats the attribution lines it used in `platforms.attribution`.

# Actor input Schema

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

Websites to audit, one entry per site (home page URL, e.g. https://www.example.com). A sub-path such as https://site.com/blog/ limits the crawl to that section. Duplicated hosts are audited once. Output: one dataset item per site.

## `language` (type: `string`):

Language of all human-readable texts (titles, explanations, fix steps, HTML report). Field names, ids and enum values are always English.

## `maxPagesPerSite` (type: `integer`):

Maximum pages crawled per site (1-200). The first 10 are included in the 'site-audit' price; further pages are billed as 'extra-page'. 5-20 is enough for most audits. On Shopify stores a few product and collection pages are crawled first.

## `maxLinkChecks` (type: `integer`):

Links found on crawled pages (but not crawled) that are checked for errors (HEAD/GET, internal links first). 0 disables extra link checks.

## `pagespeed` (type: `boolean`):

Query Google PageSpeed Insights (Lighthouse lab test + Chrome real-user Core Web Vitals) for the home page. Adds 20-60 s per site. Without an API key Google may refuse (quota); the audit continues anyway and performance is then not scored.

## `pagespeedStrategies` (type: `array`):

Devices tested by PageSpeed Insights, in parallel. Default both. The performance category score is weighted 60% mobile + 40% desktop (or uses the only device available).

## `pagespeedApiKey` (type: `string`):

Your own free Google API key (Google Cloud > APIs & Services > enable 'PageSpeed Insights API' > Credentials > API key). Used first when given. Without it, the Actor's shared key is used for up to 50 sites per run; further sites skip PageSpeed with a note. Stored encrypted, never logged or output.

## `pagespeedPages` (type: `integer`):

Run Google PageSpeed Insights on the first N crawled pages (home page first). 1 = home page only. Each extra page adds 20-60 s and uses PageSpeed quota; with the shared key of this Actor extra pages count against the same per-run cap as sites.

## `checkSecurity` (type: `boolean`):

HTTPS availability and redirect, SSL/TLS certificate, security headers, cookie flags (Secure, HttpOnly, SameSite) on every crawled response, mixed content (active/passive) and forms posting to http://, security.txt (info), server/PHP version exposure, outdated JavaScript libraries with known vulnerabilities (OSV.dev).

## `checkDomain` (type: `boolean`):

Reads public registry data via RDAP (no personal data is kept). Runs only when the security checks are enabled.

## `checkEmail` (type: `boolean`):

DNS checks of the domain's e-mail authentication: MX, SPF (multiple records, +all/?all, 10-DNS-lookup limit = permerror, broken includes), DMARC, DKIM (common selectors), MTA-STS policy + TLS-RPT, BIMI (info).

## `checkSeo` (type: `boolean`):

Titles, meta descriptions, H1/headings, canonical, noindex, language, viewport, image alt texts, OpenGraph, hreflang, robots.txt, XML sitemap, error pages.

## `checkGeo` (type: `boolean`):

AI visibility (GEO): AI crawler rules in robots.txt, Cloudflare Content Signals, CDN AI-blocking advice, snippet controls (nosnippet, max-snippet, data-nosnippet), JSON-LD quality per type, trust signals, freshness, answer-friendly structure, Bing signals, llms.txt. Scored with a pillar breakdown.

## `checkLinks` (type: `boolean`):

Broken internal and external links (anti-bot answers are reported as inconclusive, not broken).

## `checkPerformance` (type: `boolean`):

Server response time, compression and (if 'pagespeed' is on) PageSpeed Insights mobile + desktop.

## `checkTechnologies` (type: `boolean`):

Detect CMS, e-commerce platform, frameworks, analytics, consent tools, CDN/hosting (own rule list).

## `checkWordpress` (type: `boolean`):

WordPress core/plugin/theme versions and known vulnerabilities with CVE ids, CVSS score/severity, fixed-in version and CISA KEV 'actively exploited' flag (escalated to critical, 'fix today'); premium components flagged as 'latest version unknown'; closed plugins; version-hiding (hardening) advice. Only applies to WordPress sites.

## `checkShopify` (type: `boolean`):

Shopify stores: theme, apps detected from scripts, Product/Offer structured data on product pages, product/collection SEO basics, cookie consent, speed impact of apps. Only applies to Shopify sites.

## `checkPlatforms` (type: `boolean`):

Passive detection of Drupal, Joomla, PrestaShop (+ modules), Magento 1/2 (Adobe Commerce), TYPO3, Symfony, Laravel, Next.js, Nuxt and hosted builders (Wix, Squarespace, Webflow); version only when the platform itself shows it; end-of-life (endoflife.date) and known vulnerabilities (OSV/GitHub Advisory Database, Drupal advisories, NVD, Friends-of-Presta, CISA KEV); framework debug exposure seen during the normal crawl (Symfony profiler / X-Debug-Token, Laravel Ignition/Whoops/Debugbar, Magento developer mode). At most one extra request per platform for its well-known version file, only if robots.txt allows it.

## `checkAccessibility` (type: `boolean`):

Passive HTML accessibility checks on crawled pages (form labels, empty links/buttons, vague link texts like 'click here', iframe titles, skip link / main landmark, zoom blocked, page language) plus Lighthouse accessibility audits (axe-core, incl. colour contrast) when PageSpeed runs. Scored as category 'accessibility'. Not a WCAG conformity audit. Mentions the European Accessibility Act for online shops (information, not legal advice).

## `pageRecords` (type: `boolean`):

Push one dataset item of type 'page' per crawled page (score 0-100, category scores, issues with fix hints) and add page aggregates (average score, top issues, duplicates, crawl coverage) to the site item.

## `renderJs` (type: `string`):

Render pages in headless Chromium to audit what users see on JavaScript sites (React, Vue, Next.js...). Compares rendered vs raw HTML (content invisible to most AI crawlers), adds rendered links/images to the page checks, lab LCP/CLS and consent-banner detection. 'auto' renders only pages with almost no text, an empty app root or a framework shell. Rendering uses more memory and time; 2048 MB is recommended for 'always'.

## `maxRenderedPages` (type: `integer`):

Upper limit of pages rendered in the browser per site (home first).

## `screenshots` (type: `boolean`):

Store a full-page PNG (max 8000 px high) of each rendered page in the key-value store; linked from the report.

## `auditMetaTags` (type: `boolean`):

Per-page records: score the Meta tags (title, description, pixel width, Open Graph, Twitter card, charset) category. Off = category not scored (null) and its issues not reported.

## `auditHeadings` (type: `boolean`):

Per-page records: score the Headings (H1-H6 tree, missing/multiple H1, skipped levels) category. Off = category not scored (null) and its issues not reported.

## `auditContent` (type: `boolean`):

Per-page records: score the Content (word count, readability per language, keywords) category. Off = category not scored (null) and its issues not reported.

## `auditTechnical` (type: `boolean`):

Per-page records: score the Technical (robots, canonical, head closed early, lang, viewport, HTTPS) category. Off = category not scored (null) and its issues not reported.

## `auditPerformance` (type: `boolean`):

Per-page records: score the Performance (PageSpeed or response time / HTML size) category. Off = category not scored (null) and its issues not reported.

## `auditSchema` (type: `boolean`):

Per-page records: score the Structured data (JSON-LD, microdata, RDFa) category. Off = category not scored (null) and its issues not reported.

## `auditLinks` (type: `boolean`):

Per-page records: score the Links (counts, nav/content/footer, nofollow, first 10 links checked) category. Off = category not scored (null) and its issues not reported.

## `auditImages` (type: `boolean`):

Per-page records: score the Images (alt, real file size, format, broken) category. Off = category not scored (null) and its issues not reported.

## `auditAccessibility` (type: `boolean`):

Per-page records: score the Accessibility (labels, accessible names, zoom) category. Off = category not scored (null) and its issues not reported.

## `exposedFilesCheck` (type: `boolean`):

OFF by default. Only enable it for websites you own or are explicitly authorized to test. Makes a short fixed list of single requests for files that must never be public (/.git/HEAD, /.env, /wp-config.php.bak, /backup.zip as HEAD only, /.DS_Store, /phpinfo.php, /wp-content/debug.log on WordPress), at the same polite rate. Each hit is validated by a content signature to avoid false positives; file contents are never stored or output (only a redacted one-line proof). Your explicit opt-in takes precedence over robots.txt for these few files. Needs 'checkSecurity'.

## `wpReadmeLookup` (type: `boolean`):

For plugins already referenced by the site's pages, read their public readme.txt to identify the version more reliably (max 15 requests).

## `liveVulnLookup` (type: `boolean`):

Query api.wordpress.org and wpvulnerability.net for plugins/themes not in the bundled database, OSV.dev for front-end JavaScript library vulnerabilities, and the CISA KEV feed if the database has no KEV data.

## `requestIntervalSecs` (type: `number`):

Politeness delay (minimum 0.5).

## `maxConcurrentSites` (type: `integer`):

Number of sites audited in parallel (1-5). Each site is still fetched politely (one request per interval).

## `compareWithPrevious` (type: `boolean`):

Keep a compact snapshot of each audit in the named key-value store 'site-audit-history' of YOUR Apify account and add a 'changes' block (new / fixed / worse / improved findings, score deltas) plus a 'What changed since <date>' section to the report. Schedule the Actor (Apify Schedules) to turn this into monitoring. Off = no history is read or written.

## `monitorId` (type: `string`):

Optional label that keeps a separate history per client or project (agencies: one id per customer). Letters, digits, '-', '\_', '.' only. Histories are also always separated by Apify account.

## `historyStoreName` (type: `string`):

Name of the named key-value store used for the history (in the account that runs the Actor). Change it only to keep fully separate histories.

## `reportDetail` (type: `string`):

'simple' hides raw technical JSON in the HTML/PDF report but keeps, for each finding, a short line 'how it was found + confidence + sources'. 'technical' also shows the raw evidence. The dataset always contains everything.

## `pdf` (type: `boolean`):

Save a PDF version of the report as 'OUTPUT.pdf' in the key-value store (drawn without a browser with fpdf2; plain layout; Latin characters only, others are transliterated). Off by default because most callers are programs: it adds about 0.5 s and up to ~50 MB of memory for 10 sites.

## `agencyName` (type: `string`):

Shown at the top of the HTML/PDF reports. Setting any white-label field removes our 'generated by' branding.

## `agencyLogoUrl` (type: `string`):

HTTPS URL of your logo (PNG or JPEG, max 1 MB for the PDF). Displayed in the reports.

## `brandColor` (type: `string`):

Hex colour for titles and links, e.g. '#0b5394'.

## `contactEmail` (type: `string`):

Shown in the report footer (e.g. your support address).

## `footerText` (type: `string`):

Free text in the report footer (e.g. 'Prepared by Example Agency - www.example.com').

## `alerts` (type: `object`):

Optional. {"webhookUrl": "https://...", "slackWebhookUrl": "https://hooks.slack.com/services/...", "minSeverity": "high", "onlyNew": true, "domainExpiryDays": 30, "sslExpiryDays": 14}. One message per destination per run when a site has findings >= minSeverity (only NEW or WORSE ones since the previous audit when onlyNew is true; all of them on a first audit) or a domain/SSL expiry within the thresholds. Messages go only to the URLs you give (no redirects followed). For e-mail, use an Apify integration on the run (see README).

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://example.com"
    }
  ],
  "language": "en",
  "maxPagesPerSite": 20,
  "maxLinkChecks": 30,
  "pagespeed": true,
  "pagespeedStrategies": [
    "mobile",
    "desktop"
  ],
  "pagespeedPages": 1,
  "checkSecurity": true,
  "checkDomain": true,
  "checkEmail": true,
  "checkSeo": true,
  "checkGeo": true,
  "checkLinks": true,
  "checkPerformance": true,
  "checkTechnologies": true,
  "checkWordpress": true,
  "checkShopify": true,
  "checkPlatforms": true,
  "checkAccessibility": true,
  "pageRecords": true,
  "renderJs": "auto",
  "maxRenderedPages": 5,
  "screenshots": false,
  "auditMetaTags": true,
  "auditHeadings": true,
  "auditContent": true,
  "auditTechnical": true,
  "auditPerformance": true,
  "auditSchema": true,
  "auditLinks": true,
  "auditImages": true,
  "auditAccessibility": true,
  "exposedFilesCheck": false,
  "wpReadmeLookup": true,
  "liveVulnLookup": true,
  "requestIntervalSecs": 1,
  "maxConcurrentSites": 2,
  "compareWithPrevious": true,
  "historyStoreName": "site-audit-history",
  "reportDetail": "simple",
  "pdf": false,
  "alerts": {
    "minSeverity": "high",
    "onlyNew": true
  }
}
```

# Actor output Schema

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

No description

## `pdf` (type: `string`):

No description

## `portfolio` (type: `string`):

No description

## `portfolioCsv` (type: `string`):

No description

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

No description

## `findings` (type: `string`):

No description

## `pages` (type: `string`):

No description

## `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 = {
    "startUrls": [
        {
            "url": "https://example.com"
        }
    ],
    "alerts": {
        "minSeverity": "high",
        "onlyNew": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("neocraft/website-audit-all-in-one").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://example.com" }],
    "alerts": {
        "minSeverity": "high",
        "onlyNew": True,
    },
}

# Run the Actor and wait for it to finish
run = client.actor("neocraft/website-audit-all-in-one").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://example.com"
    }
  ],
  "alerts": {
    "minSeverity": "high",
    "onlyNew": true
  }
}' |
apify call neocraft/website-audit-all-in-one --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neocraft/website-audit-all-in-one"
        }
    }
}
```

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/XCdmqF5XgON2uOjch/builds/PQxipU07uCzGEe7kY/openapi.json
