# AI Visibility Monitor (`suslabs/ai-visibility-monitor`) Actor

Track ChatGPT brand mentions, Gemini answers, and Google AI Overviews. Compare share of voice and citation changes for generative engine optimization.

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

## Pricing

from $10.00 / 1,000 engine checks

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 Visibility Monitor

See whether AI answer engines name your brand, where they place it against competitors, and what changed since the last check.

You bring the brand, the competitors, and the questions customers actually ask. On a schedule, this Actor asks those questions to Google AI Overviews, Gemini, and ChatGPT (Perplexity and Google AI Mode are optional). Each delivered answer is one `engine-check`. The Actors that fetch the answers bill your Apify account separately, at their own prices.

### Who it's for

| Team | What they watch |
| --- | --- |
| SEO and marketing | Buyer questions in AI Overviews, Gemini, and ChatGPT: mentions, position, cited domains |
| Agencies | Several brands, each with its own monitor name, change feed, and Slack or webhook alerts |
| Founders | A short prompt list and a weekly check against two or three competitors |

### What you get

One dataset row per prompt, engine, and repeat:

- the answer text and the cited URLs and domains
- whether the brand was named, how many times, which alias matched, and its position among the brands named in the answer
- whether the brand's domain was cited, and which competitors were named or cited
- the engine, the source Actor, the source run id, the route, the country, the time, and the source cost for that row
- an optional sentiment label from a small word list, on sentences that name the brand

A named monitor keeps that history. Later runs add free `change` rows when the brand appears or disappears, its position moves, a competitor is newly named ahead of it, a cited domain is new, or the brand stops being cited. Repeats (1–3) produce a stable mention rate, because the same question does not always get the same answer. A free `report` row and `SUMMARY` give share of voice: the brand's mention rate and average position against each competitor, per engine and overall.

Empty checks, failed engines, change rows, and reports are free. A failed source is not billed as an `engine-check`. If the primary source refuses, returns nothing, or returns rows this Actor cannot read, the next source in that engine's list is tried. A free `status` row records which source failed. Premium sources ($0.20 on the free plan, measured 6 Nov 2026) run only when you turn on **Allow premium sources**.

### Engines

Prices below are free-plan source prices measured on 6 Nov 2026. A paid Apify plan often pays those Actors less. Our fee is separate.

| Engine | Default | Primary source | Typical source fee | Fallback |
| --- | --- | --- | --- | --- |
| Google AI Overviews | on | `apify/google-ai-overviews-scraper` | $0.003 per overview | `johnvc/Google-AI-Overview-API` ($0.0027), then `subimpact/google-serp-ai-overviews` ($0.003) |
| Gemini | on | `s-r/gemini-search` | $0.005 per answer | `apify/gemini-search-scraper` ($0.20, premium) |
| ChatGPT | on | `hyperbach/ai-search-answers` (queued web read) | about $0.01 per answer ($0.0167 if that Actor serves it live) | `apify/chatgpt-search-scraper` ($0.20, premium) |
| Perplexity | off | `hyperbach/ai-search-answers` (API route) | about $0.026 per answer | `apify/perplexity-search-scraper` ($0.20, premium). That Actor returned HTTP 402 on our plan on 6 Nov 2026. |
| Google AI Mode | off | `hyperbach/ai-search-answers` | about $0.01 per answer | none |

`hyperbach/ai-search-answers` is developed by another Store publisher. Adapters are isolated, and each source's recent success and failure count is kept in the route-health store so a broken source can be skipped with **Skip these source Actors**.

The official Google AI Overviews Actor accepts queries only. The country you set is stored on the row and is sent to sources that have a country field (Gemini, the fallbacks, ChatGPT, Perplexity). It is not a parameter of `apify/google-ai-overviews-scraper`.

### What a run costs

Our monitoring fee is **$0.01 per delivered answer** (`engine-check`), the same on every Apify plan. The platform also charges its usual Actor start event. Source fees are whatever those Actors charge your plan.

Example, free-plan source prices on 6 Nov 2026, one prompt, Google AI Overviews and Gemini, one repeat (the saved input):

| | USD |
| --- | --- |
| Our fee, 2 checks | 0.02 |
| Google AI Overviews | 0.00305 |
| Gemini | 0.005 |
| About one run | 0.028 |
| Daily for a month | 0.84 |
| Weekly for a month (4 runs) | 0.11 |

