# AI SEO, GEO and AEO Audit - AI Search Visibility Score (`s-r/geo-audit`) Actor

Audit any URL for AI search visibility. Fetches your page as GPTBot, ClaudeBot and PerplexityBot to show what each actually receives, checks robots rules, llms.txt, schema and content depth, and reports whether Google's AI answers name your brand. One weighted score, with a fix per finding.

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

## Pricing

from $25.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

## AI SEO, GEO and AEO Audit - AI Search Visibility Score

Audit any URL for AI search visibility and get one weighted score, the evidence behind every finding, and the fix ordered by how much it costs you to make. This is generative engine optimization (GEO) and answer engine optimization (AEO) measured rather than guessed: the audit fetches your page the way each AI crawler fetches it, and reports what each one actually received.

Most AI SEO audits read your `robots.txt` and tell you which crawlers are allowed. That is a statement about a text file. This one asks the page directly, as GPTBot, OAI-SearchBot, ClaudeBot, PerplexityBot, Google-Extended and Googlebot, and compares each response against what a browser gets.

### What you get

- **One weighted score, 0-100**, across six dimensions: technical SEO (25), AI crawler access (20), content depth (20), brand presence in AI answers (13), schema markup (12) and referring domains (10).
- **A per-crawler result for your page**: HTTP status, bytes, visible text, and a verdict of same, thinner, refused or error against the browser baseline.
- **Robots and llms.txt rules** for the eight crawlers that matter, plus whether `/llms.txt` exists at all.
- **Whether Google's AI answers name your brand**, how often, whether they link you, and which competitors get named where you do not.
- **Every finding with its fix**, tagged critical, warning or notice, ordered by severity and then by how cheap the fix is.
- **Schema markup coverage**: which types the page declares, which blocks failed to parse, and whether Open Graph is complete.
- **Referring domains and domain score** as the external-corroboration half of E-E-A-T.
- **The denominator, always.** Every row carries `dimensions_measured`, `dimensions_skipped` and `weight_basis`, so you can see what the score was computed over.

### Why audit AI search visibility

Search traffic is being intercepted before it reaches you. An assistant answers the question, names two or three brands, and links one of them. If your page cannot be read by the crawler that feeds the assistant, none of your other optimization work reaches that surface, and nothing in your analytics will tell you why.

The gap that usually causes it is invisible from the outside. A site can allow every AI crawler in `robots.txt` and still refuse them at the edge, because the CDN or WAF rule that stops scrapers does not know the difference between a scraper and the crawler that would have quoted you. We measured four sites while building this: one served a browser 1.21 MB and returned a 257-byte refusal to GPTBot, ClaudeBot and PerplexityBot while letting Google's AI crawler through. Its `robots.txt` was itself unreadable, so any audit that only reads rules would call that site open.

The other half is whether the answer names you at all. That is not a technical property of your page, it is a question about what the assistant says, so the only way to know is to ask it and read the answer.

### Input

| Field | Type | Required | Default | What it does |
|---|---|---|---|---|
| `url` | string | yes | — | The public page to audit |
| `brand` | string | no | — | Brand to look for inside Google's AI answers |
| `prompts` | array | no | generated | The questions to ask. Empty means three are generated from the brand |
| `competitors` | array | no | — | Named rivals to count in the answers; others are detected anyway |
| `check_ai_answers` | boolean | no | `true` | Ask the questions and report brand presence |
| `check_authority` | boolean | no | `true` | Measure referring domains |
| `country` | string | no | `us` | Two-letter country for the fetch and the answers |
| `language` | string | no | `en` | Two-letter language for the answers |
| `timeout` | integer | no | `20` | Seconds each crawler fetch may take |
| `concurrency` | integer | no | `4` | How many parts run at once |

### Output

One row per audited URL.

