# Answer Engine Optimization & GEO Audit - ChatGPT Fix Plan (`tidytools/geo-readiness-audit`) Actor

AEO with a fix plan, not just a score: for each buyer question, see which page ChatGPT, Perplexity or Google AI Overviews would quote, why competitors win, and what to add. Plus a GEO audit of AI crawler access and firewalls. $0.01/question, $0.03/site.

- **URL**: https://apify.com/tidytools/geo-readiness-audit.md
- **Developed by:** [Yukai Lin](https://apify.com/tidytools) (community)
- **Categories:** SEO tools, AI, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

### What does it do?

**It tells you why ChatGPT, Perplexity and Google AI Overviews quote your competitors instead of you, and what to change.** Give it your website and the questions your buyers ask. For each question it finds the page and the passage on your site that an answer engine would most likely quote, scores it 0-100, compares it with the pages that do get cited, and writes a **fix plan**: which page, which section, a rewrite draft built only from facts already on your pages, and what is missing. It also runs a full **GEO audit** (Generative Engine Optimization, also called AEO, answer engine optimization): can AI crawlers reach and read the site at all, with a live firewall test, a 0-100 GEO score and a shareable HTML report.

- **Answer engine optimization per question**: best page and passage, why it falls short (words, answer-first sentence, facts, shape), competitor gaps, rewrite draft, related questions engines also search, what to add. **$0.01 per question.**
- **GEO audit per site**: AI crawler access (robots.txt and firewall), content without JavaScript, structure, sitemap, schema, prioritized fixes, HTML report. **$0.03 per site** + $0.004 per extra sample page.
- **Honest by design**: it explains and fixes; it does **not** predict whether you will be cited (no tool can see an engine's retrieval). See *How accurate is it?* below.

#### How to get a fix plan in 3 steps

1. Enter your **website** and up to 20 **target questions** (what buyers type into ChatGPT or Google, e.g. `how much does plausible analytics cost`). Add `| https://...` after a question to check one specific page.
2. Optional: paste **competitor pages** that answer engines cite for those questions (ask ChatGPT or Perplexity once and copy the sources), or add competitor sites to the website list.
3. Click **Start** and open each row's `answers`: `bestUrl`, `bestPassage`, `fixes`, `draft` and `whatToAdd`, or the **HTML report** in the *Output* tab.

#### How is this different from AI rank trackers?

Rank and visibility trackers (most AI-search Actors in the Store) **measure** whether a brand is mentioned in ChatGPT or Perplexity answers, sampling paid language-model APIs; results change from run to run. This Actor **explains and fixes**: it reads your pages the way a crawler does and shows, question by question, what an engine would lift and what to add so it has something worth quoting. The two fit together: paste the URLs a tracker (or your own ChatGPT session) shows as cited into `competitorPages`, and the fix plan explains why those pages win.

#### What the GEO audit checks for every website

- 🤖 **AI crawler access in robots.txt**: 29 AI crawlers (list checked against each vendor's documentation on 2026-10-01), separated into AI search / user-triggered agents (which decide whether you can be cited) and training crawlers (a business choice that does not affect AI answers); ad-review crawlers (OAI-AdsBot, meta-externalads) are listed but not scored
- 🏷️ **Content Signals**: Cloudflare's `Content-Signal: search=…, ai-input=…, ai-train=…` line in robots.txt is reported (`contentSignals` and the `content-signals` info check)
- 🧱 **Firewall and CDN blocks**: requests the home page as 8 AI crawlers (GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, Claude-SearchBot, Claude-User, PerplexityBot, Perplexity-User) and compares the answers with a normal browser, to catch "block AI bots" rules that robots.txt does not show. Bot challenge pages served with HTTP 200 ("Just a moment…", Turnstile, CAPTCHA) and near-empty pages count as blocked, not as open
- 🚦 **Honest access verdicts**: if robots.txt itself answers 401/403/418/429 or a bot challenge, access is reported as **unknown** (check `robots-unreachable`), never as "open". If most AI search and assistant crawlers are blocked, the score is **capped** (see below)
- 🔎 **Googlebot and Bingbot access**, the indexes behind AI Overviews, Copilot and other assistants
- 📄 **Content without JavaScript**: most AI crawlers do not run JavaScript. The audit reads the raw HTML of sample pages and flags JavaScript app shells and thin pages
- 🧩 **Extractability**: `<main>`/`<article>` markup, lists, tables and question-style headings that let AI systems lift a clean passage; FAQ content and freshness (newest date in structured data) as info
- 🗺️ **Discoverability**: XML sitemap, `Sitemap:` line in robots.txt, llms.txt
- 🏷️ **Understanding and trust**: titles and meta descriptions, headings, language, noindex, Organization/WebSite structured data with `sameAs` profile links, JSON-LD coverage, author and dates on articles, About and Contact pages
- ⚡ **Speed and HTTPS** (info): time to first byte of the home page, HTTPS and HSTS
- 🛠️ **Prioritized fixes**: every failed check comes with a concrete fix, sorted by how many points it costs you
- 📑 **HTML report**: a self-contained page per site (score, category bars, fix list, crawler table) you can send to a client, plus a ranking page when you audit several sites
- 🔍 **Google AI Overview citations (optional)**: for up to 20 queries, is each audited site cited in Google's AI Overview, and at which position? Uses Apify's official Google AI Overviews Scraper on your account (see the price note below)

### Which tool should I use?

| | **GEO Audit** (this Actor) | **AI Crawler Access Checker** (by TidyTools) |
|---|---|---|
| Question | "Why is my site not cited by AI answers, and what should I fix first?" | "Do these 5,000 domains allow AI crawlers in robots.txt, and what changed since last week?" |
| Depth | Live firewall test with challenge detection, content without JavaScript, structured data, score, HTML report | robots.txt policy only: custom paths, matched rules, ready-to-paste fix snippet |
| Best for | Site owners, marketers, agencies auditing one client or a few competitors | Researchers, data teams, agencies watching many domains on a schedule |
| Price | about $0.046 per site (5 pages) | $0.002 per site |

### Who is it for?

- **SEO and GEO agencies**: audit prospects and clients in bulk, send each one an HTML report with a clear score and to-do list
- **Site owners and marketers**: find out why AI answers never mention your site, and how you compare with competitors
- **Developers**: catch a firewall rule or a client-side rendering change that hides the site from AI crawlers
- **Researchers**: compare how open different industries are to AI crawlers

### How much does it cost?

| Event | Price |
|---|---|
| Audited website (robots.txt, firewall test, sitemap, llms.txt, home page) | **$0.03** |
| Each extra sample page | **$0.004** |
| Each target question analyzed, per website (optional) | **$0.01** |

With the default of 5 sample pages, a site costs about **$0.046** ($0.03 + 4 × $0.004). One site with 10 target questions costs about **$0.146** ($0.046 + 10 × $0.01). **No start fee.** Questions where no page of the site could be read are free.

**AI Overview citations (optional):** this Actor adds no fee. It runs Apify's official [Google AI Overviews Scraper](https://apify.com/apify/google-ai-overviews-scraper) (`apify/google-ai-overviews-scraper`) once per run for all your queries. That run is started on **your** Apify account and billed there by that Actor's own pricing: $0.003 per AI Overview captured on the Free plan ($0.002 Bronze, $0.0015 Silver, $0.001 Gold), plus a $0.00005 start fee (checked September 2026). Queries without an AI Overview are not charged by it. Example: 10 queries ≈ $0.03. We verified this in a test run: the scraper run appeared in the account that started the audit, with its own charges (1 start + 2 AI Overviews). Because that run is billed outside this run's charge limit, this Actor gives it its own maximum charge of $0.01 per query (at least $0.05; shown in `SUMMARY.aiOverviews.maxChargeUsd`) and stops it if it has not finished within 10 minutes. Unreachable websites are not charged, and the HTML reports and the multi-site summary are free. Higher Apify plans get volume discounts (see the *Pricing* tab).

For comparison, other GEO audit Actors in the Apify Store charge $0.008 to $0.035 per page, and some add a start fee of $0.02 to $0.05 per run (checked September 2026).

### Control your cost

- **Charged**: each audited website ($0.03) and each extra sample page that could be read ($0.004). *Pages to sample per site* is the main cost control.
- **Free**: unreachable websites (also a site where neither the home page nor any sample page could be read, even with a real browser: `errorType: "unreachable"`), sites whose robots.txt closes the home page to crawlers (`errorType: "robots_disallowed"`), sample pages that could not be read, target questions with no readable page, invalid input lines (including private and local addresses such as `localhost` or `192.168.x.x`), the HTML reports and the multi-site summary. Every row has `charged: true` or `false`. If a site has fewer pages to sample than requested (no sitemap and few links in the home page's HTML), the row shows `samplePagesRequested`, `samplePagesFound`, `samplePagesAnalyzed` and a `samplePagesNote` with the reason.
- At the start, the log and status message show the plan: websites × $0.03 + extra pages × $0.004 = the most the run can cost, compared with your **maximum charge per run** (set in the run options).
- When that maximum is reached, the run stops auditing new websites. The status message says how many were not audited, and `SUMMARY.notProcessed` lists them (count and up to 100 inputs).
- The optional AI Overview check is billed by `apify/google-ai-overviews-scraper` on your account, outside this limit, with its own cap (see above).
- **If Apify restarts the run** (server migration or Resurrect), items already finished are skipped and not charged again (`SUMMARY.resumedSkipped`).
- **Time limit per website** (Advanced settings, default 300 seconds): a site whose audit (robots.txt, firewall test, sitemap, llms.txt and sample pages) takes longer gets a row with `errorType: "timeout"` and is not charged. The optional AI Overview check is not part of it.

### How to use it

1. Enter one or more **websites** (`example.com` is fine). To compare with competitors, simply add their sites too. The audit covers each site's root: for `cloudflare.com/learning/` the result says in `note` that the path was not audited separately. A line that is not a website gets its own error row (`errorType: "invalid_input"`, not charged) and the other sites are still audited.
2. Add **target questions** for the fix plan (optional, $0.01 each per site), and **competitor pages** if you know which pages get cited.
3. Optional: change **Pages to sample per site** (default 5, including the home page).
4. Click **Start**. Open **HTML report** in the *Output* tab, open each result for the full list of checks and the per-question `answers`, or export the table.
5. Optional: schedule the Actor weekly and add a webhook in the *Integrations* tab to be told when a run finishes, then compare scores.

#### Input example

A site with two target questions and one competitor page cited for the first question (this is the prefilled example; about $0.066):

```json
{
    "websites": ["https://plausible.io"],
    "targetQuestions": ["how much does plausible analytics cost", "is plausible analytics gdpr compliant"],
    "competitorPages": ["how much does plausible analytics cost | https://usefathom.com/pricing"]
}
```

GEO audit only, several sites compared in one run:

```json
{
    "websites": ["https://www.python.org", "https://stripe.com", "https://medium.com"],
    "samplePages": 5,
    "testUserAgents": true,
    "htmlReport": true
}
```

With the AI Overview citation check (competitors are simply more websites in the same run; one scraper run covers them all):

```json
{
    "websites": ["https://www.python.org", "https://www.w3schools.com"],
    "checkAiOverviews": true,
    "aiOverviewQueries": ["how to install python on windows", "what is a python virtual environment"]
}
```

#### Output example

A real result for python.org (September 2026, shortened). It is open to every AI crawler and its pages have plenty of server-rendered text, but it has no sitemap:

```json
{
    "type": "site",
    "site": "https://python.org",
    "score": 82,
    "grade": "B",
    "uncappedScore": 82,
    "gated": false,
    "gateReason": null,
    "partial": false,
    "reportUrl": "https://api.apify.com/v2/key-value-stores/<store>/records/REPORT-python.org.html",
    "categories": {
        "access": { "points": 40, "max": 40 },
        "discovery": { "points": 0, "max": 15 },
        "content": { "points": 29, "max": 30 },
        "entity": { "points": 11, "max": 12 }
    },
    "recommendations": [
        { "priority": "high", "check": "sitemap", "fix": "Publish an XML sitemap and list it in robots.txt with a \"Sitemap:\" line.", "pointsLost": 8 },
        { "priority": "medium", "check": "llms-txt", "fix": "Add /llms.txt: a Markdown file with your site name, a one-line summary and links to your key pages.", "pointsLost": 4 },
        { "priority": "medium", "check": "robots-sitemap-line", "fix": "Add \"Sitemap: https://your-site/sitemap.xml\" to robots.txt.", "pointsLost": 3 },
        { "priority": "low", "check": "extractable-structure", "fix": "Wrap the main content in <main> or <article>, and present key facts as lists or tables and common questions as H2/H3 headings; AI systems quote well-structured passages more easily.", "pointsLost": 1 }
    ],
    "aiCrawlers": {
        "allowed": ["GPTBot", "OAI-SearchBot", "ChatGPT-User", "ClaudeBot", "Claude-SearchBot", "Claude-User", "PerplexityBot", "..."],
        "blocked": [],
        "unknown": [],
        "blockedAtFirewall": [],
        "userAgentTest": { "baseline": 200, "baselineBytes": 53213, "results": [{ "bot": "GPTBot", "status": 200, "blocked": false, "blockedReason": null, "bytes": 53213 }, "..."] }
    },
    "robotsTxt": { "status": 200, "found": true, "state": "found", "reason": null, "sitemaps": [] },
    "performance": { "ttfbMs": 335, "https": true, "hsts": true },
    "pages": [
        { "url": "https://python.org/", "title": "Welcome to Python.org", "readVia": "http", "via": "direct", "wordCount": 1182, "hasDescription": true, "h1Count": 5, "h2Count": 9, "lang": "en", "listCount": 28, "tableCount": 1 }
    ]
}
```

medium.com blocks only training crawlers (and Amazonbot) in robots.txt, but its firewall answers every AI user agent with HTTP 403 while a normal browser gets the page (re-checked 1 October 2026, before the crawler-list update of the same day: Amazonbot then counted as an AI search crawler; it is a training crawler now and Amzn-SearchBot takes its place among the 9 major engines). 7 of 9 AI search and assistant crawlers are blocked, but only by the firewall test, which is unverified (AI user agents sent from a data-center IP), so the softer cap of 80 applies. The score is below it, so it is not capped; the firewall check still costs points and appears in `recommendations`:

```json
{
    "site": "https://medium.com",
    "score": 69,
    "uncappedScore": 69,
    "grade": "C",
    "gated": false,
    "gateSource": null,
    "aiCrawlers": {
        "blocked": ["GPTBot", "ClaudeBot", "Applebot-Extended", "Meta-ExternalAgent", "Amazonbot", "Bytespider"],
        "blockedAtFirewall": ["GPTBot", "OAI-SearchBot", "ChatGPT-User", "ClaudeBot", "PerplexityBot", "Claude-SearchBot", "Claude-User", "Perplexity-User"]
    }
}
```

A JavaScript app loses the content points, because crawlers that do not run JavaScript get an empty page:

```json
{
    "id": "server-rendered",
    "status": "fail",
    "points": 0,
    "max": 10,
    "message": "1 of 1 page(s) are JavaScript app shells with little or no text in the HTML (e.g. https://excalidraw.com/). Most AI crawlers do not run JavaScript, so they see an empty page.",
    "fix": "Render the main content on the server (SSR or static generation) so it is in the HTML response, not only after JavaScript runs."
}
```

With *Check Google AI Overview citations*, each site gets `aiOverviews` and an `ai-overview-citations` info check (also shown in the HTML report). Real result (September 2026, python.org, same run as w3schools.com):

```json
{
    "aiOverviews": {
        "queries": 2,
        "withOverview": 2,
        "cited": 2,
        "citations": [
            { "query": "how to install python on windows", "hasOverview": true, "cited": true, "position": 1, "citedUrls": ["https://www.python.org/downloads/"], "sources": [{ "url": "https://www.python.org/downloads/" }, { "url": "https://learn.microsoft.com/en-us/windows/dev-environment/python" }, "..."] },
            { "query": "what is a python virtual environment", "hasOverview": true, "cited": true, "position": 2, "citedUrls": ["https://docs.python.org/3/library/venv.html", "https://docs.python.org/3/tutorial/venv.html"], "sources": ["..."] }
        ]
    }
}
```

In the same run, w3schools.com was cited for the second query only (`"position": 4`). Subdomains count as the site (docs.python.org for python.org). `position` is the rank of the first citing source. If the scraper cannot run, `aiOverviews.error` says why and the audit itself is unaffected.

A site that publishes Content Signals gets `contentSignals` (real result, www.cloudflare.com): `{ "search": "yes", "aiInput": "yes", "aiTrain": "yes", "userAgent": "*", "raw": "ai-train=yes, search=yes, ai-input=yes" }` and an info check: "robots.txt declares Cloudflare Content Signals (…): search allowed, AI answers (ai-input) allowed, AI training (ai-train) allowed." A missing signal means no preference.

Each result also includes `checks` (every check with status, points and message), `robotsTxt` (with `state`: `found`, `missing`, `unavailable` or `server-error`), `sitemap`, `llmsTxt` and `llmsFullTxt`. Failed items carry an `errorType` (for example `network`, or `unreachable` when no page of the site could be read) and are not charged. Every row has `input` (your original line; `inputs` when several lines point to the same site) and `charged`. For spreadsheets, n8n and Make, each site also has flat fields: `topFix1`, `topFix2`, `topFix3` (the fixes with the most impact), `blockedAiCrawlers` (blocked in robots.txt) and `firewallBlocked` (refused in the firewall test), as comma-separated text.

#### Compare several sites

When you audit two or more sites, the dataset gets one extra row with `"type": "summary"` (free; turn off *Add a summary row to the dataset* to keep one row per site, the summary then stays in `SUMMARY`), and `REPORT.html` becomes a ranking page that links to each site's report. Real example with stripe.com, python.org and medium.com (September 2026, before the firewall-only cap became softer; medium.com now scores 69 without a cap):

```json
{
    "type": "summary",
    "site": "Summary of 3 sites",
    "averageScore": 78,
    "ranking": [
        { "rank": 1, "site": "https://stripe.com", "score": 96, "grade": "A", "gated": false },
        { "rank": 2, "site": "https://python.org", "score": 82, "grade": "B", "gated": false },
        { "rank": 3, "site": "https://medium.com", "score": 55, "grade": "D", "gated": true }
    ],
    "topIssues": [
        { "check": "llms-txt", "sitesAffected": 2, "sites": ["https://python.org", "https://medium.com"], "fix": "Add /llms.txt: ..." },
        { "check": "firewall", "sitesAffected": 1, "sites": ["https://medium.com"], "fix": "Check your CDN / firewall bot settings ..." }
    ],
    "passRate": { "ai-search-crawlers": 0.67, "firewall": 0.67, "sitemap": 0.67, "llms-txt": 0.33, "extractable-structure": 0.33 }
}
```

The run's `SUMMARY` record in the key-value store has `status` (`SUCCESS`, `PARTIAL_RESULTS`, `FAILED`, `NO_RESULTS` or `LIMIT_REACHED`), the average score, the ranking and the report link.

#### How the score works

| Category | Points | What counts |
|---|---|---|
| Access | 40 | AI search and assistant crawlers allowed (20), Googlebot and Bingbot allowed (10), no firewall block or challenge for AI user agents (10). If robots.txt cannot be read, the first two become one `robots-unreachable` check (30) worth half marks |
| Discovery | 15 | XML sitemap (8), `Sitemap:` line in robots.txt (3), llms.txt (4) |
| Content | 30 | Text visible without JavaScript (10), extractable structure (2), titles and descriptions (5), headings (5), language (3), not noindex (5) |
| Understanding and trust | 15 | Organization/WebSite schema on the home page (5), `sameAs` profile links (1), JSON-LD on pages (3), author and date on articles (3), About and Contact links (3) |

Grades: A ≥ 90, B ≥ 75, C ≥ 60, D ≥ 40, F below 40. Checks that cannot be measured (for example no article pages in the sample) are left out of the score instead of counting as failures. `partial: true` means some checks could not be run because pages could not be read.

**Access cap.** Content quality cannot make up for being shut out. The cap looks at 9 major AI answer engines (OAI-SearchBot, ChatGPT-User, Claude-SearchBot, Claude-User, PerplexityBot, Perplexity-User, Amzn-SearchBot, DuckAssistBot, MistralAI-User). A crawler counts as blocked when robots.txt disallows it or the firewall test refuses or challenges it. Blocks in robots.txt are the site's stated policy: if **all** of them are blocked there, the score is capped at **25**; if **more than half**, at **55**. The firewall test is an unverified test (AI crawler user agents sent from a data-center IP; verified crawlers may still be allowed), so when the firewall test is needed to reach those numbers the cap is softer: **70** when all are blocked, **80** when more than half are. Capped results have `gated: true`, a `gateReason`, `gateSource` (`robots`, `firewall` or `both`), and the `uncappedScore` for reference. Blocking training crawlers never triggers the cap.

#### Why do AI answers cite my competitors? (target questions)

Add up to 20 **target questions**, the questions your buyers type into ChatGPT, Perplexity or Google (for example `bulk audio transcription api`). For each question and each website, the Actor:

1. Picks candidate pages: the home page plus the URLs that match the question best, from **all of the site's XML sitemaps** (the `Sitemap:` lines in robots.txt, else `/sitemap.xml`; sitemap indexes are followed, up to 16 sitemap files and 50,000 URLs, each section getting a fair share) and the home page's links. URLs are ranked by the words in their path and link text, plus a text-embedding match for pages named in other words; pages that robots.txt closes to our crawler are skipped. `pagesPerQuestion` (default 4, home page included) sets how many are read, or give the page yourself with `question | https://...`. The site's pricing page (for price questions; also `/preise`, `/prix`, `/precios`) or its docs home, help center or FAQ page (for other questions) is read as well when the ranking did not pick it, since its URL rarely carries the question's words, and so is a top-level section named by the question (`/macbook-air/`, `/services`). Blog indexes, tag, category and shop collection pages are skipped, and old dated pages (`/2013/06/...`) rank below current ones unless the question asks about a year. Questions in German, French and Spanish are recognised (price, yes/no and how-to wording, question words, prices written `11,90 € pro Monat`). Each answer lists `pagesChecked` (with `source`: `home`, `sitemap`, `link`, `hub` or `pinned`) and the top 5 `candidatesRanked`; each site row has `questionCandidates` (sitemaps read, URLs found).
2. Reads the raw HTML (what crawlers that do not run JavaScript receive), splits it into heading-scoped passages and finds the passage an answer engine would most likely quote. A code example (`<pre>` block) belongs to the passage under its heading, so documentation pages that answer with code are read as answers.
3. Scores that passage 0-100: does it use the buyer's words, does its first sentence answer the question, does the heading match, does it give concrete facts (prices, limits, numbers), does it have the shape the question needs (steps or a code example for a how-to or API question, a table for a comparison, a price for a price question), is it near the top of the page. A text-embedding model also credits passages that answer in other words (turn off with `semanticMatch: false`).
4. Compares it with your **competitor pages** (paste the sources ChatGPT or Perplexity showed you) and with the other websites in the run, then writes the fixes: which page, which section, what to add or rewrite. Fixes where competitors already do better come first.
5. Builds a **fix plan** (on by default, `fixPlan`; included in the per-question price):
   - **Related questions (fan-out).** Answer engines also search 2-4 related questions for each question (price, how it works, limits, alternatives). The Actor lists them and shows, for each, the best passage on your site, whether it is covered, and the best competitor page.
   - **Rewrite draft** of your best page's opening passage: answer first, in 40-90 words, written by a language model on our backend **only from text already on your pages**. The draft is then checked against your page: numbers that are not on the page become `[ADD: number]`, sentences with names or claims ("fastest", "#1") that are not on the page are dropped, and a "Yes"/"No" the page does not support is replaced by `[ADD: yes or no, and the condition]`. Every `[ADD: ...]` marks a fact you have to fill in. When no passage answers the question, the draft is a new section built from all the pages checked.
   - **What to add**: missing words, related questions to answer, the numbers buyers compare, steps or a table, and sources to cite.

```json
"answers": [{
  "question": "example bakery opening hours",
  "answerScore": 78, "verdict": "strong", "bestUrl": "https://example-bakery.com/visit",
  "bestPassage": { "heading": "VISIT THE BAKERY", "firstSentence": "MON 7:30am – 6pm; TUES 7:30am – 6pm; ...", "missingTerms": [] },
  "fixes": [{ "id": "answer-first", "priority": "high", "action": "Start the section \"VISIT THE BAKERY\" with a one-sentence direct answer (under 30 words) ..." }],
  "subQuestions": [
    { "question": "example bakery holiday hours", "score": 74, "covered": true, "url": "https://example-bakery.com/visit", "heading": "ABOUT THE BAKERY" },
    { "question": "example bakery schedule", "score": 55, "covered": false, "missingTerms": ["schedule"] }
  ],
  "draft": { "kind": "rewrite", "source": "ai", "heading": "Example Bakery Opening Hours",
    "draft": "Example Bakery opens at 7:30am. It is open daily from 7:30am to 6pm. The bakery is open on most holidays. It closes on Thanksgiving and Christmas day.",
    "factsUsed": ["OPEN DAILY: 7:30am – 6pm"], "placeholders": [], "removed": [] },
  "whatToAdd": [{ "type": "sub-question", "item": "A short section that answers \"example bakery schedule\"", "why": "answer engines also search this; the best match on the site scores 55/100" }]
}]
```

**When your own pages show a bot challenge.** If a page answers our crawler (honest user agent, from a cloud network) with a challenge page such as Cloudflare's "Just a moment...", the result says so plainly and puts it first: `errorType: "bot_challenge"` on the question, a `bot-challenge` fix ("AI crawlers and agents may be challenged too") as the first recommendation and `topFix1`, with what to check in your bot protection. The Actor then tries the same page once from our second server (a different network, same user agent) so you still get the analysis, and the competitor pages are analysed either way. Questions with no readable page are not charged.

**Price questions.** When none of the pages checked states a price in its raw HTML (for example, plan prices filled in by JavaScript), the fix plan says so (`price-not-in-html`): crawlers that do not run JavaScript cannot quote a price you do not put in the HTML.

**How accurate is it?** The answer score estimates whether your page has a passage worth quoting **if** an answer engine retrieves it. Whether it is retrieved also depends on search ranking, brand authority and mentions on other sites, which no page analysis can see, so the score is not a citation forecast. In our own test of 10 buyer questions, our pages scored 81-91 (as good as the cited competitors) but appeared in the top web results for only 1 question: the fixes that mattered were titles that use the buyer's exact words, and mentions on other sites. The report keeps these layers apart: crawler access (the GEO score), passage quality (answer score) and the competitor gaps. **The fix plan is not a citation prediction either**: the related questions are a language model's estimate of an engine's fan-out (engines do not publish theirs), and a draft is a starting point to review, not text to paste unread. Before release we checked 30 questions on 10 real sites of different kinds (SaaS, local restaurant, e-commerce, docs, travel blog, agency, a German site, a big brand, a one-page portfolio, a site behind bot protection) and scored each answer on four points: right page, relevant passage, draft facts all found on the page, and specific "what to add". 27 of 30 had no point failed; the drafts' numbers and names were checked back against the pages. What still goes wrong: on a brand with many similar products the best page can be about a sibling product (asked about AppleCare+, the site's only priced page was about AppleCare One), a page whose content is only a form or only rendered by JavaScript cannot be read, and a draft may state a site's promotion terms as its general policy. Word matching can still pick a related page over the best one, and a page that is neither in a sitemap nor linked from the home page is not found; `pagesChecked` and `candidatesRanked` show what was compared, and `question | https://...` pins the page.

### Use with AI agents (MCP)

Connect Apify's MCP server (https://mcp.apify.com?tools=tidytools/geo-readiness-audit) to Claude, Cursor or any MCP client, then ask e.g. "Audit stripe.com and our site example.com for AI search readiness and tell me the top three fixes" or "Why would ChatGPT quote stripe.com instead of example.com for 'how much does a payment API cost', and what should we add?"

```json
{ "websites": ["example.com", "stripe.com"], "samplePages": 3, "targetQuestions": ["how much does a payment api cost"] }
```

Failed items are not charged and carry an `errorType`, so an agent can tell a blocked site from a network error.

### Use it from code and integrations

Run it from your own code with the Apify API. This call waits for the run and returns the results as JSON (replace `YOUR_TOKEN` with your Apify API token):

```bash
curl -X POST "https://api.apify.com/v2/acts/tidytools~geo-readiness-audit/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"websites":["https://www.python.org","https://stripe.com","https://excalidraw.com"]}'
```

The synchronous endpoint waits up to 5 minutes. For bigger runs, start the run with `POST https://api.apify.com/v2/acts/tidytools~geo-readiness-audit/runs` and read the dataset when it finishes, or use the `apify-client` package for JavaScript or Python.

**Schedules and integrations:** run it daily or weekly with Apify Schedules, get a webhook when a run finishes, or send the results to Zapier, Make, n8n, Google Sheets, Slack and other apps with Apify integrations. Results can be exported as JSON, CSV, Excel or XML.

### Tips

- Blocking **training** crawlers (GPTBot, Google-Extended, CCBot…) does not lower the score. Blocking **AI search and user-triggered** agents does, because it keeps you out of AI answers.
- Some sites refuse all requests from data centers. The audit then reads pages with a real browser and says so in the `plain-requests-refused` check; verified AI crawlers may still be treated differently by your firewall. If even a normal browser request is refused or challenged, the firewall check is marked "not measured" instead of guessing.
- A firewall answer of HTTP 429 (rate limit) is reported as inconclusive and does not count as a block.
- **Advanced settings**: *Plain HTTP requests from* chooses where sample pages are fetched (Apify's network first by default, to see the raw HTML crawlers get, then our servers, then a real browser). Add a *Proxy* to send the Apify-network requests (robots.txt, llms.txt, firewall test, raw HTML) through Apify Proxy; proxy usage is billed to your Apify account.
- To generate an llms.txt file, use **llms.txt Generator** (by TidyTools). For a per-bot robots.txt report across many sites and week-to-week change tracking, use **AI Crawler Access Checker** (by TidyTools). To check your structured data in depth, use **Schema Markup Validator** (by TidyTools).

### Limitations

- The firewall test sends requests with AI crawler user agents from Apify's servers. Real AI crawlers come from their own verified IP addresses, so a firewall can treat them differently in either direction; use the result as a signal to check your CDN or firewall settings.
- The audit samples pages; it does not crawl the whole site.
- A high score means AI systems can access and read your site. It does not guarantee that they will cite it. The optional AI Overview check shows real citations for your queries at the time of the run; AI Overviews vary by time, location and personalization. Other assistants (ChatGPT, Perplexity) are not queried, because that needs paid API access.
- Target-question analysis reads only the candidate pages per question (home page plus up to 5 others) as raw HTML; content that only appears after JavaScript runs is treated as missing, on purpose (most AI crawlers do not run JavaScript).
- Sitemap reading for target questions is capped (16 sitemap files, 50,000 URLs, 45 seconds per site); on a very large site, give the page to check with `question | https://...`.
- Fix-plan drafts and related questions come from a language model and are checked against your pages, but you should still review them; placeholders mark facts only you can supply.
- **Not a citation prediction.** The answer score and the fix plan say whether your page has a passage worth quoting and what to add; whether an engine retrieves and cites it also depends on search ranking, authority and mentions on other sites, which this Actor cannot see. ChatGPT and Perplexity are not queried.
- A site that refuses every request from our crawler (HTTP 403 even through a real browser) cannot be analysed: the row is free and carries a `crawler-refused` fix instead.
- Target questions are matched word by word, so they work in English, German, French, Spanish and other languages written with spaces. A question in Chinese, Japanese, Thai or another script without spaces between words gets a free row with `errorType: "unsupported_language"`.
- Only the first 3 MB of a page's HTML is read for target questions; on a very long single page (a full specification, a huge list) a passage further down is not seen.

### FAQ

**Does the Actor collect usage data?** The Actor sends one anonymous usage counter per run (input type, item count range, outcome) to improve the tool; no URLs, content or account data.

**What is a GEO audit, and what does it check?** GEO (Generative Engine Optimization, also called AEO) is SEO for AI answers: if AI crawlers are blocked or see an empty page, your site cannot be cited. This Actor checks whether AI search engines and assistants (ChatGPT, Perplexity, Claude, Google AI Overviews, Copilot) can reach, read and understand your website, gives it a GEO score from 0 to 100, lists the fixes that matter most and saves a shareable HTML report. A high score means AI systems can access and read your site; it does not guarantee that they will cite it.

**Can it show whether Google AI Overviews cite my site?** Yes, turn on **Check Google AI Overview citations** and enter up to 20 **AI Overview queries**: for each query, the result shows whether each audited site is cited and at which position. It uses Apify's official Google AI Overviews Scraper, which runs on your Apify account and bills by its own pricing; this Actor adds no fee. Other assistants (ChatGPT, Perplexity) are not queried.

**My robots.txt allows AI crawlers. Can my site still be invisible to AI search?** Yes. With **Test AI crawler user agents** on, the home page is requested as 8 AI crawlers and compared with a normal browser, to catch firewall or CDN rules that robots.txt does not show; bot challenge pages served with HTTP 200 count as blocked. The audit also reads the raw HTML of sample pages and flags JavaScript app shells, because most AI crawlers do not run JavaScript.

**What is answer engine optimization (AEO), and how does this Actor help?** AEO (also GEO) means making your pages the ones AI answer engines quote. Engines first retrieve pages, then lift the passage that answers best. This Actor checks both ends you control: whether AI crawlers can reach and read your pages (GEO audit), and, for each buyer question, whether your best page has a passage worth quoting and exactly what to add (fix plan).

**What happens if my input is wrong?** The run never fails because of the input. Each line that is not a website (a typo, `ftp://...`, a private or local address such as `localhost:3000`, `192.168.1.1` or `169.254.169.254`) gets its own free row with `errorType: "invalid_input"`, and the other sites are still audited; an empty input gives one free error row. Competitor pages may be written without `https://`; lines that are not page addresses are ignored and listed in the log and in `SUMMARY.ignoredCompetitorLines`. An unanswered question says why in `errorType`: `robots_disallowed`, `refused` (401/403), `not_found`, `unreachable` (DNS, SSL, Cloudflare 52x/530), `bot_challenge`, `unsupported` (not an HTML page) or `unsupported_language`; all of them are free.

**Does it respect robots.txt?** Yes. Every request names us (`TidyToolsGEOAudit`, with a link to this page), and sample pages and target-question pages are read only where robots.txt allows our crawler. A site whose robots.txt closes the home page to all crawlers is not read at all: you get a free row (`errorType: "robots_disallowed"`) with the AI crawler table and the fix. The one exception is the firewall test, which by design requests the home page with AI crawler user agents (GPTBot, ClaudeBot...) to see whether your firewall treats them differently; it is reported as a simulation. The Actor never requests private or local network addresses, also not when a page redirects to one.

**How much does a GEO audit cost?** Each audited website costs $0.03 and each extra sample page $0.004, so with the default 5 sample pages a site costs about $0.046, with no start fee. Each target question adds $0.01 per website (10 questions: $0.10). **Pages to sample per site** is the main cost control. Unreachable websites, sample pages that could not be read, invalid input lines, the HTML reports and the multi-site summary are free.

### Support

Open an issue in the **Issues** tab with the website URL. Issues are checked regularly.

Found this useful? A short review on the Store helps other users find it.

# Actor input Schema

## `websites` (type: `array`):

Websites to audit (home page or domain), e.g. https://example.com or example.com, one per line. The audit covers the site root: a path in a URL is noted but not audited separately. Lines that are not websites get an error row and are not charged. Add your competitors to compare: multi-site runs also get a ranking row and a comparison report.

## `samplePages` (type: `integer`):

How many pages (including the home page) are analyzed per site. Pages are picked from different sections of the sitemap or home page links. Each page after the home page costs $0.004.

## `targetQuestions` (type: `array`):

Up to 20 questions your buyers ask ChatGPT, Perplexity or Google, e.g. "bulk audio transcription api" or "how to check if GPTBot is blocked". For each question and each website, the Actor finds the page and passage an answer engine would most likely quote, scores it 0-100 and writes concrete fixes (what to add or rewrite, and where). Add " | https://..." after a question to check one specific page. Questions in English, German, French, Spanish and other languages written with spaces between words; Chinese, Japanese and Thai questions get a free error row. $0.01 per question per website; questions with no readable page are free.

## `competitorPages` (type: `array`):

Pages that answer engines cite (or that rank) for your questions, e.g. the sources ChatGPT or Perplexity showed you. A plain URL is compared on every question; "question | https://..." on that question only. Each site's answers are ranked against them and fixes that competitors already do better are moved up. Other websites in the same run are compared automatically. No extra charge.

## `testUserAgents` (type: `boolean`):

Request the home page as GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, Claude-SearchBot, Claude-User, PerplexityBot and Perplexity-User to detect CDN or firewall rules that block AI crawlers even when robots.txt allows them. Bot challenge pages served with HTTP 200 (Cloudflare "Just a moment", Turnstile, CAPTCHA) count as blocked.

## `htmlReport` (type: `boolean`):

Save a shareable, self-contained HTML report per site (REPORT-<host>.html) and REPORT.html (overview for multi-site runs) in the key-value store. No extra charge.

## `summaryRow` (type: `boolean`):

Multi-site runs add one extra dataset row (type "summary") with the ranking and common issues. Turn off to keep the dataset to one row per site (for Google Sheets, n8n or Make); the summary is still saved in the SUMMARY record.

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

How many websites are audited at the same time.

## `httpVia` (type: `string`):

Some sites block requests from data centers. Auto retries from a second network before falling back to a real browser. Sample pages are read from Apify's network first (to see the raw HTML crawlers get); robots.txt, llms.txt and the firewall test always use Apify's network. If those routes are refused, Auto also tries our second server (Oracle, different IP) before a real browser.

## `siteTimeoutSecs` (type: `integer`):

A website whose audit (robots.txt, firewall test, sitemap, llms.txt and sample pages, all retries and the browser fallback included) takes longer than this is reported with errorType "timeout" and not charged. The optional AI Overview check is not part of it. 0 = no limit.

## `pagesPerQuestion` (type: `integer`):

How many pages of each site are checked per target question: the home page plus the sitemap or home-page URLs whose path matches the question best. Ignored for questions pinned to a URL.

## `semanticMatch` (type: `boolean`):

Also compare the question and passages with a text-embedding model, so a passage that answers in other words ("speech to text" for "transcription") is credited. Off = word matching only. No extra charge.

## `fixPlan` (type: `boolean`):

For each target question: 2-4 related sub-questions that answer engines also search (query fan-out) with how well your pages cover each, a rewrite draft of your best page's opening passage (answer first, built only from facts already on your page; missing facts are marked \[ADD: ...], never invented) and a short "what to add" list. Uses a language model on our backend; included in the per-question price. Off = analysis and fixes only (faster).

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

Only used for requests sent from Apify's network (robots.txt, llms.txt, the firewall test and raw page HTML). Apify proxy usage is billed to your Apify account.

## `checkAiOverviews` (type: `boolean`):

For each query below, fetch Google's AI Overview with Apify's official Google AI Overviews Scraper (apify/google-ai-overviews-scraper) and report whether each audited site is cited and at which position. That Actor runs on your Apify account and bills by its own pricing (about $0.003 per AI Overview on the Free plan, September 2026); this Actor adds no fee. One scraper run covers all sites, capped at $0.01 per query (at least $0.05) and 10 minutes.

## `aiOverviewQueries` (type: `array`):

Up to 20 search queries your customers would ask Google, e.g. "best crm for small business". Used only with "Check Google AI Overview citations". Not every query shows an AI Overview.

## Actor input object example

```json
{
  "websites": [
    "https://plausible.io"
  ],
  "samplePages": 5,
  "targetQuestions": [
    "how much does plausible analytics cost",
    "is plausible analytics gdpr compliant"
  ],
  "competitorPages": [
    "how much does plausible analytics cost | https://usefathom.com/pricing"
  ],
  "testUserAgents": true,
  "htmlReport": true,
  "summaryRow": true,
  "maxConcurrency": 5,
  "httpVia": "auto",
  "siteTimeoutSecs": 300,
  "pagesPerQuestion": 4,
  "semanticMatch": true,
  "fixPlan": true,
  "checkAiOverviews": false
}
```

# Actor output Schema

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

No description

## `results` (type: `string`):

No description

## `full` (type: `string`):

No description

## `summary` (type: `string`):

No description

## `details` (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 = {
    "websites": [
        "https://plausible.io"
    ],
    "targetQuestions": [
        "how much does plausible analytics cost",
        "is plausible analytics gdpr compliant"
    ],
    "competitorPages": [
        "how much does plausible analytics cost | https://usefathom.com/pricing"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("tidytools/geo-readiness-audit").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "websites": ["https://plausible.io"],
    "targetQuestions": [
        "how much does plausible analytics cost",
        "is plausible analytics gdpr compliant",
    ],
    "competitorPages": ["how much does plausible analytics cost | https://usefathom.com/pricing"],
}

# Run the Actor and wait for it to finish
run = client.actor("tidytools/geo-readiness-audit").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "websites": [
    "https://plausible.io"
  ],
  "targetQuestions": [
    "how much does plausible analytics cost",
    "is plausible analytics gdpr compliant"
  ],
  "competitorPages": [
    "how much does plausible analytics cost | https://usefathom.com/pricing"
  ]
}' |
apify call tidytools/geo-readiness-audit --silent --output-dataset

```

## MCP server setup

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

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/mcmwUZfsZ9rTlgMqr/builds/yAkICA46LVr1BqCrM/openapi.json