Those two source figures are what the free plan actually charged on 6 Nov 2026 (runs `iE1TM4AfubaRY7dy2` and `kCfXgeYeTPTnw1Qql`). Platform compute for this Actor is included in our fee.

Adding ChatGPT on the queued route is about $0.01 more per prompt. `COST_ESTIMATE` in the run is the same arithmetic for the input you actually saved, in plain language. `maxSourceCostUsd` (default $0.50, $0.05 on the saved input) stops this Actor from starting a source call that would pass the cap.

### Presets

Leave **Prompts** empty and pick a preset. The questions are written into `EFFECTIVE_CONFIG` before any source is called.

| Preset | Questions it fills |
| --- | --- |
| Brand vs competitors | What the brand is, the brand against the first competitor, and a "best alternative" question |
| Category leaders | Best, small-team, and comparison questions for the category you type |
| Product comparison | The brand against each competitor |
| Buying questions | Worth it, price, and reviews |

### Alerts and the change feed

With a monitor name, change rows and failed-engine rows can go to Slack, Discord, or a webhook. Delivery is retried, each POST has `X-SusLabs-Delivery-Id`, and a webhook secret adds `X-SusLabs-Signature` (`sha256=` over `deliveryId.timestamp.body`).

**Public change feed** writes RSS, Atom, and JSON files. `SUMMARY.feedUrls` lists them. Anyone with the link can read the feed.

### How to read the result

These are answers those source Actors returned from each engine's public web experience, or from hyperbach's Perplexity API route, at one time. The same prompt can name a different brand, or cite a different page, on the next run or for another Apify user. A single check is one observation. The monitor is there so you can see the pattern, not so one row can stand in for the engine.

Brand matching is done here, the same way for every engine: whole words, aliases, case-insensitive. This Actor does not trust a source's own brand fields. Sentiment is the share of positive and negative words in sentences that name the brand. It is a word list, not a judgment of the answer.

### FAQ

**Do I need an OpenAI, Google, or Perplexity key?**
No. Answers come from the source Actors above, called inside your run. You pay those Actors on your Apify account.

**Is a ChatGPT row the signed-in ChatGPT product?**
No. It is the answer the ChatGPT source Actor returned from the public web experience on that run. Wording and citations vary.

**Why was a check free?**
The source failed, returned nothing usable, or the row is a change, a report, or a status. Those do not charge `engine-check`.

**A source charged me and the row is empty.**
That source is recorded on the status row and in route health, including a zero-row charge. The next source in the list is tried when the whole call comes back empty. Set **Skip these source Actors** to stop using one that keeps failing.

**Can I compare countries?**
Set **Country** and keep it stable for a monitor. Gemini and the fallbacks use it. The primary Google AI Overviews Actor does not accept a country.

# Actor input Schema

## `preset` (type: `string`):

Optional prompt set. If you leave Prompts empty, the preset fills them and the run writes that list to EFFECTIVE_CONFIG.

## `brand` (type: `object`):

The brand to look for. Matching is whole-word and case-insensitive, including aliases.

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

Names and domains to compare. Positions are the order the names first appear in the answer.

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

Used by the Category leaders preset, for example CRM or note taking.

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

Questions a customer would ask. One check is one prompt on one engine, once per repeat.

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

Default engines are Google AI Overviews, Gemini and ChatGPT. The saved input below leaves ChatGPT off so the first run stays cheap. Perplexity and Google AI Mode are off until you add them.

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

Two-letter country code passed to sources that accept one. The official Google AI Overviews Actor accepts queries only, so that route records the country without sending it.

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

Ask each prompt this many times per engine. The stable mention rate is the share of those repeats that name the brand.

## `monitorName` (type: `string`):

When set, the run keeps history for these prompts and engines and emits change rows. Editing the brand or competitors does not reset history.

## `resetMonitor` (type: `boolean`):

Clear saved history for this monitor name.

## `positionMoveThreshold` (type: `integer`):

Emit a position change when the brand's rank among named brands moves by at least this many places.

## `publicFeed` (type: `boolean`):

Write RSS, Atom and JSON files of change events. SUMMARY.feedUrls lists them. Anyone with the link can read the feed.

## `sentiment` (type: `boolean`):

Score sentences that name the brand with a small word list. No model call.

## `allowPremiumSources` (type: `boolean`):

