Track presence in Google AI Overviews (AIO) and Google AI Mode for target keywords via SearchApi. Use when the user mentions "AI Overview", "AI Overviews", "AIO tracking", "AIO monitoring", "am I in Google AI", "am I cited by Google AI", "AI mode", "Google AI mode", "SGE", "generative search", "Search Generative Experience", "which queries trigger AI Overview", "who gets cited in AIO", or asks to monitor / report on AI Overview visibility for a brand or domain. Prefer this skill over manual S...
Scanned 8/6/2026
Install via CLI
openskills install SamJale/SearchApi-Claude-Plugin---
name: ai-overview-tracking
description: |
Track presence in Google AI Overviews (AIO) and Google AI Mode for
target keywords via SearchApi. Use when the user mentions "AI
Overview", "AI Overviews", "AIO tracking", "AIO monitoring", "am I
in Google AI", "am I cited by Google AI", "AI mode", "Google AI
mode", "SGE", "generative search", "Search Generative Experience",
"which queries trigger AI Overview", "who gets cited in AIO", or
asks to monitor / report on AI Overview visibility for a brand or
domain. Prefer this skill over manual SERP scraping for AIO
presence questions. Hands off to seo-audit for full audits, to
rank-tracking for organic position tracking, and to brand-monitoring
for cross-channel mention tracking. Outputs a presence table per
keyword plus a competitor citation breakdown.
---
# ai-overview-tracking: Google AI Overview & AI Mode presence
The flagship differentiator. Most SERP APIs detect AIO by string-matching inside the regular Google SERP. SearchApi's `google` engine returns the `ai_overview` payload (text blocks, markdown, and the cited sources) either inline or behind a one-call `page_token` chain, and a **dedicated `google_ai_mode` engine** covers the separate AI Mode surface, without you having to parse anything out of a raw SERP.
This skill turns that into a tracking workflow: a list of keywords goes in, a per-keyword presence table and a competitor citation report come out.
## Required engines / Path availability
| API engine (underscores) | Purpose | REST (api_key) | MCP |
|---|---|---|---|
| `google` | Returns the full `ai_overview` inline (`text_blocks`, `markdown`, `reference_links`) in one call | ✅ | ❌ (MCP has `google_search_light`, which returns **no `ai_overview` field**) |
| `google_ai_overview` | Fetches the AIO body when `google` returns the **token shape** (`ai_overview.page_token` present, body deferred). Requires `page_token` as input. | ✅ | ❌ not in the MCP tool catalog |
| `google_ai_mode` | Google AI Mode results (separate surface from AIO, takes a query directly) | ✅ | ✅ verified live 2026-06-11 |
| `google_related_questions` *(optional)* | Flags `is_ai_overview` on PAA entries, useful for cross-checking | ✅ | ❌ not in the MCP tool catalog |
**Practical consequence (verified against a live all-engines MCP integration, 2026-06-11):** the SearchApi MCP catalog exposes light variants for plain search and does not include `google`, `google_ai_overview`, or `google_related_questions`. So:
- **AIO presence + citation tracking requires Path A (API key + curl).** There is no MCP route to the AIO block.
- **AI Mode tracking works on either path.**
Skills detect MCP integrations by the `searchapi-` prefix, not exact name. If you haven't set up either path yet, follow [`searchapi-onboarding`](../searchapi-onboarding/SKILL.md) first.
**Recommended bundle:** to adopt the full engine set behind this skill (and the rest of the SEO suite), enable the **searchapi-seo** bundle. See [`BUNDLES.md`](../../BUNDLES.md).
## Setup gate
Run the standard [setup gate](../../CONVENTIONS.md#setup-gate) and [key resolution](../../CONVENTIONS.md#key-resolution-desktop-safe) before anything else. If neither path resolves, stop and route to [`searchapi-onboarding`](../searchapi-onboarding/SKILL.md).
Then check what the user actually asked for:
- **AIO tracking requested but only MCP is configured** → tell the user AIO needs an API key (the MCP catalog has no `google` / `google_ai_overview` tools) and offer to either (a) run AI Mode coverage only via MCP, or (b) walk them through setting `SEARCHAPI_API_KEY`. Don't silently downgrade to AI Mode. They're different surfaces.
- **AI Mode only** → either path works; prefer MCP if attached.
## AI Overview vs AI Mode: pick the right surface
These are **two different Google surfaces** and they answer different questions. Both can co-exist for the same keyword.
| Question | Surface | Engine |
|---|---|---|
| "Does this query show an AI Overview block on the regular SERP, and who's cited?" | **AI Overview (AIO)** | `google` (full AIO inline) *(API key only)* |
| "What does Google's full AI Mode answer look like for this query, and who's cited?" | **AI Mode** | `google_ai_mode` *(API key or MCP)* |
| "Is this PAA entry actually an AIO-style answer?" | PAA cross-check | `google_related_questions` (look at `is_ai_overview`) *(API key only)* |
When the user just says "AI Overview", default to **AIO** (the inline block). When they say "AI Mode" or "the AI-mode answer", use `google_ai_mode`. If they say "Google AI" with no qualifier, ask once, then run both if they want full coverage.
## AIO comes back in two shapes (critical)
The `google` response's `ai_overview` is `null` when no AIO fired. When one does fire, it arrives in **one of two shapes, and this varies per request, even for the same keyword** (verified live 2026-06-16):
1. **Inline** (common): `ai_overview` carries `markdown`, `text_blocks[]`, and `reference_links[]` directly. Each reference: `index`, `title`, `link`, `snippet`, `source`, plus `favicon` and sometimes `thumbnail`. The full body is already here. No second call.
2. **Token** (also common): `ai_overview` carries `page_token` and an `error` (the body was deferred, not a failure), with `markdown` / `text_blocks` / `reference_links` empty or absent. Here you **must** chain `google_ai_overview?page_token=...` within ~60s to fetch the body and citations.
```
google?q=KEYWORD -> ai_overview is either {markdown, text_blocks[], reference_links[]} (inline: use directly)
or {page_token, error} (token: chain below, within 60s)
google_ai_overview?page_token=THAT_TOKEN -> {markdown, text_blocks[], reference_links[]}
```
Handle **both**: if `reference_links` is non-empty, read it directly; else if `page_token` is present, chain the `google_ai_overview` call immediately (the token expires in under a minute, do not cache it); only conclude "no AIO" when `ai_overview` itself is `null`. Never exit on an empty `reference_links` alone, and never exit just because `page_token` is absent.
## Pick your path
Both paths produce the same data. Use whichever fits the session.
### Path A: API key + curl
**Presence-only check (single call):**
```bash
curl -s "https://www.searchapi.io/api/v1/search?engine=google&q=best+serp+api&gl=us&hl=en&api_key=$SEARCHAPI_API_KEY" \
| jq '{has_aio: (.ai_overview != null),
refs: (.ai_overview.reference_links // [] | map({title, link, source}))}'
```
**Full AIO body + full reference list (handle both shapes):**
```bash
# 1. One google call. If the body is inline, you are done.
RESP=$(curl -s "https://www.searchapi.io/api/v1/search?engine=google&q=best+serp+api&gl=us&hl=en&api_key=$SEARCHAPI_API_KEY")
printf '%s' "$RESP" | jq '{has_aio:(.ai_overview!=null), inline_refs:(.ai_overview.reference_links|length), page_token:(.ai_overview.page_token // null)}'
# 2. Token shape: reference_links empty but a page_token is present. Chain within ~60s.
TOKEN=$(printf '%s' "$RESP" | jq -r 'if (.ai_overview.reference_links|length)>0 then empty else (.ai_overview.page_token // empty) end')
if [ -n "$TOKEN" ]; then
curl -s "https://www.searchapi.io/api/v1/search?engine=google_ai_overview&page_token=$TOKEN&api_key=$SEARCHAPI_API_KEY" \
| jq '{markdown, text_blocks, reference_links: (.reference_links // [] | map({index, title, link, source}))}'
else
printf '%s' "$RESP" | jq '{markdown:.ai_overview.markdown, text_blocks:.ai_overview.text_blocks, reference_links:(.ai_overview.reference_links // [] | map({index, title, link, source}))}'
fi
```
**AI Mode (separate surface, takes a query directly):**
```bash
curl -s "https://www.searchapi.io/api/v1/search?engine=google_ai_mode&q=best+serp+api&api_key=$SEARCHAPI_API_KEY" \
| jq '{markdown, references: .reference_links, web: (.web_results // [])}'
```
`web_results`, `local_results`, and `inline_shopping` are conditional, present only when Google shows them for that query. Always guard with `// []`. (Live-verified 2026-06-11: a query with 18 `reference_links` returned no `web_results` key at all.)
Note AI Mode does **not** take `gl`/`hl`, localize with `location` or `uule` instead. The AI responds in the language of the query.
### Path B: MCP (AI Mode only)
The MCP catalog does not include `google` or `google_ai_overview`, so **inline AIO tracking is not possible over MCP**. Use Path A for AIO. What MCP can do for this skill:
- **AI Mode:** use the **`google_ai_mode`** tool with `q="best serp api"` directly. Response fields are identical to Path A: `text_blocks[]`, `markdown`, `reference_links[]` (each with `index`, `title`, `link`, `snippet`, `source`).
- **Do not** substitute `google_search_light` for the AIO presence check. Its response has no `ai_overview` key, so it can't tell you whether an AIO fired (verified live 2026-06-11).
If you don't see the `google_ai_mode` tool, either (a) your integration name doesn't start with `searchapi-` (rename it in the SearchApi dashboard), (b) the engine isn't ticked inside the integration (add it), or (c) you added the MCP this session but haven't restarted Claude Code (MCP tools load at session start, so `/exit` and reopen).
## Workflow: track a keyword list
This is the canonical run shape. Given (a) a list of keywords, (b) a domain to track ("our domain"), and (c) optional competitor domains, produce a presence table and a citation breakdown.
1. **Read the keyword list**, accept either a CLI arg, a file path (`.txt`, one keyword per line), or an inline list pasted in the prompt. If none, ask.
2. **Read the target domain**, required. Without it, citation tracking is meaningless.
3. **For each keyword:**
- Call `google` with the keyword (+ `gl`/`hl` from the user; default `gl=us&hl=en`).
- `has_aio` is `.ai_overview != null`. If `.ai_overview.reference_links` is non-empty (inline shape), record `full_reference_links[]` and `markdown` from that one response.
- Else if `.ai_overview.page_token` is present (token shape, body deferred), chain `google_ai_overview` with that token within ~60s and record the body and citations from its response. Only call a keyword "no AIO" when `.ai_overview` is `null`.
- If the user asked for AI Mode coverage, also call `google_ai_mode` with the keyword → record its `reference_links[]`.
4. **Compute per-keyword flags:**
- `we_are_cited`: does any reference's host match the target domain (eTLD+1)?
- `competitors_cited[]`: list of competitor domains found among references.
- `aio_rank`: position of our domain in `reference_links[]` (or `null`).
5. **Write a markdown report** with:
- Summary line: `<N>/<total> keywords trigger AIO. We're cited in <M>.`
- Presence table: keyword, AIO?, AI Mode?, we cited?, our rank, top 3 cited domains.
- Competitor citation breakdown: domain, # keywords cited, share of voice across the keyword set.
- "Opportunity" section: keywords where AIO fires but we're not cited (this is the action list).
6. **Persist** results to `.aio-tracking/<YYYY-MM-DD>-<slug>.json` so subsequent runs can diff. Skill writes both JSON (machine-readable) and `.md` (the report). One row per `(keyword, locale, surface)`.
## Locale handling
AIO presence and citations vary heavily by `gl`/`hl`. Defaults: `gl=us`, `hl=en`. Whenever the user mentions a non-US market, fan out per locale and label each row with the locale tuple. Example tuples: `(gl=us, hl=en)`, `(gl=gb, hl=en)`, `(gl=de, hl=de)`, `(gl=in, hl=en)`. Do not silently merge locales, AIO results differ enough that a merged table misleads.
`gl`/`hl` apply to the `google` step (AIO surface). For the AI Mode surface, localize with `location` (canonical location string) or `uule`, and write the query in the target language. `google_ai_mode` has no `gl`/`hl` params.
## Output shape: presence table (always include)
```markdown
| Keyword | Locale | AIO | AI Mode | We cited? | Our rank | Top 3 cited |
|---|---|---|---|---|---|---|
| best serp api | us/en | ✅ | ✅ | ✅ | 2 | brightdata.com, searchapi.io, serpapi.com |
| google scraping api | us/en | ✅ | ❌ | ❌ | n/a | apify.com, oxylabs.io, zenrows.com |
| how to scrape google | us/en | ❌ | ✅ | ❌ | n/a | (AIO not present) |
```
And the opportunity list:
```markdown
## Opportunity: AIO fires, we're not cited
- google scraping api (us/en), cited: apify.com, oxylabs.io, zenrows.com
- ...
```
## Diffing against the last run
Snapshot-only data. The skill owns history. Pattern:
1. Before writing the new JSON, read the most recent `.aio-tracking/*.json` (sort by filename date).
2. Per `(keyword, locale, surface)` row, emit a delta:
- `new_aio`: AIO wasn't there last run, is now.
- `lost_aio`: was there, gone now.
- `gained_citation`: we weren't cited, now we are.
- `lost_citation`: we were cited, now we're not.
- `new_competitor`: competitor cited now who wasn't before.
3. Add a "Changes since last run" section to the report. If no prior run, skip.
## Universal gotchas
- **AIO comes in two shapes, both common.** Inline: read `.ai_overview.{markdown, text_blocks, reference_links}` directly. Token: `.ai_overview` has `page_token` + `error` and an empty body, so chain `google_ai_overview` on the token within <60s (do not cache it). Decide by whether `reference_links` is non-empty; never exit just because `page_token` is absent.
- **AIO is API-key only; MCP can't do it.** The MCP catalog has no `google` / `google_ai_overview` tools, and `google_search_light` returns no `ai_overview` field. AI Mode is the only surface this skill can track over MCP.
- **Google fixed at 10 results/page since Sept 2025.** Don't pass `num=100` to the `google` step, it's ignored.
- **`google_domain` + ccTLD params deprecated April 2025.** Use `gl`/`hl` only.
- **AI Mode and AIO are independent.** A keyword can trigger one and not the other; report both columns separately.
- **`reference_links[]` may include duplicates or near-duplicates** (same domain, different paths). Group by eTLD+1 when computing "domains cited".
- **Domain match should be eTLD+1, not host string.** `www.searchapi.io` and `searchapi.io` are the same advertiser; `blog.example.com` and `example.com` are usually the same site.
- **Don't fabricate.** Every claim in the report must come from a row in the JSON. If `has_aio` is false, do not invent an "AIO answer summary", say it's absent.
- **Cost note:** for an N-keyword × L-locale run, the AIO body is `N × L` `google` calls when AIO returns inline, plus one extra `google_ai_overview` call for each keyword that comes back in the token shape (worst case `N × L × 2`). Add another `N × L` if AI Mode is on. Surface the call count before large runs.
## Handoffs
- **Full SEO audit including AIO** → use [`seo-audit`](../seo-audit/SKILL.md) instead. That skill calls into this one for the AIO section.
- **Organic position tracking (not AIO)** → [`rank-tracking`](../rank-tracking/SKILL.md). Use `google_rank_tracking` there for dedup'd top-100 snapshots.
- **Brand mentions across web/social/news** → [`brand-monitoring`](../brand-monitoring/SKILL.md).
- **Engine-level reference (params, response fields, deprecations)** → [`searchapi-best-practices`](../searchapi-best-practices/SKILL.md).
## Never fabricate
Every claim about a keyword's AIO state must be backed by:
1. The exact `curl` or MCP tool call run.
2. An excerpt from the response (`ai_overview` object or `null`, the reference list, the markdown body if fetched).
If you summarise an AIO body the user didn't see, cite the response excerpt inline. If a keyword's row says "AIO: ❌", that's because step 1's response had `ai_overview: null` for that keyword, not because you guessed.
No comments yet. Be the first to comment!