# AI Search Readiness Audit - GEO Check for ChatGPT & Perplexity (`appdata-labs/ai-search-readiness-audit`) Actor

Score any web page 0–100 for Generative Engine Optimization: can ChatGPT, Claude, Perplexity and Google AI Overviews reach, read and cite it? Checks AI crawler access, llms.txt, noindex, schema, question headings, statistics, quotes and cited sources, and lists fixes in order. No LLM.

- **URL**: https://apify.com/appdata-labs/ai-search-readiness-audit.md
- **Developed by:** [Kobkoon Janngam](https://apify.com/appdata-labs) (community)
- **Categories:** Agents, MCP servers, SEO tools
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.60 / 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.
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

## AI Search Readiness Audit — GEO check for ChatGPT, Perplexity and AI Overviews

**Score any web page 0–100 on whether AI search engines can reach it, read it and cite it, and get the fixes in order of impact.** Generative Engine Optimization (GEO) for ChatGPT, Claude, Perplexity and Google AI Overviews, checked by fixed rules. No LLM is involved and you need no API key.

**Quick start:** press **Start** with no configuration. It audits three sample pages so one run shows the whole output. Then paste your own URLs: your blog posts, product pages, a competitor's page that keeps getting cited.

Built for SEO and content teams, agencies running GEO audits for clients, and AI agents that need a page's AI-readiness as data.

### Sample output

From real runs on 31 October 2026:

| url | score | biggest fix |
|---|---|---|
| stripe.com | 88 | Publish a date (no dateModified or `<time>` found) |
| en.wikipedia.org/wiki/Generative_engine_optimization | 84 | Add /llms.txt |
| developers.google.com/search/docs/appearance/ai-features | 71 | Add Article or FAQPage JSON-LD (only breadcrumbs found) |
| www.theverge.com | 71 | robots.txt blocks ChatGPT-User, Claude-SearchBot, Claude-User, PerplexityBot and Perplexity-User |

Each row shows every check with its points (`pts`), its maximum (`max`) and the value it was judged on, so you can see exactly where each point went:

```json
{
  "url": "https://www.theverge.com/", "finalUrl": "https://www.theverge.com/",
  "status": "ok", "httpStatus": 200, "score": 71, "title": "The Verge",
  "checks": {
    "aiCrawlerAccess": { "pts": 8, "max": 20, "value": { "blocked": ["ChatGPT-User", "Claude-SearchBot", "Claude-User", "PerplexityBot", "Perplexity-User"] }, "trainingBlocked": 6 },
    "indexable": { "pts": 10, "max": 10, "value": "yes" },
    "llmsTxt": { "pts": 0, "max": 5, "value": "missing" },
    "structuredData": { "pts": 10, "max": 10, "value": ["NewsMediaOrganization", "ImageObject", "Person", "WebSite"] },
    "statistics": { "pts": 8, "max": 8, "value": { "count": 81, "per1000Words": 29 } },
    "quotations": { "pts": 5, "max": 5, "value": 10 },
    "citations": { "pts": 7, "max": 7, "value": { "links": 21, "domains": 17 } },
    "…": "…"
  },
  "fixes": [
    "robots.txt blocks ChatGPT-User, Claude-SearchBot, Claude-User, PerplexityBot, Perplexity-User from this page, so those answer engines cannot read or cite it. …",
    "Add /llms.txt: a short Markdown index of your key pages for language models (llmstxt.org).",
    "…"
  ],
  "note": null, "checkedAt": "2026-10-31T15:30:12.000Z"
}
```

Rows run 1.5–2 KB and are capped well under 4 KB, small enough to hand straight to an AI agent. At most 8 fixes per page, biggest point loss first.

### The checks

| Check | Points | Full marks when |
|---|---|---|
| `aiCrawlerAccess` | 20 | robots.txt lets the answer crawlers read this path: OAI-SearchBot, ChatGPT-User, Claude-SearchBot, Claude-User, PerplexityBot, Perplexity-User, Googlebot, Bingbot |
| `indexable` | 10 | No `noindex`, `nosnippet` or `max-snippet:0` in a meta tag or `X-Robots-Tag` header |
| `structuredData` | 10 | JSON-LD or microdata with a content type: Article, FAQPage, Product, Organization… |
| `statistics` | 8 | 8+ figures (percentages, amounts, counts) per 1,000 words |
| `citations` | 7 | 3+ links to outside sources in the main content (navigation, footer and social links don't count) |
| `llmsTxt` | 5 | `/llms.txt` exists as text |
| `questionHeadings` | 5 | 2+ H2/H3 headings phrased as questions |
| `quotations` | 5 | 2+ quotations (`<blockquote>`, `<q>` or a quoted sentence) |
| `freshness` | 5 | A published or modified date in JSON-LD, meta tags or `<time>` |
| `contentDepth` | 5 | 300+ words of main content |
| `title`, `metaDescription` | 4 + 4 | Present, 15–65 and 50–170 characters |
| `canonical`, `singleH1`, `openGraph`, `answerFirst` | 3 each | Canonical link; exactly one H1; og:title, og:description and og:image; first paragraph of 80 words or fewer |

Training crawlers (GPTBot, ClaudeBot, Google-Extended, CCBot…) **cost no points**. Blocking model training is a legitimate choice and does not keep a page out of AI answers. The row still counts them in `trainingBlocked`.

### Why these checks

The content checks come from the GEO study by Aggarwal et al., [arXiv 2311.09735](https://arxiv.org/abs/2311.09735) ([full text](https://arxiv.org/html/2311.09735v3)). It tested nine ways of rewriting a page across thousands of queries. Adding quotations, statistics and cited sources did best, raising a page's visibility in generative-engine answers by up to 40%. Keyword stuffing did no better than leaving the page alone. So this audit counts figures, quotes and outbound sources, and never counts keywords.

Access comes first because a page an engine cannot fetch cannot be cited. Each vendor documents which crawler feeds its answers: [OpenAI](https://developers.openai.com/api/docs/bots), [Anthropic](https://support.claude.com/en/articles/8896518-does-anthropic-crawl-data-from-the-web-and-how-can-site-owners-block-the-crawler) and [Perplexity](https://docs.perplexity.ai/guides/bots). Google says AI Overviews draw on its normal Search index, with no extra requirements, and that `nosnippet` limits them ([AI features and your website](https://developers.google.com/search/docs/appearance/ai-features)).

### Input

| Field | Default | Notes |
|---|---|---|
| `urls` | 3 sample pages | Full page URLs; a bare domain audits its home page. Up to 200. A bad entry is skipped with a reason in the log and in `SUMMARY`; the rest still run. |

### FAQ

**What does it fetch?** The page, the site's `robots.txt` and its `llms.txt`. Nothing else. It follows at most 5 redirects and waits at most 15 seconds per request.

**Does it run JavaScript?** No. It reads the HTML the server sends, which is also what most AI crawlers read. Content that only appears after scripts run is invisible to them too.

**Is the score what ChatGPT thinks of my page?** No. Nobody outside OpenAI can know that. The score measures the things published research and the vendors' own documentation say matter, the same way every time, so you can track changes.

**What if robots.txt disallows the page?** If it disallows generic crawlers on that path, the page is not fetched. The row says so and you are not charged.

**What am I charged for?** One event per page fetched and scored. Pages that are unreachable, not HTML, or return an error are returned with the reason and not charged.

### Works well with

Same publisher, same output conventions, same honest-failure rules:

- [**ai-crawler-access-checker**](https://apify.com/appdata-labs/ai-crawler-access-checker) — Which sites block GPTBot, ClaudeBot and PerplexityBot, and which publish llms.txt.

### Disclaimer

This Actor is unofficial and not affiliated with OpenAI, Anthropic, Perplexity, Google or any AI vendor, and the score is not theirs. It scrapes no website catalogue and calls no third-party API: it fetches only the pages you name, plus each site's robots.txt and llms.txt, identifying itself honestly in its user agent and honouring robots.txt.

# Actor input Schema

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

The pages to audit, as full URLs (https://example.com/blog/post). A bare domain audits its home page. Up to 200 per run. An entry that is not a public web page is skipped with a reason; the rest still run.

## Actor input object example

```json
{
  "urls": [
    "https://example.com/blog/how-to-choose-a-crm",
    "example.com"
  ]
}
```

# Actor output Schema

## `rows` (type: `string`):

One row per page: the 0–100 score, every check with its points and the value it was judged on, and the fixes in order.

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

Pages requested, audited and skipped (with reasons), the average score, and which checks failed most often.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://en.wikipedia.org/wiki/Generative_engine_optimization",
        "https://developers.google.com/search/docs/appearance/ai-features",
        "https://www.theverge.com/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("appdata-labs/ai-search-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 = { "urls": [
        "https://en.wikipedia.org/wiki/Generative_engine_optimization",
        "https://developers.google.com/search/docs/appearance/ai-features",
        "https://www.theverge.com/",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("appdata-labs/ai-search-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 '{
  "urls": [
    "https://en.wikipedia.org/wiki/Generative_engine_optimization",
    "https://developers.google.com/search/docs/appearance/ai-features",
    "https://www.theverge.com/"
  ]
}' |
apify call appdata-labs/ai-search-readiness-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,appdata-labs/ai-search-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/qwjd7IKAhVSH21TYn/builds/IduK1qWxpdmahtT0Q/openapi.json