When a primary source fails, a $0.20 fallback (Apify's ChatGPT, Gemini or Perplexity scrapers) is used only if this is on.

## `maxSourceCostUsd` (type: `number`):

Cap on source-Actor charges for this run. A call that would pass the cap is not started. Default $0.50. The saved input uses $0.05.

## `unavailableSources` (type: `array`):

Actor ids to skip, for example apify/google-ai-overviews-scraper. The next source for that engine is used. Use this when a source is returning empty rows.

## `slackWebhookUrl` (type: `string`):

Incoming webhook for change and status rows.

## `discordWebhookUrl` (type: `string`):

Discord incoming webhook for change and status rows.

## `webhookUrl` (type: `string`):

JSON POST with a delivery id. Optional HMAC in X-SusLabs-Signature.

## `webhookSecret` (type: `string`):

When set, each POST is signed. The signature is sha256 over deliveryId.timestamp.body.

## `webhookAuthorization` (type: `string`):

Sent only to the generic webhook, not to Slack or Discord.

## `webhookMode` (type: `string`):

Send one alert per row, or a digest of up to 10.

## `alertMaxAttempts` (type: `integer`):

How many times a failed alert is retried.

## `alertRetentionDays` (type: `integer`):

How long a pending alert is kept for retry.

## `webhookMaxItems` (type: `integer`):

How many queued alerts this run will try to send.

## Actor input object example

```json
{
  "brand": {
    "name": "Notion",
    "aliases": [
      "Notion AI"
    ],
    "domains": [
      "notion.so"
    ]
  },
  "competitors": [
    {
      "name": "Coda",
      "aliases": [],
      "domains": [
        "coda.io"
      ]
    }
  ],
  "prompts": [
    "best note taking apps for teams"
  ],
  "engines": [
    "google_ai_overviews",
    "gemini"
  ],
  "country": "US",
  "repeats": 1,
  "resetMonitor": false,
  "positionMoveThreshold": 1,
  "publicFeed": false,
  "sentiment": true,
  "allowPremiumSources": false,
  "maxSourceCostUsd": 0.05,
  "webhookMode": "item",
  "alertMaxAttempts": 5,
  "alertRetentionDays": 14,
  "webhookMaxItems": 20
}
```

# Actor output Schema

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

No description

## `effective_config` (type: `string`):

No description

## `cost_estimate` (type: `string`):

No description

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

No description

## `monitor_state` (type: `string`):

No description

## `route_health` (type: `string`):

No description

## `answers` (type: `string`):

No description

## `citations` (type: `string`):

No description

## `changes` (type: `string`):

No description

## `reports` (type: `string`):

No description

## `status` (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 = {
    "brand": {
        "name": "Notion",
        "aliases": [
            "Notion AI"
        ],
        "domains": [
            "notion.so"
        ]
    },
    "competitors": [
        {
            "name": "Coda",
            "aliases": [],
            "domains": [
                "coda.io"
            ]
        }
    ],
    "prompts": [
        "best note taking apps for teams"
    ],
    "engines": [
        "google_ai_overviews",
        "gemini"
    ],
    "country": "US",
    "repeats": 1,
    "maxSourceCostUsd": 0.05
};

// Run the Actor and wait for it to finish
const run = await client.actor("suslabs/ai-visibility-monitor").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 = {
    "brand": {
        "name": "Notion",
        "aliases": ["Notion AI"],
        "domains": ["notion.so"],
    },
    "competitors": [{
            "name": "Coda",
            "aliases": [],
            "domains": ["coda.io"],
        }],
    "prompts": ["best note taking apps for teams"],
    "engines": [
        "google_ai_overviews",
        "gemini",
    ],
    "country": "US",
    "repeats": 1,
    "maxSourceCostUsd": 0.05,
}

# Run the Actor and wait for it to finish
run = client.actor("suslabs/ai-visibility-monitor").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 '{
  "brand": {
    "name": "Notion",
    "aliases": [
      "Notion AI"
    ],
    "domains": [
      "notion.so"
    ]
  },
  "competitors": [
    {
      "name": "Coda",
      "aliases": [],
      "domains": [
        "coda.io"
      ]
    }
  ],
  "prompts": [
    "best note taking apps for teams"
  ],
  "engines": [
    "google_ai_overviews",
    "gemini"
  ],
  "country": "US",
  "repeats": 1,
  "maxSourceCostUsd": 0.05
}' |
apify call suslabs/ai-visibility-monitor --silent --output-dataset

```

## MCP server setup

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

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/JwAAuEy9ytpf5Tu2m/builds/fsqzxSlyP0aD3MluE/openapi.json
