# AI Overview & SERP Visibility Monitor (`thirdwatch/ai-search-visibility`) Actor

Track whether your brand is cited in Google's AI Overview, owns the featured snippet, and ranks organically for the keywords you care about. One row per keyword. Measures public SERP surfaces only - no third-party LLM APIs.

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

## Pricing

from $10.00 / 1,000 results

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/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 Overview & SERP Visibility Monitor

**Does Google's AI recommend your brand?** This Actor answers that question with evidence instead of guesswork. For every keyword you track, it checks whether Google rendered an AI Overview, whether that AI Overview cited your site, who owns the featured snippet, where you rank organically, and which competitors are being surfaced instead of you.

One dataset row per keyword. Built for weekly monitoring by SEO agencies, in-house marketing teams, and anyone whose organic traffic is being absorbed by AI-generated answers.

### Why this exists

Google's AI Overview answers the question at the top of the page. The user reads it and leaves. Your rank-3 blue link never gets the click. Traditional rank trackers still report "position 3" and call that a good week.

Generative Engine Optimization (GEO) and Answer Engine Optimization (AEO) are the response to that shift, and both need the same missing input: **a measurement of whether you are being cited in the answer surfaces themselves.** That is what this Actor produces.

### What it measures

For each keyword:

| Field | Meaning |
|---|---|
| `keyword` | The query that was checked |
| `country` | Country code the SERP was localized to |
| `aiOverviewPresent` | Whether Google rendered an AI Overview at all |
| `brandCitedInAiOverview` | Whether the AI Overview cited your brand — `null` when no AI Overview existed |
| `aiOverviewSources[]` | Every source the AI Overview cited (`url`, `domain`, `title`) |
| `organicRank` | Your 1-based organic position — `null` when you are genuinely absent |
| `featuredSnippetOwner` | Domain that owns the featured snippet, if any |
| `brandOwnsFeaturedSnippet` | Whether that owner is you |
| `peopleAlsoAsk[]` | The People Also Ask questions on the page |
| `competitorsCited[]` | Per competitor: AI Overview citation and organic rank |
| `checkedAt` | UTC timestamp of the check |
| `status` | `checked` or `failed` |

Diagnostic fields are included too: `aiOverviewDetectedBy`, `brandCitedMatchedBy`, `organicRankMatchedBy`, `matchStrategy`, `organicResultsParsed`, and `error`.

### The honesty rule this Actor is built around

**Google does not render an AI Overview for every query.** Many monitoring tools quietly report that absence as "your brand was not cited", which is a different and much more alarming claim.

This Actor keeps the two facts separate:

- No AI Overview was rendered → `aiOverviewPresent: false`, `brandCitedInAiOverview: null`
- An AI Overview was rendered and did not cite you → `aiOverviewPresent: true`, `brandCitedInAiOverview: false`
- An AI Overview was rendered and cited you → `aiOverviewPresent: true`, `brandCitedInAiOverview: true`
- The results page could not be retrieved → `status: "failed"`, every measured field `null`, **and no charge**

The same discipline applies to `organicRank`: absent is `null`, never `0` and never a `999` sentinel that sorts like a real rank.

### How brand matching works

Supply `domain` whenever you can. Domain matching is the reliable signal, and it matches subdomains too (`help.notion.so` counts as `notion.so`). Brand-name matching is the fallback and uses word-boundary matching, so `Notion` does not match `Notionally Ltd`.

Every row tells you which method produced the answer via `brandCitedMatchedBy` and `organicRankMatchedBy`, so you can audit any result you find surprising.

### No LLM APIs

This Actor measures **public Google SERP surfaces only**. It does not call OpenAI, Perplexity, Anthropic, or any other model provider, and it does not resell output from an API it does not own. What it reports is what Google actually served on a public results page — verifiable by hand, and stable as a compliance story.

### Input

```json
{
  "brand": "Notion",
  "domain": "notion.so",
  "keywords": [
    "best note taking app",
    "notion alternatives",
    "team wiki software"
  ],
  "country": "us",
  "language": "en",
  "competitors": ["obsidian.md", "Evernote"]
}
```

| Input | Required | Default | Notes |
|---|---|---|---|
| `brand` | yes | — | Brand name to track |
| `domain` | no | — | Your domain. Strongly recommended for accuracy |
| `keywords` | yes | — | One row is produced per keyword |
| `country` | no | `us` | Two-letter country code |
| `language` | no | `en` | Two-letter language code |
| `competitors` | no | `[]` | Names or domains; a dotted entry with no space is treated as a domain |
| `proxyConfiguration` | no | GOOGLE\_SERP | Must use Apify's GOOGLE\_SERP group |

### Output

