# SEO & AI Search (GEO) Audit: llms.txt, AI Bots, Schema (`dodiabak/seo-geo-audit`) Actor

Audit any website for Google SEO and AI search (ChatGPT, Perplexity, Claude, Gemini, AI Overviews): 50+ checks, AI crawler access in robots.txt, llms.txt, schema.org, broken links, SEO + GEO scores and prioritized fixes. $15 per 1,000 pages.

- **URL**: https://apify.com/dodiabak/seo-geo-audit.md
- **Developed by:** [Dodi Abak](https://apify.com/dodiabak) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

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

## SEO & AI Search (GEO) Audit: llms.txt, AI Bots, Schema

**Find out why a website doesn't rank in Google, and why ChatGPT, Perplexity, Claude, Gemini and Google AI Overviews don't cite it, with a fix for every problem.** Give it a homepage or a list of URLs. For every page it returns an **SEO score**, a **GEO score** (Generative Engine Optimization, meaning AI search readiness) and a ranked list of issues, each with a plain-English fix. One extra row per site covers robots.txt rules for **18 AI crawlers**, **llms.txt**, the XML sitemap and the top fixes across the whole site.

**$15 per 1,000 audited pages** ($0.015 per page). Site summaries, failed pages and bot-challenge pages are free. No browser and no proxies are needed, so a 10-page audit takes about 5–10 seconds.

### What it checks (50+ checks)

| Area | Checks |
| --- | --- |
| **AI search (GEO)** | robots.txt access for OAI-SearchBot, ChatGPT-User, GPTBot, Claude-SearchBot, Claude-User, ClaudeBot, PerplexityBot, Perplexity-User, Google-Extended, Googlebot, Bingbot, Applebot-Extended, meta-externalagent, Amazonbot, CCBot, Bytespider, DuckAssistBot and MistralAI-User (search bots and training-only bots reported separately) · `/llms.txt` and `/llms-full.txt` presence and format · **content visible without JavaScript** (most AI crawlers don't render JS) · schema.org entities · author and dates (E-E-A-T) · question-style headings and FAQ · lists and tables · summary lead paragraph |
| **Indexing** | HTTP status, redirect chains, `noindex` in meta **and** `X-Robots-Tag`, canonical (missing, multiple, pointing elsewhere), XML sitemap (index-aware, URL count), Sitemap line in robots.txt, Googlebot/Bingbot blocked, robots.txt 5xx |
| **Metadata** | Title and meta description presence and length, `lang`, viewport, charset, favicon |
| **Structure** | H1 count, heading-level skips, JSON-LD validity and types, microdata |
| **Content** | Word count, thin content, reading time |
| **Social** | Open Graph (title, description, image), Twitter card |
| **Links** | Internal and external link counts, empty anchors, and optional **broken-link checking** (404/410/5xx/dead hosts, HEAD then GET to avoid false positives) |
| **Performance** | Time to first byte, HTML weight, compression, lazy-loading of images |
| **Security** | HTTPS, mixed content, HSTS |
| **International** | hreflang code validity and self-reference |

Every issue has an `id`, a `severity` (`critical`, `high`, `medium` or `low`), a `category`, a `message` and a `fix`. Scores start at 100 and drop by 20, 10, 4 or 1 points per issue, depending on severity.

### Why this one?

| | This Actor | Typical SEO audit Actors |
| --- | --- | --- |
| AI search readiness | **GEO score**, 18 AI bots, llms.txt, JS-only content detection | Classic SEO only |
| Output | **Prioritized fixes** in plain English plus a site summary | Raw metrics |
| Price | **$0.015 per page**, site summary free | $0.01–$0.04 per page |
| Speed | HTTP-only, ~1 s per page | Headless browser, slow |
| Agent-friendly | Compact `topFixes` arrays, stable issue ids, small input | Large, noisy output |

### Use cases

- **SEO and GEO agencies:** run a client audit in seconds, export to CSV or Sheets, and send the fix list.
- **AI agents (MCP):** "Audit acme.com for AI search" works as a single call. The agent gets scores and `topFixes` it can act on or explain.
- **Site owners:** check that ChatGPT, Perplexity and Google AI Overviews can actually read and cite your site, and whether robots.txt blocks them by accident.
- **Monitoring:** schedule weekly runs to catch `noindex` mistakes, broken links, missing canonicals or a robots.txt change that blocks AI search.
- **Lead generation for agencies:** audit prospects' sites and lead with concrete findings.

### How to use it

1. Enter one or more URLs in **Websites or pages to audit**. A homepage is enough.
2. Set **Max pages per site**. Pages are picked from the sitemap first, then from internal links. Set it to 1, or turn off **Discover more pages**, to audit only the exact URLs you entered.
3. Optional: turn on **Check for broken links**.
4. Click **Start**. Use the **Pages**, **Site summaries**, **All issues** or **AI search readiness** view, or export JSON, CSV or Excel.

#### Input example

```json
{
  "startUrls": [{ "url": "https://apify.com" }, { "url": "https://example.com" }],
  "maxPagesPerSite": 10,
  "checkBrokenLinks": true
}
```

#### Output example: a page row (shortened)

```json
{
  "type": "page",
  "url": "https://example.com/",
  "status": "ok",
  "seoScore": 58,
  "geoScore": 82,
  "overallScore": 70,
  "critical": 0, "high": 2, "medium": 6, "low": 2,
  "topFixes": [
    "[high] Add a 70–160 character summary. Search snippets and AI answers often quote it.",
    "[high] Add one H1 that states the page topic in plain words.",
    "[medium] Add <link rel=\"canonical\" href=\"…\"> pointing to the preferred URL to avoid duplicate-content splits."
  ],
  "issues": [
    { "id": "description-missing", "severity": "high", "category": "metadata", "message": "Missing meta description.", "fix": "Add a 70–160 character summary. Search snippets and AI answers often quote it." }
  ],
  "title": "Example Domain",
  "wordCount": 25,
  "structuredDataTypes": [],
  "jsRenderedSuspected": false,
  "aiBotsBlockedHere": []
}
```

#### Output example: a site summary row (free)

```json
{
  "type": "site",
  "url": "https://vite.dev",
  "pagesAudited": 3,
  "siteSeoScore": 91,
  "siteGeoScore": 94,
  "blockedAiSearchBots": [],
  "blockedAiTrainingBots": [],
  "llmsTxt": { "found": true, "url": "https://vite.dev/llms.txt", "bytes": 3528, "looksValid": true, "linkCount": 6 },
  "aiBots": [{ "agent": "OAI-SearchBot", "owner": "OpenAI", "purpose": "search", "allowedOnHomepage": true }],
  "topFixes": [
    "[medium] (3 pages) Add JSON-LD: Organization + WebSite on the homepage, Article/BlogPosting on posts, …",
    "[low] (site-wide) Add /robots.txt with a Sitemap: line and explicit rules for AI crawlers."
  ]
}
```

Pages that can't be audited get a free row such as `{"status": "error", "errorCode": "HTTP_404"}`. The other error codes are `TIMEOUT`, `UNREACHABLE`, `NOT_HTML`, `BLOCKED` (the site served a bot challenge) and `INVALID_URL`.

A run summary is also saved to the key-value store record `OUTPUT`.

### Main output fields (page rows)

| Field | Meaning |
| --- | --- |
| `seoScore`, `geoScore`, `overallScore` | 0–100 scores |
| `issues[]`, `topFixes[]` | All issues, most severe first, and the 5 most important fixes |
| `title`, `metaDescription`, `h1`, `headingCounts` | On-page basics |
| `canonical`, `canonicalIsSelf`, `indexable`, `metaRobots`, `xRobotsTag` | Indexing signals |
| `structuredDataTypes`, `author`, `datePublished`, `dateModified`, `hasFaqSchema`, `questionHeadings` | Entity and E-E-A-T signals for AI answers |
| `wordCount`, `lists`, `tables`, `jsRenderedSuspected` | Content and AI readability |
| `aiBotsBlockedHere` | AI crawlers that robots.txt blocks for this URL |
| `internalLinks`, `externalLinks`, `brokenLinks[]` | Links (broken links only when checking is on) |
| `ttfbMs`, `loadMs`, `htmlBytes`, `redirects`, `https` | Speed and security |

### Pricing

Pay per event: **$0.015 per audited page** ($15 per 1,000). You are never charged for site summaries, error rows or bot-challenge pages. The run stops cleanly at your **Max cost per run**.

Examples:

- 1 homepage: $0.015.
- A 50-page site audit: $0.75.
- 100 client homepages: $1.50.

### FAQ

**What is GEO?** Generative Engine Optimization: making your content easy for AI answer engines (ChatGPT search, Perplexity, Claude, Gemini, Google AI Overviews, Copilot) to crawl, understand and cite. The GEO score covers crawler access, content that is readable without JavaScript, structured data, author and date signals, and answer-friendly formatting.

**Should I block GPTBot or CCBot?** That's your call. They are *training* crawlers. This Actor reports them separately and only flags blocked *search* and *user-fetch* bots as a problem, because blocking those removes you from AI answers.

**Does it run JavaScript?** No, and that's deliberate. Most AI crawlers don't run JavaScript either, so the audit sees what they see. If your content only appears after JavaScript runs, the `content-js-only` issue tells you.

**Is it polite to websites?** Yes. It identifies itself with the user agent `SEO-GEO-Audit/1.0`, uses at most 3 parallel requests per site, and skips discovered pages that robots.txt disallows. URLs you enter are always audited.

**Can I use it from an AI agent?** Yes. It works through the Apify MCP server and the API. Input is a list of URLs, and the output has stable issue ids and short `topFixes`.

### Limitations

- Core Web Vitals (LCP, CLS, INP) need a real browser and are not measured. TTFB, HTML weight and compression are reported instead.
- Sites behind aggressive bot protection may return a challenge page. You get a free `BLOCKED` row.
- Keyword rankings and backlinks are out of scope.

### Support

Found a false positive or want a new check? Open an issue on the Actor's **Issues** tab. Fixes usually ship within a few days.

# Changelog

This Actor's version history is a separate document: https://apify.com/dodiabak/seo-geo-audit/changelog.md

# Actor input Schema

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

Homepages or specific URLs. Several URLs on the same site are audited as one site (one robots.txt / llms.txt / sitemap check).

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

Pages audited (and charged) per site, including the URLs you entered. Set 1 to audit only the exact URL.

## `crawl` (type: `boolean`):

Find more pages of the same site from the sitemap and internal links, up to the max pages. Off = audit only the URLs you entered.

## `useSitemap` (type: `boolean`):

Queue sitemap URLs before links found on pages.

## `checkBrokenLinks` (type: `boolean`):

Test every link on each audited page (HEAD, then GET) and report 404/410/5xx and dead hosts. Slower; no extra charge.

## `maxLinksPerPage` (type: `integer`):

Only used with broken-link checks.

## `includeSiteSummary` (type: `boolean`):

One free row per site: site SEO and GEO scores, robots.txt AI-bot access table, llms.txt, sitemap, and the top fixes across all pages.

## `respectRobotsTxt` (type: `boolean`):

Skip discovered pages that robots.txt disallows for all bots. The URLs you enter are always audited.

## `requestTimeoutSecs` (type: `integer`):

Per request.

## `concurrency` (type: `integer`):

Each site also uses up to 3 parallel requests.

## `includeFailedPages` (type: `boolean`):

Add a free error row for pages that return 4xx/5xx, time out or are not HTML.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://apify.com"
    }
  ],
  "maxPagesPerSite": 10,
  "crawl": true,
  "useSitemap": true,
  "checkBrokenLinks": false,
  "maxLinksPerPage": 100,
  "includeSiteSummary": true,
  "respectRobotsTxt": true,
  "requestTimeoutSecs": 25,
  "concurrency": 5,
  "includeFailedPages": true
}
```

# Actor output Schema

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

No description

## `sites` (type: `string`):

No description

## `report` (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://apify.com"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("dodiabak/seo-geo-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 = { "startUrls": [{ "url": "https://apify.com" }] }

# Run the Actor and wait for it to finish
run = client.actor("dodiabak/seo-geo-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 '{
  "startUrls": [
    {
      "url": "https://apify.com"
    }
  ]
}' |
apify call dodiabak/seo-geo-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dodiabak/seo-geo-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/mecfll9baiGx4FcYw/builds/iLVg7jCHWONkyCCvL/openapi.json
