Website Audit: SEO, GEO, Security, WordPress & Speed
Pricing
from $490.00 / 1,000 site audits
Website Audit: SEO, GEO, Security, WordPress & Speed
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.
Pricing
from $490.00 / 1,000 site audits
Rating
0.0
(0)
Developer
Neocraft
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Features
Share
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
- Paste one or more website addresses in Start URLs and press Start (defaults are fine).
- Open the Output tab: the HTML report (
OUTPUT), the dataset (onesiteitem per website + onepageitem per crawled page) and, if asked,OUTPUT.pdf.
Minimal input (API / AI agents):
{ "startUrls": [{ "url": "https://example.com" }] }
Typical input — French report, 10 pages (no extra-page charge), PDF, JavaScript rendering only where needed:
{"startUrls": [{ "url": "https://example.com" }],"language": "fr","maxPagesPerSite": 10,"renderJs": "auto","pdf": true}
Agency / monitoring input — several sites, history and alerts, white-label report:
{"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 |
| 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 |
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:
{"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, ornot-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 (stableid+ 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 ofOUTPUT).
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:
POSTJSON.{"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
alertsblock and the run savesALERTS.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-mailActor 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 — 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:
summary— score, grade, platform, counts per severity, one-sentenceheadline.topPriorities— up to 5 objects{rank, id, category, severity, confidence, device, title, firstStep, whoCanFix}.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).scores—global,grade,categories(only evaluated ones) andbreakdown(geopillars,performanceper device,accessibility= passive vs Lighthouse).findings— every finding with stableid,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 rawevidence. Each finding also hashowDetected(one sentence: data source/method) andsources([{name, url}]: data providers, RFCs and up to 3 primary advisories).changes(see Monitoring),alerts(if configured),monitorId.wordpress,shopify,platforms(v6),technologies,details(raw facts per module),pages,limitations.platforms.detected[]=, plus{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)}primary,debugExposure[]andattribution[](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.
{"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: falseand 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.txtis 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-Cookieheaders 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 — thenotessay 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_versiongives 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.
Vulnerability data: WPVulnerability (CC0; aggregated from public sources,
each vulnerability links to its original advisory), CISA KEV
(public domain), OSV.dev for JavaScript libraries, and WordPress.org version data;
PHP support dates from php.net. The WPScan API is not used.
CMS / framework data (v6, nightly, attribution kept per record): OSV.dev bulk Packagist export and
npm queries, i.e. the GitHub Advisory Database (CC-BY 4.0) and the
Drupal security advisories (drupal-advisory-database, MIT);
Friends-of-Presta PrestaShop advisories (MIT); Joomla core CVEs from the
NVD — 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 (MIT); CISA KEV (public domain).
Each item repeats the attribution lines it used in platforms.attribution.