```json
{
  "url": "https://example.com/pricing",
  "domain": "example.com",
  "score": 72,
  "grade": "needs work",
  "dimension_scores": { "technical": 92, "ai_access": 50, "content": 84, "schema": 75, "brand": 46, "authority": 70 },
  "dimension_weights": { "technical": 25, "ai_access": 20, "content": 20, "schema": 12, "brand": 13, "authority": 10 },
  "dimensions_measured": ["ai_access", "authority", "brand", "content", "schema", "technical"],
  "dimensions_skipped": [],
  "weight_basis": 100,
  "scored": true,
  "issues_critical": 1,
  "issues_warning": 3,
  "issues_notice": 2,
  "ai_crawler_matrix": {
    "baseline_ok": true,
    "baseline_text_bytes": 36405,
    "ai_agents_served": ["Google-Extended"],
    "ai_agents_not_served": ["GPTBot", "OAI-SearchBot", "ClaudeBot", "PerplexityBot"],
    "ai_agents_served_thinner": [],
    "verification_unknown": true
  },
  "findings": [
    {
      "severity": "critical",
      "code": "ai_crawler_not_served",
      "message": "A browser is served this page but GPTBot, OAI-SearchBot, ClaudeBot, PerplexityBot received an error or an empty shell.",
      "fix": "Confirm with your CDN or WAF whether these crawlers are refused.",
      "effort": "config"
    }
  ],
  "prompts_requested": 2,
  "ai_answers_read": 3,
  "duration_seconds": 41.2
}
```

### Use cases

**An SEO lead losing traffic to AI answers.** You can see impressions falling and no technical fault in Search Console. This tells you whether the assistants can read the page at all, and if they can, whether they name you when asked your category's questions. Those are two different problems with two different fixes, and conflating them wastes a quarter.

**An agency running a GEO audit for a client.** You need a defensible number and a list a developer can act on, not a screenshot of a dashboard. Every finding carries the measurement that produced it and a fix tagged by effort, so the configuration changes separate cleanly from the content work. Run it across a client's key templates and the pattern shows up immediately.

**A developer who owns the CDN rules.** Bot-blocking rules are usually written once, broadly, and never revisited. This tells you precisely which declared crawlers your edge refuses, so you can decide deliberately rather than discovering it a year later. It also tells you when the result is ambiguous, which matters if you run crawler verification.

**A founder checking whether the company exists to an assistant.** Ask the three questions your buyers ask. The audit reports whether the brand is named, whether it is linked, and which competitors are named instead, with their mention counts. That is a clearer read on positioning than a rank tracker gives.

### How it compares

| | This actor | Typical GEO audit tool |
|---|---|---|
| Crawler access | Fetches the page as each crawler and reports what arrived | Reads `robots.txt` and reports the rules |
| Ambiguity | Flags `verification_unknown` when a refusal could be crawler verification | Reports a block |
| Missing data | Leaves the dimension out of the denominator | Scores it zero |
| Brand in AI answers | Asks the questions and reads the answers | Usually a separate product |
| Output | Structured rows with evidence and fixes | A score and a PDF |

The honest gaps: this does not render JavaScript, so a page whose content only appears after a script runs is reported as thin, which is exactly what a crawler without a renderer sees but is not what a user sees. It also audits one URL per run rather than crawling a whole site.

### Pricing

**$0.025 per page audited, plus $0.02 per AI answer read.** A run that audits one page and checks three AI answers is **$0.086**, including the $0.001 to start the run. That is the whole price: no per-crawler charge, no separate fee for the sources behind each dimension.

You are only charged for what came back. A question the AI did not answer is free, and switching `check_ai_answers` off leaves a page-only audit at **$0.026**, crawler matrix included.

### Limits and gotchas

- **One URL per run.** Audit your key templates rather than a whole site.
- **Public pages only.** A login-walled page cannot be read by a crawler and cannot be audited here.
- **No JavaScript rendering.** Content injected by a script is reported as absent, because that is what a crawler without a renderer receives.
- **Crawler results are measured from a datacentre address.** A site that verifies crawlers by reverse DNS will refuse an unverified caller and still serve the real bot. When that pattern appears the row sets `verification_unknown` and says so rather than calling it a block.
- **The brand check needs a brand name.** Without one it is skipped, and the score is computed over the remaining dimensions rather than marking the page down.
- **A skipped dimension is not a zero.** If a part of the audit cannot run, `weight_basis` drops below 100 and `dimensions_skipped` names it.
- **AI answers vary by country and language.** Set both to the market you sell in; the defaults are `us` and `en`.

### FAQ

**Can I check if ChatGPT can read my website?**
Yes. The audit requests your page as GPTBot and OAI-SearchBot and reports the status, the bytes and the visible text each received, compared against a browser.