```json
{
  "keyword": "best note taking app",
  "country": "us",
  "brand": "Notion",
  "domain": "notion.so",
  "status": "checked",
  "aiOverviewPresent": true,
  "aiOverviewDetectedBy": "attr:data-subtree=aio",
  "brandCitedInAiOverview": true,
  "brandCitedMatchedBy": "domain",
  "aiOverviewSources": [
    { "url": "https://obsidian.md/", "domain": "obsidian.md", "title": "Obsidian" },
    { "url": "https://notion.so/product", "domain": "notion.so", "title": "Notion" }
  ],
  "organicRank": 2,
  "organicRankMatchedBy": "domain",
  "featuredSnippetOwner": "obsidian.md",
  "brandOwnsFeaturedSnippet": false,
  "peopleAlsoAsk": ["What is the best free note taking app?"],
  "competitorsCited": [
    { "competitor": "obsidian.md", "citedInAiOverview": true, "organicRank": 1 }
  ],
  "matchStrategy": "domain",
  "checkedAt": "2026-08-03T12:00:00+00:00"
}
```

### Pricing

Pay per event. You are charged **once per keyword actually checked** — meaning a results page was successfully retrieved and parsed into a row. Keywords whose fetch failed still appear in your dataset marked `status: "failed"` so you know what was not covered, and they are **never charged**.

| Event | FREE | BRONZE | SILVER | GOLD |
|---|---|---|---|---|
| `keyword-checked` | $0.05 | $0.045 | $0.04 | $0.035 |

A 50-keyword weekly check costs $2.50/week on the FREE tier.

### Use cases

- **Weekly GEO/AEO reporting for retainer clients** — show whether client brands are being cited in AI Overviews, not just where they rank.
- **AI Overview coverage mapping** — find which of your keywords now trigger an AI Overview at all. That subset is where classic rank tracking has stopped predicting traffic.
- **Competitive citation share** — see which competitors Google's answer surfaces prefer, and on which queries.
- **Content prioritization** — pages that rank well but are never cited are candidates for restructuring toward extractable, answer-shaped content.
- **Featured snippet defense** — track snippet ownership changes over time.
- **Post-migration verification** — confirm a domain migration did not drop you out of the answer surfaces.

### Notes on reliability

Google publishes no stable contract for the AI Overview block, and its markup changes. The detector is layered — several structural selectors, then several text markers — and every row records which signal fired in `aiOverviewDetectedBy`, so drift is visible in your data rather than silently reported as "no AI Overview".

Runs use Apify's GOOGLE\_SERP proxy group, which handles Google's anti-bot protection internally. Residential proxies do not work for Google Search and are not used as a fallback.

Runs respect the platform timeout: if the deadline approaches, the Actor stops cleanly with the keywords it has finished rather than being killed mid-run.

### Technical

- Pure HTTP, 256 MB, `apify/actor-python:3.14`. No browser.
- Rate limited between keywords.
- One keyword failing never fails the run.

# Actor input Schema

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

The brand name you want to track, e.g. 'Notion'. Used for string matching when no domain is supplied, and always used to detect brand mentions inside the AI Overview text.

## `domain` (type: `string`):

Your website domain, e.g. 'notion.so'. Strongly recommended: domain matching is far more reliable than brand-name matching, and subdomains are matched too. Every output row reports which method produced the match.

## `keywords` (type: `array`):

Search queries to check. One dataset row is produced per keyword.

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

Two-letter country code for localized results, e.g. 'us', 'uk', 'in', 'de'.

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

Two-letter language code for the results page, e.g. 'en', 'de', 'fr'.

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

Competitor brand names or domains to check alongside yours. Entries containing a dot and no space are treated as domains; everything else is matched by name.

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

Proxy settings. This Actor requires Apify's GOOGLE\_SERP proxy group, which handles Google's anti-bot protection internally. Residential proxies do not work for Google Search.

## Actor input object example

```json
{
  "brand": "Notion",
  "domain": "notion.so",
  "keywords": [
    "best note taking app",
    "notion alternatives"
  ],
  "country": "us",
  "language": "en",
  "competitors": [
    "obsidian.md",
    "Evernote"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "GOOGLE_SERP"
    ]
  }
}
```

# Actor output Schema

## `results` (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": "Notion",
    "domain": "notion.so",
    "keywords": [
        "best note taking app",
        "notion alternatives"
    ],
    "country": "us",
    "language": "en",
    "competitors": [
        "obsidian.md",
        "Evernote"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("thirdwatch/ai-search-visibility").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": "Notion",
    "domain": "notion.so",
    "keywords": [
        "best note taking app",
        "notion alternatives",
    ],
    "country": "us",
    "language": "en",
    "competitors": [
        "obsidian.md",
        "Evernote",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("thirdwatch/ai-search-visibility").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": "Notion",
  "domain": "notion.so",
  "keywords": [
    "best note taking app",
    "notion alternatives"
  ],
  "country": "us",
  "language": "en",
  "competitors": [
    "obsidian.md",
    "Evernote"
  ]
}' |
apify call thirdwatch/ai-search-visibility --silent --output-dataset

```

## MCP server setup

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

```

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/nWPqYDYCZJfBKs4nJ/builds/XZEKODXgCWKD7EIny/openapi.json
