# GEO / AI Visibility Audit – Technical Website Checks (`stevenkramp/geo-ai-visibility-audit`) Actor

Audit AI search crawler rules, server-readable content, JSON-LD, answer structure and source signals. Technical scores and fixes without LLM calls.

- **URL**: https://apify.com/stevenkramp/geo-ai-visibility-audit.md
- **Developed by:** [Steven Kramp](https://apify.com/stevenkramp) (community)
- **Categories:** SEO tools, AI, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.49 / 1,000 audited pages

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

## GEO / AI Visibility Audit

Find technical obstacles to AI search access and extractable website content, for SEO teams, agencies and website owners.

Audit public HTML without a browser or paid language-model calls. Each page receives a transparent technical score, observations and concrete fixes. Check search crawler rules separately from training permissions, initial-HTML readability, JSON-LD, answer structure and factual/source signals.

### Price and costs

**$4.99 per 1,000 audited HTML pages**, plus Apify's standard start event ($0.00005 per run). Paid Apify plans get tier discounts down to $3.49 per 1,000 pages. 100 audited pages cost about $0.50.

Only successfully audited HTML pages are charged, even when their score is low. Failed, blocked, non-HTML and truncated responses and the summary are free.

Tested on Apify on November 4, 2026: one website, 60 page attempts, 50 audited pages, 10 non-HTML sitemap entries skipped free of charge, about 2 minutes at 512 MB, without proxy.

Proxy use is off by default. A blocked URL is reported without retrying under another identity. If you enable Apify Proxy, Apify bills the proxy traffic separately.

### Input

```json
{
  "startUrls": [{"url": "https://example.com/guide"}],
  "maxPages": 20,
  "maxDepth": 2,
  "useSitemap": true,
  "crawlLinks": true,
  "proxyConfiguration": {"useApifyProxy": false}
}
```

maxPages limits attempted pages across the **whole run**, including errors; maximum 1,000. maxDepth applies to HTML links; sitemap discoveries enter at depth 0. Disable both discovery options to audit only the supplied URLs. An empty input uses the schema's example.com sample. Explicit invalid types and null settings are rejected.

Requests use the identified StevenKrampBot, a minimum 2-second host interval, published Crawl-delay and one attempt per URL/hop. A hard request limit, response-size limits and a timeout reserve bound work. Starts, files named by robots.txt and every redirect target are checked. Unknown robots access stops fetching; missing robots.txt allows access. Denied robots.txt responses also stop this Actor.

### Output and privacy

The dataset contains page rows plus one summary row. Each successful page has seven scored checks and fixed English fix hints. This **fictional excerpt** shows selected fields:

```json
{
  "type": "page",
  "pageId": "SHA256_OF_NORMALIZED_INPUT_URL",
  "inputIndex": 0,
  "statusCode": 200,
  "error": null,
  "billable": true,
  "score": 75,
  "scoreVersion": "technical-1",
  "checks": [{
    "code": "ai-search-access",
    "passed": false,
    "weight": 25,
    "fix": "Review the reported search-crawler rules in robots.txt. Allow only the search access you intend; training access is a separate choice."
  }]
}
```

pageId, finalPageId and siteId are SHA-256 fingerprints, not raw URLs. inputIndex is the zero-based seed position, or null for discovered pages. To map a URL locally, normalize scheme/host, drop the default port and fragment, retain path/query and hash UTF-8 bytes. The dataset omits URL paths, query strings, titles, author names, contacts, snippets, raw schema values and source URLs. For easier mapping, supply an explicit URL list and disable discovery.

The SUMMARY key-value record includes attempted/audited/failed/skipped counts, physical HTTP requests, average known score, stop reason, sitemap results and billablePagesStored. A page may be audited but not stored when the SDK cost limit rejects it; these counts are distinct. Queued aliases can already have been visited. Downloaded HTML and llms.txt are not persisted.

### Score

| Check | Points |
|---|---:|
| Both observed AI search crawler rules allow the page | 25 |
| No applicable noindex/none/nosnippet restriction | 10 |
| At least 800 visible characters in initial HTML | 20 |
| Structurally valid schema.org JSON-LD, no invalid block | 10 |
| Heading followed by a 40–700-character paragraph/list item | 15 |
| Numerical facts with units or date signals | 10 |
| At least one external HTTP(S) link | 10 |

Unknown access stays null instead of becoming denied or zero. A complete score is null if a scored observation is unknown; observedPoints and assessedWeight preserve the known portion. Failed fetches have no score. Schema validity is structural, not full schema.org validation; microdata is counted separately. Facts and source checks count patterns without verifying truth, relevance, authority or whether a link supports a claim. Navigation and boilerplate may contribute.

Training and user-requested agents do not affect search-access points. This is a static robots interpretation, not a live test from provider IPs. [OpenAI documents distinct bot purposes](https://developers.openai.com/api/docs/bots); user-triggered requests can behave differently. [Anthropic also separates search, training and user-requested agents](https://support.claude.com/en/articles/8896518-does-anthropic-crawl-data-from-the-web-and-how-can-site-owners-block-the-crawler).

Root /llms.txt is optional and carries **zero score weight**. Successful non-HTML Markdown with a title and an absolute link passes this narrow heuristic. Missing, invalid, denied, truncated and unavailable files are distinguished. Scoped files, relative-link variants, Markdown alternates and llms-full.txt are outside this check. The [llms.txt proposal](https://llmstxt.org/) is not a universal provider requirement. Nonstandard Content-Signal values retain group scope and do not change scores.

### API

After publication, set APIFY_TOKEN in your own environment and call:

```bash
curl --request POST \
  --header "Authorization: Bearer $APIFY_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{"startUrls":[{"url":"https://example.com/"}],"maxPages":1,"useSitemap":false,"crawlLinks":false}' \
  "https://api.apify.com/v2/acts/stevenkramp~geo-ai-visibility-audit/run-sync-get-dataset-items?memory=512&timeout=120"
```

This endpoint and MCP identifier are reserved for the new Actor; they are not currently published.

### Limits

A high score **does not guarantee mentions, citations, rankings or recommendations by ChatGPT or any other AI service**. These checks establish no such relationship. No model is called, no prompt visibility is measured and no JavaScript or browser rendering is evaluated. Low text density alone is not a JS shell: that warning requires very little visible text plus scripts. Access can differ by IP, provider policy, authentication and bot protection.

Use only public pages you are entitled to audit. There is no login, CAPTCHA solving or bot impersonation. Cookies are cleared between requests. Public-destination checks are not a guarantee against DNS rebinding. This is not a legal-compliance or security audit.

### Use with AI agents (MCP)

Once published, connect an MCP client to:
https://mcp.apify.com?tools=stevenkramp/geo-ai-visibility-audit

Example: “Audit these public documentation URLs for technical AI search readiness. Explain failed checks and distinguish search crawling from model training.”

### Local checks

Install bounded requirements in an isolated environment, then run:

```bash
python -m unittest discover -s tests -v
python -m tests.lauf_auswerten dataset.json
python -m tests.kosten --pages 1000 --sites 1
python -m tests.kosten --run-json run.json --summary-json summary.json
```

Tests use fictional HTTP responses without contacting websites. The cost helper leaves missing cost inputs null.

# Actor input Schema

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

Public HTTP(S) pages. Map inputIndex to your own page list.

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

Upper limit of page attempts across the whole run, including failed attempts. Keeps runtime and cost predictable.

## `maxDepth` (type: `integer`):

How many link steps away from a start URL the audit may follow. 0 audits only the start pages; pages found in sitemaps start at depth 0.

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

Read the site's sitemaps (from robots.txt or /sitemap.xml) to find more pages on the same website.

## `crawlLinks` (type: `boolean`):

Follow HTML links that stay on the same website. Turn off together with sitemaps to audit only the URLs you entered.

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

Off by default. Blocked pages are reported, not retried under another identity. Proxy fees are billed by Apify separately.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://example.com/"
    }
  ],
  "maxPages": 20,
  "maxDepth": 2,
  "useSitemap": true,
  "crawlLinks": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

No description

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

// Run the Actor and wait for it to finish
const run = await client.actor("stevenkramp/geo-ai-visibility-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://example.com/" }] }

# Run the Actor and wait for it to finish
run = client.actor("stevenkramp/geo-ai-visibility-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://example.com/"
    }
  ]
}' |
apify call stevenkramp/geo-ai-visibility-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,stevenkramp/geo-ai-visibility-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/IQRHxz7wauSJYEDBZ/builds/fYhB3CJegXjcjammT/openapi.json
