# AI Brand Visibility & Citation Audit — Repeat-Tested AEO/GEO (`aivisiblelab/ai-visibility-audit-repeat-tested`) Actor

ChatGPT, Perplexity, Gemini & Google AI Overview: does your brand get named? Every question is asked 3-5 times, so you see which findings are stable and which are noise.

- **URL**: https://apify.com/aivisiblelab/ai-visibility-audit-repeat-tested.md
- **Developed by:** [Anton Y.](https://apify.com/aivisiblelab) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $24.00 / 1,000 ai answer analyzeds

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 Brand Visibility Audit — Repeat-Tested

**Ask an AI engine the same question twice and you can get two different answers.
Every other visibility checker runs each question once and reports the result as
if it were a measurement. This one runs each question 3–5 times and shows you
the spread.**

Does ChatGPT recommend your brand? Perplexity? Gemini? Google's AI Overview?
This Actor asks the questions your buyers actually type — never naming you, so
the engines aren't just reading your name back — then reports how often you are
named, in what position, who gets named instead, which sources the engines cite,
and **which of those findings are stable enough to act on.**

***

### Why repeats matter more than the score

Answer engines are non-deterministic. In published testing, mention rates for
the same brand and the same question swing by tens of percentage points between
runs, and the set of sources cited on two runs of the same day can overlap by
less than 10%.

That means a single-run report is one draw from a random variable, presented as
a fact. If you take a 40% visibility score from a one-shot tool and brief your
team on it, you may be acting on noise.

This Actor gives you the number **and** the honesty check next to it:

- `mention_rate_pct` — how often you were named across all answers
- `unstable_pairs_pct` — the share of question × engine pairs where repeated
  runs disagreed with each other
- `unstable_pairs` — exactly which ones, with the hit count (`2 of 3 runs`)

A 60% mention rate with 10% unstable pairs is a finding. The same 60% with 70%
unstable pairs is a coin flip, and you should know which one you bought.

### What you get

Two record types in the dataset.

**Per answer** — one row for every question × engine × repeat:

| Field | Meaning |
|---|---|
| `engine`, `run` | which engine, which repeat |
| `question` | the buyer question asked |
| `brand_mentioned` | were you named in this answer |
| `brand_rank_in_answer` | your position in the order options appear |
| `named_instead` | competitors named in this answer, de-duplicated |
| `cited_sources` | URLs the engine cited |
| `sources_readable` | whether this engine's citations resolved to real URLs at all |
| `answer_text` | the full raw answer, so you can check our work |

**Summary** — one row per run:

| Field | Meaning |
|---|---|
| `mention_rate_pct` | overall share of answers naming you |
| `top5_rate_pct` | share where you appeared in the first five options |
| `mention_rate_by_engine_pct` | per-engine breakdown |
| `unstable_pairs_pct` / `unstable_pairs` | the repeat-test result |
| `own_site_cited_in_answers` | how often engines cited **your** domain |
| `answers_with_readable_sources` | how many answers the domain figures were computed over |
| `engines_without_readable_sources` | engines whose citations could not be resolved (see Notes) |
| `top_competitors_named` | who is being recommended instead of you |
| `top_cited_domains` | where the engines are getting their opinion |

`own_site_cited_in_answers = 0` is the single most actionable line in the
report: it means the engines' opinion of your category is assembled entirely
from third-party pages, and no amount of editing your own site changes it until
you appear on those pages.

### Input

Give it a brand name. Everything else has a sensible default.

```json
{
  "brandName": "Plausible Analytics",
  "website": "plausible.io",
  "description": "Privacy-friendly website analytics, a lightweight alternative to Google Analytics.",
  "engines": ["chatgpt", "perplexity"],
  "questionCount": 5,
  "repeats": 3
}
```

Leave `prompts` empty and buyer questions are generated from your description —
under a hard rule that **no generated question may name your brand**, because a
question that names you measures nothing. Supply `prompts` (one per line) to
use your own.

`category` is worth setting if you know it (`"website analytics tools"`). Left
empty it is inferred. It controls which names count as competitors: without a
category, extraction tends to pull in frameworks, hosting providers and
databases mentioned in passing, which shifts your reported position.

### What you pay

Two separate charges, and it's fair that you see both before you run:

1. **This Actor: $0.03 per answer received and parsed.** One question, on one
   engine, once. So the default 5 questions × 2 engines × 3 repeats = 30 events
   \= $0.90. Failed engine runs and answers we cannot parse are **not** charged.
2. **The engine scrapers this Actor calls**, billed to your own account by
   Apify's own first-party scrapers. **This is where the plan you are on
   matters a lot:**

| Your Apify plan | Scraper cost per answer | 30-answer audit (5 q × 2 engines × 3 repeats) |
|---|---|---|
| Paid (Starter and up) | ~$0.005–0.014 | **~$0.30** in scraper fees |
| **FREE** | **$0.20** | **$6.00** in scraper fees |

The free tier costs **40× more per answer**, because that is how Apify's
scrapers price free-plan usage — it is not our fee. On a free account, run
**2 questions × 1 engine × 3 repeats** (6 answers) to see the output shape,
and move to a paid plan before tracking a brand for real. The economics of
this Actor only work on a paid plan, and we would rather say so up front than
have you discover it in your usage bill.

Set **Max total charge** on the run to cap the first component. The Actor stops
cleanly when the limit is reached and reports `stopped_on_spend_limit: true`
rather than silently truncating.

### Choosing repeats

| Repeats | Use it for |
|---|---|
| 1 | A quick look at output shape. **Not a measurement** — no stability data is produced |
| **3** | Default. The usual acceptance-test minimum, and enough to separate stable findings from noise |
| 5 | Before a board deck or a budget decision, when you need the spread to be tight |

### Notes and limits

- **Unofficial.** This Actor is not affiliated with, endorsed by, or sponsored
  by OpenAI, Perplexity, Google or Microsoft. Engine names are used only to
  identify which service each answer came from.
- Answers reflect the engines at the moment of the run, from the geography and
  session conditions of the underlying scrapers. Re-running next week will give
  different numbers — that is the nature of the thing being measured, and the
  reason the stability columns exist.
- Competitor and position extraction is done by a language model over the
  answer text. `answer_text` is included in every row so you can verify any
  number yourself.
- **Cited-source metrics are engine-dependent, and we tell you when.** Google's
  AI Overview returns its citations as opaque Google redirect links with no
  resolvable destination, so for that engine we cannot report which domains
  were cited. Rather than counting those answers as "no sources", they are
  excluded from the source metrics, and `answers_with_readable_sources` plus
  `engines_without_readable_sources` tell you exactly what the domain figures
  were computed over. Per-answer, `sources_readable` says the same thing.
- Brand-name matching accepts product suffixes (`Notion` matches `Notion AI`)
  but rejects matches resting on a single short word, to avoid crediting you
  with someone else's mention.

### Track it over time

Schedule the Actor weekly with the same input, and the dataset becomes a
time series of both your visibility and its stability. Use the same `prompts`
each time — regenerated questions measure a different thing.

# Actor input Schema

## `brandName` (type: `string`):

The brand, product or company you want to find in AI answers.

## `website` (type: `string`):

Used to check whether the engines cite your own site as a source. Optional but recommended — 'never cited' is usually the single most actionable finding.

## `description` (type: `string`):

One or two sentences. Used to generate buyer questions and to decide which product category you are compared in.

## `market` (type: `string`):

Optional. Added only to the questions where a buyer would naturally say it.

## `category` (type: `string`):

Optional. E.g. 'website analytics tools'. Left empty, it is inferred from the description. Setting it explicitly makes competitor extraction sharper.

## `prompts` (type: `string`):

One question per line. If filled, generation is skipped and exactly these are used. Do not name your brand in them.

## `questionCount` (type: `integer`):

Ignored when you supply your own questions. Total answers = questions x engines x repeats, and every answer is billed - so 5 x 2 x 3 = 30 answers. On the FREE Apify plan start at 2 questions, 1 engine, 3 repeats.

## `engines` (type: `array`):

Each engine is queried by its dedicated first-party Apify scraper, and those scraper runs are billed to your own account on top of this Actor's fee. On a paid Apify plan that is ~$0.005-0.014 per answer; on the FREE plan the same scrapers charge $0.20 per answer, i.e. 40x more. See 'What you pay' in the README before raising the engine count on a free account.

## `repeats` (type: `integer`):

How many times each question is asked on each engine. This is the point of this Actor: one run is a single draw from a random variable. 3 is the usual acceptance-test minimum; 1 turns the report back into a guess. Cost scales linearly, so cut questions before you cut repeats.

## Actor input object example

```json
{
  "brandName": "Plausible Analytics",
  "website": "plausible.io",
  "description": "Privacy-friendly website analytics, a lightweight alternative to Google Analytics.",
  "questionCount": 1,
  "engines": [
    "google_ai_overview"
  ],
  "repeats": 3
}
```

# Actor output Schema

## `overview` (type: `string`):

Per-answer table: which engine named the brand on which repeat.

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

Full dataset including the summary record.

# 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 = {
    "brandName": "Plausible Analytics",
    "website": "plausible.io",
    "description": "Privacy-friendly website analytics, a lightweight alternative to Google Analytics.",
    "questionCount": 1,
    "engines": [
        "google_ai_overview"
    ],
    "repeats": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("aivisiblelab/ai-visibility-audit-repeat-tested").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 = {
    "brandName": "Plausible Analytics",
    "website": "plausible.io",
    "description": "Privacy-friendly website analytics, a lightweight alternative to Google Analytics.",
    "questionCount": 1,
    "engines": ["google_ai_overview"],
    "repeats": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("aivisiblelab/ai-visibility-audit-repeat-tested").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 '{
  "brandName": "Plausible Analytics",
  "website": "plausible.io",
  "description": "Privacy-friendly website analytics, a lightweight alternative to Google Analytics.",
  "questionCount": 1,
  "engines": [
    "google_ai_overview"
  ],
  "repeats": 3
}' |
apify call aivisiblelab/ai-visibility-audit-repeat-tested --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,aivisiblelab/ai-visibility-audit-repeat-tested"
        }
    }
}
```

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/INoXpjVsifoaNFYHf/builds/eTKbw00VyWBimUvJ1/openapi.json
