# SEO Audit + AI Visibility · Are You Citable by ChatGPT? (`siccscha/seo-health-auditor`) Actor

Technical SEO audit plus the question no other audit answers: can AI systems cite you? Checks which AI crawlers your robots.txt allows (only retrieval agents can cite you), flags pages that are empty without JavaScript, and diffs against your previous run. Plus fixes you can paste.

- **URL**: https://apify.com/siccscha/seo-health-auditor.md
- **Developed by:** [Chris S](https://apify.com/siccscha) (community)
- **Categories:** SEO tools, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$50.00 / 1,000 page auditeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## SEO Audit + AI Visibility — is your site citable by ChatGPT?

A bulk SEO audit and AI visibility checker in one run: it crawls your site, checks every page for
technical SEO issues, verifies every link, and answers the question no other audit answers — **can
AI systems reach and cite you?** It reads your `robots.txt` the way each AI crawler does, diffs
every run against your previous one, and hands you ready-to-paste fixes. Built for bulk, schedules,
API, Make, Zapier and MCP.

### The question nobody else's audit answers: **are you citable?**

Your readers increasingly get your answer from ChatGPT, Claude or Perplexity instead of from a blue
link. Whether you appear there is decided by your `robots.txt` — and by a distinction almost nobody
gets right:

| Crawler type | Examples | Blocking it means |
|---|---|---|
| **Training** | GPTBot, ClaudeBot, Google-Extended, Applebot-Extended, CCBot | Your content is not used to train models |
| **Retrieval** | **OAI-SearchBot, ChatGPT-User, Claude-SearchBot, Claude-User, PerplexityBot** | **You can never be cited in an AI answer** |

Most sites that "blocked the AI bots" used one blanket rule and quietly made themselves invisible in
AI answers — which is the opposite of what they wanted. This Actor tells you exactly where you stand:

```json
"aiVisibility": {
  "summary": { "retrievalAgentsAllowed": "6/7", "trainingCrawlersAllowed": "4/8",
               "citable": false, "pagesServerRendered": "19/19",
               "pagesWithAnswerShapedHeadings": 17, "pagesWithAuthorAndDate": 0 },
  "findings": [
    { "severity": "critical", "code": "ai-worst-of-both",
      "message": "Training crawlers are allowed while retrieval agents are blocked — your content may be used to train models but can never be cited back to you." },
    { "severity": "critical", "code": "ai-conflicting-robots-rules",
      "message": "robots.txt gives GPTBot, ClaudeBot both \"Disallow: /\" and \"Allow: /\". Which one wins is decided by each vendor's own tie-breaking… Typical cause: a CDN injected a managed block above your own rules." }
  ]
}
```

**Three things this catches that a browser-based audit structurally cannot:**

1. **Contradictory robots.txt rules for the same crawler.** Very common now that CDNs inject
   "managed" AI-blocking blocks above hand-written rules — the site then genuinely does not know
   what it is telling that crawler, because the outcome depends on each vendor's tie-breaking.
2. **`Content-Signal` declarations**, which are an express reservation of rights under Article 4 of
   the EU DSM Directive — a legal statement, flagged so you can check it says what you intend,
   especially if you did not add it.
3. **Pages that are empty without JavaScript.** AI retrieval agents do not execute scripts. This
   Actor reads the served HTML, so it sees your site exactly the way they do — a page Google ranks
   fine (Google renders) can be blank to every AI system, and only a non-rendering crawler can
   tell you.

Plus the signals AI answers rely on to lift and attribute a passage: question-shaped headings,
`FAQPage` markup, an author and a date.

The `robots.txt` matcher implements Google's documented semantics — longest match wins, `Allow`
beats `Disallow` on equal length, a group naming the agent beats `*` — and is covered by its own
test suite (`npm run test:robots`, 14 cases). A wrong answer here would tell you that you are
blocked when you are not, so it is asserted rather than assumed.

***

Beyond that, three more things ordinary SEO crawlers don't do:

1. **It compares against your previous run.** Every run stores a baseline per site, so the output
   says *which pages got worse since last time, which improved, which are new and which disappeared*.
   That is the difference between a report you read once and a monitor you can act on.
2. **It hands you the fix, not just the finding.** Each page carries a `fixes` array with the exact
   markup to paste: the title shortened at a word boundary, a meta description drafted from the page,
   the canonical, viewport, hreflang and Open Graph tags, ready to ship.
3. **It finds contradictions, not just defects.** Pages your sitemap asks Google to index while the
   page says `noindex`. Canonicals pointing at 404s or at pages that canonicalise onwards. Redirect
   **chains and loops**, traced hop by hop. Pages in your sitemap that nothing on your site links to.
   None of these are visible when you look at one page at a time — they only appear when you join the
   crawl, the sitemap, the canonicals and the link graph.
4. **It validates structured data against what Google actually requires.** Not "does the JSON-LD
   parse" — whether the block is *eligible for a rich result* at all. An `Article` without a
   `headline` is valid JSON and earns nothing.

One charge per page covers all of it — the AI-visibility audit, the comparison, the patches, the
contradiction checks and everything below.

Plus everything you'd expect: broken links, titles, meta descriptions, headings, canonicals,
indexability including the `X-Robots-Tag` header, hreflang, mobile viewport, thin content, mixed
content, TLS certificate expiry, duplicate titles across the whole site, robots.txt, sitemap.xml,
llms.txt — with a **0–100 score and a ranked recommendation list per page**.

Built for bulk and for schedules: hundreds or thousands of pages, results in a dataset, reachable
from the API, Make, Zapier or an MCP client.

### What gets checked on every page

| Check | Finding |
|---|---|
| Every `<a href>` link, internal and external | `broken-links` with the exact status code per dead URL |
| Title tag | `missing-title`, `title-too-long` (>65), `title-too-short` (<10) |
| **Duplicate titles / descriptions across the whole site** | `duplicate-title`, `duplicate-meta-description` — pages competing with each other |
| Meta description | `missing-meta-description`, `meta-description-too-long` (>170) |
| H1 structure | `missing-h1`, `multiple-h1` |
| **Heading outline** | `heading-hierarchy-skip` (h2 → h4 and similar jumps) |
| Canonical | `missing-canonical`, `invalid-canonical`, `cross-domain-canonical`, `multiple-canonicals` |
| **Indexability** | `not-indexable` with the reason — including the **`X-Robots-Tag` HTTP header**, the blocker that is invisible in the HTML |
| Meta robots | `noindex` (flags pages you may be de-indexing by accident) |
| **Mobile readiness** | `missing-viewport` |
| **Language & hreflang** | `missing-lang`, `invalid-hreflang`, `hreflang-missing-self` |
| **Content depth** | `thin-content` (< 250 words of real body text, scripts stripped) |
| **Social cards** | `incomplete-social-tags` (og:title/description/image, twitter:card) |
| Redirects | `redirected` (the crawled URL differs from the final URL) |
| Images | `images-missing-alt` with counts |
| JSON-LD structured data | `structured-data-errors` with the exact parse error |
| HTTPS hygiene | `mixed-content` (http resources on https pages) |
| **TLS certificate** | issuer, expiry date, days remaining; `certificate-expiring` under 30 days |

#### Contradictions — findings that need two sources joined

| Finding | What it means |
|---|---|
| `sitemap-but-noindex` | Your sitemap asks Google to index a page that tells Google not to |
| `canonical-target-broken` | The canonical points at a URL that answers 404 — the page is dropped entirely |
| `canonical-chain` | The canonical points at a page that canonicalises somewhere else again |
| `redirect-chain` / `redirect-loop` | Traced hop by hop; a loop means no user or crawler ever arrives |
| `orphan-page` | In the sitemap, linked from nowhere on the site |
| `sitemap-url-redirects` | The sitemap lists a URL that redirects instead of answering |

**Orphan detection is suppressed rather than guessed** when the crawl could not support the claim —
if `maxPages` cut the crawl short or the depth was too shallow, missing inbound links prove nothing.
The summary says so explicitly in `orphanDetection.note` instead of reporting noise.

#### Fixes you can paste

```json
"fixes": [
  { "issue": "title-too-long", "action": "replace", "selector": "head title",
    "snippet": "<title>XRechnung-Fehlercodes im Klartext: BR, BR-CO und BR-DE</title>" },
  { "issue": "missing-meta-description", "action": "insert-into-head",
    "snippet": "<meta name=\"description\" content=\"…\" />",
    "note": "Drafted from the first paragraph of this page — review the wording before shipping." }
]
```

Only deterministic fixes are emitted, and anything drafted from page content says so. No invented
copy presented as fact.

Every page item carries a **`score` from 0 to 100** and a **`recommendations` array** — the findings
translated into plain sentences, sorted so the most damaging fix is first. That is what goes into a
client report without rewriting.

Plus one **site summary item** per run: average score, robots.txt (exists? disallow-all accident?
references sitemap?), sitemap.xml, llms.txt presence, the full issue histogram, every unreachable
page, all certificates — and **`orphanPages`**: URLs that are in your sitemap but that **no page on
your site links to**. Those are the pages search engines discover and then quietly ignore.

### Example output (one page item)

```json
{
    "type": "page",
    "url": "https://example.com/pricing",
    "statusCode": 200,
    "title": "Pricing – Example",
    "metaDescription": null,
    "h1Count": 1,
    "canonicalStatus": "self",
    "imagesWithoutAlt": 2,
    "jsonLdBlocks": 1,
    "jsonLdErrors": [],
    "linkCount": 58,
    "brokenLinkCount": 1,
    "brokenLinks": [{ "url": "https://example.com/old-docs", "status": 404 }],
    "issues": ["missing-meta-description", "images-missing-alt", "broken-links"],
    "issueCount": 3
}
```

### How to audit a website in 3 steps

1. **Enter the start URL** — the Actor crawls all internal pages from there (same host), up to your `maxPages` limit.
2. **Run it** — every page is audited and every distinct link target is verified (HEAD with GET fallback, redirects followed).
3. **Use the results** — filter the dataset to `issueCount > 0`, export CSV for the client report, or wire it to Make/Zapier/Slack for alerts.

#### Example input

```json
{
    "startUrls": [{ "url": "https://example.com" }],
    "maxPages": 50,
    "auditAiVisibility": true,
    "compareWithPreviousRun": true,
    "includeHealthyPages": false
}
```

That is a full technical audit of up to 50 pages, the AI-visibility check, and a diff against your
previous run — issues-only output, ready for a client report.

### Monitor continuously — this is the point

Schedule the Actor and every run answers "what changed":

```json
"changes": {
  "comparedWith": "2026-07-22T06:11:03.000Z",
  "regressedCount": 1,
  "regressed": [{ "url": "https://example.com/pricing",
                  "newIssues": ["broken-links"], "scoreBefore": 98, "scoreNow": 83 }],
  "improvedCount": 4,
  "newPages": ["https://example.com/pricing-enterprise"],
  "pagesGone": ["https://example.com/old-offer"]
}
```

Wire `regressedCount > 0` to Slack or email through an Apify integration and you have a regression
alarm for your own or your clients' sites: the release that broke a canonical shows up the next
morning, not in a quarterly audit. The baseline lives in a named key-value store per host, so it
survives across runs and schedules. The first run has nothing to compare against and says so.

Turn off `includeHealthyPages` for a compact issues-only report.

### Use with AI agents (MCP)

This Actor is available as a tool through the [Apify MCP server](https://mcp.apify.com), so agents
in Claude, ChatGPT or any MCP client can run audits and read the results without a line of glue
code. Prompts that map directly onto this Actor:

- *"Audit example.com and list every broken link with its status code."*
- *"Check whether example.com is citable by ChatGPT and Perplexity, and tell me which robots.txt lines to change."*
- *"Compare example.com against last week's audit and summarize what got worse."*

The dataset is plain JSON with stable field names, and the site summary item states its own caveats
(suppressed orphan detection, skipped link checks) in prose — so an agent reading the output knows
what the numbers do and do not claim.

### What this adds over a standard SEO audit actor

| Capability | Standard audit actors | This one |
|---|---|---|
| AI visibility: which AI crawlers your robots.txt actually allows, training vs. retrieval | — | ✔ with per-vendor verdicts and conflicting-rule detection |
| Pages empty without JavaScript (what AI retrieval agents see) | — (browser-based audits structurally cannot) | ✔ |
| `llms.txt` check | — | ✔ |
| Diff against your previous run (`changes`) | — | ✔ baseline per host, survives schedules |
| Ready-to-paste fixes (`fixes[]`) | findings only | ✔ exact markup snippets |
| Cross-source contradictions (sitemap vs. noindex, canonical chains, orphan pages) | — | ✔ |
| On-page checks, broken links, redirects, structured data | ✔ | ✔ |
| Core Web Vitals / page speed | some | — deliberately (see FAQ), use the companion Actor |

### FAQ

**Does it use a headless browser?**
No — it audits the server-rendered HTML (what search-engine crawlers see first), which keeps runs fast and cheap. JavaScript-only content is out of scope for this version.

**Does it measure page speed or Core Web Vitals?**
No, and deliberately not: LCP, CLS and TBT need a real Chrome run, which costs roughly ten times as much per page as reading HTML. Putting it in here would make every audit expensive for everybody. Use the **Core Web Vitals Auditor** for that — same author, built to run alongside this one: audit the site here, feed the URLs that matter into the measurement there.

**How is the score calculated?**
Each finding subtracts a fixed weight from 100 — a broken link or a missing title costs far more than a slightly long meta description. Additive, capped at 0, no hidden model: you can reproduce any score from the `issues` array, and sort a dataset by it without having to trust us.

**Will it hammer my site?**
Concurrency is capped at 10 requests and the Actor identifies itself with a clear User-Agent. It audits the site you point it at — intended for your own sites or sites you have permission to audit.

**What counts as a broken link?**
Any link target answering HTTP ≥ 400, timing out, or failing DNS/TLS. Servers that reject HEAD requests are automatically re-checked with GET before being reported.

**Can AI agents use this?**
Yes — via Apify's MCP server, agents can run audits and read results programmatically ("audit example.com and list all broken links").

### Pricing

**$0.05 per page audited.** One charge covers everything for that page: all on-page checks, every
link on it verified, the redirect chain traced, structured data validated against Google's
requirements, the contradiction checks, the score, the ready-to-paste fixes and the comparison
against your previous run. The site summary item is free, and `maxPages` caps the cost exactly.

No cheaper-than-the-others pitch: this is priced in the same band as the established bulk audit
tools, and the argument for choosing it is the capabilities above that they don't have.

### More from this publisher

- **[Core Web Vitals Auditor](https://apify.com/siccscha/core-web-vitals-auditor)** — bulk Lighthouse
  for a list of URLs: LCP, CLS, TBT, ranked fix opportunities. The measurement companion to this audit.
- **[TED Tender Scraper](https://apify.com/siccscha/ted-tender-scraper)** — search and monitor EU
  public procurement notices via the official TED API.
- **[E-Invoice Parser](https://apify.com/siccscha/e-invoice-parser)** — XRechnung, ZUGFeRD/Factur-X
  and UBL e-invoices to clean, validated JSON.

# Actor input Schema

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

One or more start URLs. The Actor crawls all internal pages from here (same host) and audits each one: technical SEO findings, broken links, AI citability, a 0–100 score and ready-to-paste fixes per page.

## `maxPages` (type: `integer`):

Upper bound on audited pages. You are only charged per page actually audited. Raise it for a full-site audit — at 25 pages a run costs $1.25.

## `checkExternalLinks` (type: `boolean`):

Also test links pointing to OTHER domains (slower, but finds outbound link rot). Internal links are always verified.

## `auditStructuredData` (type: `boolean`):

Validate every JSON-LD block against what Google actually requires for a rich result — not just "does it parse": syntax errors with their position, missing @type, and missing required fields for 18 schema.org types (an Article without a headline is valid JSON and earns nothing).

## `maxCrawlDepth` (type: `integer`):

How many clicks away from the start URL to follow. 0 = only the start URLs.

## `includeHealthyPages` (type: `boolean`):

When off, only pages with at least one finding are pushed (cheaper, report-style output). The site summary item is always pushed.

## `checkCertificate` (type: `boolean`):

One TLS handshake per host to read issuer and expiry date. Flags certificates expiring within 30 days.

## `detectOrphanPages` (type: `boolean`):

Compare sitemap.xml against the internal link graph and report pages that no page on the site links to.

## `traceRedirectChains` (type: `boolean`):

Follow redirects hop by hop to reveal chains and loops instead of only the endpoint. Costs one request per hop, capped at 300 URLs.

## `emitFixes` (type: `boolean`):

Add a `fixes` array per page with the exact markup to insert or replace — shortened titles, drafted meta descriptions, canonical/viewport/hreflang tags.

## `compareWithPreviousRun` (type: `boolean`):

Store a baseline per site and report in `changes` which pages got worse, which improved, and which are new. This is what makes a schedule useful.

## `auditAiVisibility` (type: `boolean`):

Check whether AI systems can reach and cite you: which AI crawlers robots.txt allows (training vs. retrieval — only retrieval agents can cite you), llms.txt quality, and which pages carry no text without JavaScript.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://crawlee.dev"
    }
  ],
  "maxPages": 25,
  "checkExternalLinks": true,
  "auditStructuredData": true,
  "maxCrawlDepth": 10,
  "includeHealthyPages": true,
  "checkCertificate": true,
  "detectOrphanPages": true,
  "traceRedirectChains": true,
  "emitFixes": true,
  "compareWithPreviousRun": true,
  "auditAiVisibility": true
}
```

# Actor output Schema

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

One item per audited page: score 0-100, findings, ready-to-paste fixes, AI-readability verdict, broken links. The last item is the site summary with the AI-visibility section, robots.txt findings and the comparison with the previous run.

## `issuesOnly` (type: `string`):

The same dataset reduced to the fields an agency acts on: page, score, findings, broken links, AI-readable verdict.

## `baseline` (type: `string`):

Key-value store holding the baseline used to report what got worse since the previous run.

## `runInConsole` (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://crawlee.dev"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("siccscha/seo-health-auditor").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://crawlee.dev" }] }

# Run the Actor and wait for it to finish
run = client.actor("siccscha/seo-health-auditor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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://crawlee.dev"
    }
  ]
}' |
apify call siccscha/seo-health-auditor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=siccscha/seo-health-auditor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/9dNhrV1NnLAgR5FTR/builds/f7fk7nJC4ro6xcy9b/openapi.json