**What is the difference between GEO, AEO and SEO?**
SEO optimises for a ranked list of links. GEO and AEO optimise for being named and cited inside a generated answer. They overlap heavily on the technical side, which is why this audit scores both together rather than pretending they are separate disciplines.

**Does robots.txt control whether AI crawlers can read my site?**
It is the part you control by writing a rule, but it is not the only gate. Your CDN or WAF can refuse a crawler regardless of what the rules file says, and that is the case this audit exists to catch.

**Do I need an llms.txt file?**
It is an emerging convention rather than a requirement, and its absence is reported as a notice, not a fault. The audit checks whether one exists and is readable.

**Why does my score say it was computed over 87 points?**
Because one dimension could not be measured and was left out of the denominator instead of being scored zero. `dimensions_skipped` names it. Scoring a missing measurement as zero would mark your page down for something that was never tested.

### Related Actors

- [Technical SEO Audit](https://apify.com/s-r/seo-audit-scraper) for the on-page, schema, robots and DNS layer on its own
- [AI Brand Visibility Tracker](https://apify.com/s-r/ai-brand-visibility) for tracking brand presence in Google's AI answers over time
- [Backlinks Checker](https://apify.com/s-r/backlinks-checker) for referring domains and anchor detail

# Actor input Schema

## `url` (type: `string`):

The public page to audit. Any public URL: a homepage, a product page, an article. Private and login-walled pages cannot be read by a crawler and cannot be audited here either.

## `brand` (type: `string`):

The brand to look for inside Google's AI answers. Leave empty to skip that check: the score then reports on the dimensions it did measure rather than marking the page down.

## `prompts` (type: `array`):

The questions your buyers ask. Left empty, three are generated from the brand name. Each question is one AI answer fetched and one charged prompt.

## `competitors` (type: `array`):

Named rivals to count inside the AI answers. Rivals are also detected automatically, so this is for the ones you already care about.

## `check_ai_answers` (type: `boolean`):

Ask the questions above and report whether the brand is named, linked, and which competitors appear instead. Needs a brand name.

## `check_authority` (type: `boolean`):

Measure how many domains link to the site, as the external-corroboration half of the score.

## `country` (type: `string`):

Two-letter country code used for the audit fetch and for the AI answers, because both differ by market.

## `language` (type: `string`):

Two-letter language code for the AI answers.

## `timeout` (type: `integer`):

How long each crawler fetch may take before it is recorded as an error rather than a refusal.

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

How many parts of the audit run at once.

## Actor input object example

```json
{
  "url": "https://stripe.com/pricing",
  "brand": "Stripe",
  "prompts": [
    "What is Stripe?",
    "What are the best alternatives to Stripe?"
  ],
  "competitors": [
    "Adyen",
    "PayPal"
  ],
  "check_ai_answers": true,
  "check_authority": true,
  "country": "us",
  "language": "en",
  "timeout": 20,
  "concurrency": 4
}
```

# Actor output Schema

## `audit` (type: `string`):

The scored audit row, with per-dimension scores, findings and the crawler matrix.

## `output` (type: `string`):

Score, grade, which dimensions were measured and which were skipped.

## `errors` (type: `string`):

Parts of the audit that could not run, with a redacted reason.

# 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 = {
    "url": "https://stripe.com/pricing",
    "brand": "Stripe",
    "prompts": [
        "What is Stripe?",
        "What are the best alternatives to Stripe?"
    ],
    "competitors": [
        "Adyen",
        "PayPal"
    ],
    "country": "us",
    "language": "en"
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/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 = {
    "url": "https://stripe.com/pricing",
    "brand": "Stripe",
    "prompts": [
        "What is Stripe?",
        "What are the best alternatives to Stripe?",
    ],
    "competitors": [
        "Adyen",
        "PayPal",
    ],
    "country": "us",
    "language": "en",
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/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 '{
  "url": "https://stripe.com/pricing",
  "brand": "Stripe",
  "prompts": [
    "What is Stripe?",
    "What are the best alternatives to Stripe?"
  ],
  "competitors": [
    "Adyen",
    "PayPal"
  ],
  "country": "us",
  "language": "en"
}' |
apify call s-r/geo-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/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/xWxxAJYW7vorBXMCC/builds/sFQosNABwEafj309R/openapi.json
