# AI Search Visibility Monitor | Share of Voice | Anomaly Alerts (`broomwagon/ai-search-visibility-monitor`) Actor

Track your brand's share of voice across AI answer engines — ChatGPT, Perplexity, Gemini, Google AI Overviews. Deterministic deltas and statistical anomaly alerts against your rolling baseline, on a schedule. Stateless: you hold the memory, we store nothing.

- **URL**: https://apify.com/broomwagon/ai-search-visibility-monitor.md
- **Developed by:** [Brandon Mensing](https://apify.com/broomwagon) (community)
- **Categories:** AI, SEO tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.10 / entity report

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## AI Search Visibility Monitor

<img align="right" src="https://broomwagon.vercel.app/icons/brand-mark.png" width="64" alt="broomwagon.">

> Share of voice, trend deltas, and anomaly alerts for your brand across AI answer engines — deterministic, scheduled, and stateless.

AI answers are the new front page. When ChatGPT, Perplexity, Gemini, or Google's AI
Overviews answer a buyer's question, either your brand is in the answer or a
competitor's is — and that visibility moves week to week with no changelog. This actor
turns snapshots of AI answers into a deterministic visibility time series: **share of
voice per entity, delta since last run, and statistical anomaly alerts** when your
visibility spikes or collapses.

Part of the **Broomwagon** family: deterministic tools that watch scraped and AI data
change. No LLM judges your numbers — same snapshots in, same metrics out, every time.

### The pipeline: collect → measure → alert

You don't need to gather AI answers yourself. Collection is a solved problem on
Apify — this actor is the measurement stage that runs after it:

1. **Collect (any AI-answer scraper).** Schedule a collection actor from the Store
   (Google AI Mode / AI Overviews scrapers, ChatGPT scrapers, Perplexity scrapers)
   to ask your buyer-intent queries — "best CRM for small business", "top donut
   shops in Andersonville" — on a cadence. Its output is a dataset of answers.
2. **Measure (this actor).** Point `datasetId` at that run's dataset and list the
   `entities` you track: your brand and your competitors. Every text field is
   scanned; whole-word entity matching, deterministic counts.
3. **Alert.** Each run emits one row per entity: share of voice, mention counts,
   delta vs. last run, z-score vs. your rolling baseline, and `anomaly: true` when
   a change is statistically real (|z| ≥ 3 against an EWMA baseline) rather than
   noise. Filter on `anomaly` in n8n/Make and pipe it to Slack; that's the whole
   monitoring loop.

The chaining is zero-glue: create one key-value store in your account, put its ID in
`memoryStoreId`, and schedule this actor right after your collection run. Baselines,
deltas, and anomaly flags accumulate automatically, run after run.

### Why a separate measurement stage

The value isn't in any single week's answers — it's in noticing when they change.
That takes memory: which engines mentioned you last month, what your normal share of
voice looks like, whether this week's dip is Tuesday noise or a real displacement.
Normally that memory means a SaaS subscription and someone else's database. Here it
means a compact aggregate you hold (see below), and a per-run price in cents.

### Stateless, with memory

Every run returns a compact aggregate (the `MEMORY` key-value record) holding your
per-entity rolling baselines — sufficient statistics, never your data. Pass it back
next run (`memory`), or point the actor at a key-value store in **your** account
(`memoryStoreId`) and scheduled runs chain automatically. Broomwagon stores nothing;
your history is yours.

First run establishes the baseline; deltas begin on run two; anomaly detection engages
after five runs of baseline.

### Try it in two minutes (no scraper needed)

**Run 1.** Set **Entities to track** to `["Your Brand", "Competitor A"]`, paste a few
AI-answer snapshots into **Inline snapshots** (any records with text fields work —
each record is one answer), and start the run. You get one visibility row per entity
(share of voice, mention counts) and a **`MEMORY`** record in the run's key-value
store: your rolling baselines, held by you.

**Run 2.** Copy the `MEMORY` object into **Previous memory**, run again with fresh
snapshots — now each row also carries `previousShare`, `delta`, and a `zScore`.
After five runs of baseline, statistically significant swings set `anomaly: true`
and bill as anomaly alerts. In real use, replace the pasted snapshots with your
scheduled collection run's dataset as described above.

### Input

- `entities` (required): names to track, e.g. `["Broomwagon", "Competitor A"]`.
- `datasetId` | `items` | `fileUrl`: the snapshot records.
- `textFields`: which snapshot fields to scan (default: all text).
- `memory` / `memoryStoreId`: the memory, as above.

### Output

- Dataset: one `entity-visibility` row per entity per run (share, mentions, delta,
  zScore, anomaly).
- `OUTPUT`: run report. `MEMORY`: your rolling baselines, to send back next run.

### Pricing (pay-per-event)

| Event | What you pay for | Price |
| --- | --- | --- |
| `entity-report` | Per entity tracked, per run | $0.10 |
| `anomaly-alert` | Per statistically significant change detected | $0.05 |
| `apify-actor-start` | Run start, per GB of run memory | $0.005 |

**Worked example:** tracking your brand plus four competitors, daily:
5 entities × $0.10 × 30 days ≈ **$15.15/month** (plus a nickel per anomaly actually
detected — a healthy month has few). An agency running ten client rosters:
\~$150/month, billed per event, no seats, no contract. Collection costs (the
AI-answer snapshots you pipe in) are separate and belong to whichever scraper you
choose.

Set a max charge on any run (`Maximum cost per run` in Console, or
`ACTOR_MAX_TOTAL_CHARGE_USD` via API) and the actor stops cleanly at your budget.

### Integrations

- **Apify Schedule (zero glue):** create a key-value store once, put its ID in
  `memoryStoreId`, and schedule this actor right after your snapshot collection run.
  Baselines, deltas, and anomaly flags accumulate automatically.
- **API:** `POST https://api.apify.com/v2/acts/broomwagon~ai-search-visibility-monitor/runs`
  with `{"entities": [...], "datasetId": "<snapshot run's dataset>", "memory": <last MEMORY record>}`.
  Read `MEMORY` back from the run's key-value store for the next call.
- **n8n / Make:** collection node → this actor → filter on `anomaly: true` → Slack or
  email alert. That's a complete brand-monitoring pipeline in four nodes.
- **MCP / AI agents:** callable as a tool via the Apify MCP server; agents round-trip
  the `MEMORY` object as tool context between calls.

### Roadmap

Citation-level analytics (which sources AI engines cite when they mention you —
being cited without being named is its own signal), competitor-displacement events,
and per-engine breakdowns. Built by an ex-Elastic engineer who spent years on log
analytics — this is time-series observability applied to AI search.

### The Broomwagon family

This actor is one of nine deterministic post-processing tools from [Broomwagon](https://apify.com/broomwagon): the layer that follows your scrapers and agents, cleaning and watching what they produce. Same input, same output, every time.

- [Scraper Output Monitor](https://apify.com/broomwagon/scraper-output-monitor) — catch the day your scrape silently breaks.
- [Only New Items](https://apify.com/broomwagon/only-new-items) — deliver only records you have never delivered before.
- [Dataset Deduper](https://apify.com/broomwagon/dataset-deduper) — exact and fuzzy dedupe for any dataset, with an audit trail.
- [CSV Doctor](https://apify.com/broomwagon/csv-doctor) — repair broken CSV files, with a report of every fix.
- [LLM Output Guard](https://apify.com/broomwagon/llm-output-guard) — validate LLM and agent JSON against your schema.
- [PII Redactor](https://apify.com/broomwagon/pii-redactor) — strip emails, phones, SSNs, cards, and addresses, deterministically.
- [Record Linker](https://apify.com/broomwagon/record-linker) — fuzzy join two datasets that share no key.
- [Google Maps Scraper Deduper](https://apify.com/broomwagon/google-maps-deduper) — merge duplicate places across Google Maps scrapes.

# Actor input Schema

## `entities` (type: `array`):

Brand, product, or company names to measure visibility for. Matching is case-insensitive on normalized text.

## `datasetId` (type: `string`):

Dataset of AI-answer or SERP snapshots from a collection run (e.g. Google Search Scraper AI mode / ChatGPT / Perplexity results, or any AI-visibility scraper). Pipe it straight in.

## `items` (type: `array`):

Snapshot records provided inline as a JSON array. Handy for small jobs and testing.

## `fileUrl` (type: `string`):

URL of a JSON or NDJSON file of snapshot records.

## `textFields` (type: `array`):

Which fields of each snapshot to scan for entity mentions. Default: every string field in the record.

## `memory` (type: `object`):

An object beginning with "memoryVersion", copied from the MEMORY record in your last run's key-value store (not your snapshots — those go in Dataset or Inline snapshots). Enables trends, deltas, and anomaly alerts against your rolling baseline.

## `memoryStoreId` (type: `string`):

Optional: a key-value store in YOUR account. The actor reads the memory from it before running and writes the update back after, so scheduled runs chain automatically with no workflow tool.

## Actor input object example

```json
{
  "entities": [
    "Your Brand",
    "Competitor A",
    "Competitor B"
  ],
  "items": [
    {
      "engine": "example-engine",
      "query": "best widget tool",
      "answerText": "The top widget tools this year are Your Brand and Competitor A. Your Brand stands out for ease of use."
    },
    {
      "engine": "example-engine",
      "query": "widget tool comparison",
      "answerText": "Competitor A and Competitor B both offer solid features, but many reviewers prefer Competitor A for larger teams."
    }
  ]
}
```

# Actor output Schema

## `visibility` (type: `string`):

Share of voice per tracked entity with deltas, z-scores, and anomaly verdicts.

## `memory` (type: `string`):

Your rolling per-entity baselines. Send it back as `memory` on the next run, or skip this by using `memoryStoreId`.

## `report` (type: `string`):

Entities analyzed, anomalies detected, and baseline status.

# 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 = {
    "entities": [
        "Your Brand",
        "Competitor A",
        "Competitor B"
    ],
    "items": [
        {
            "engine": "example-engine",
            "query": "best widget tool",
            "answerText": "The top widget tools this year are Your Brand and Competitor A. Your Brand stands out for ease of use."
        },
        {
            "engine": "example-engine",
            "query": "widget tool comparison",
            "answerText": "Competitor A and Competitor B both offer solid features, but many reviewers prefer Competitor A for larger teams."
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("broomwagon/ai-search-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 = {
    "entities": [
        "Your Brand",
        "Competitor A",
        "Competitor B",
    ],
    "items": [
        {
            "engine": "example-engine",
            "query": "best widget tool",
            "answerText": "The top widget tools this year are Your Brand and Competitor A. Your Brand stands out for ease of use.",
        },
        {
            "engine": "example-engine",
            "query": "widget tool comparison",
            "answerText": "Competitor A and Competitor B both offer solid features, but many reviewers prefer Competitor A for larger teams.",
        },
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("broomwagon/ai-search-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 '{
  "entities": [
    "Your Brand",
    "Competitor A",
    "Competitor B"
  ],
  "items": [
    {
      "engine": "example-engine",
      "query": "best widget tool",
      "answerText": "The top widget tools this year are Your Brand and Competitor A. Your Brand stands out for ease of use."
    },
    {
      "engine": "example-engine",
      "query": "widget tool comparison",
      "answerText": "Competitor A and Competitor B both offer solid features, but many reviewers prefer Competitor A for larger teams."
    }
  ]
}' |
apify call broomwagon/ai-search-visibility-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,broomwagon/ai-search-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/5PhVLMiTVVmbfXpgQ/builds/oZ4aaZL5hOoQ5fUM0/openapi.json
