How to track competitor visibility in Google AI Overviews
Tracking competitor visibility in Google AI Overviews means running the questions your buyers actually ask and recording which rivals the overview names and cites. Because an overview is assembled from a live search, a delivered record arrives with the pages it drew on attached — so you don't just learn that a rival won the query, you get the address of the page that won it.
One query is a browser tab. A query set, every week, with the citation order intact and one honest caveat kept in view — that Google shows no overview for many searches — is one POST to https://api.agentgeo.org/v1/fetches and about forty lines of code. This guide covers the manual sweep, the API version in curl and Python, the same job handed to an agent over MCP, and the ways competitor data quietly misleads — chief among them the two denominators every AI Overview rate needs.
To track competitor visibility in Google AI Overviews, run your category questions and record two things per rival: whether the overview names them in answerText, and whether their domain appears in sources[], and in what position. Do it once by hand to learn the shape of your category. Then do it on a schedule — and read every rival's count against how many of your queries produced an overview at all, not against all of them.
The manual way (and why it stops working)
For a first pass it really is simple. Search the question a buyer would ask — "best expense management software for a growing startup", not "is Acme any good" — find the AI Overview, and read the prose for every company it names. Then do the part that makes this surface worth checking: expand the reference chips beside the block and note whose domains are there, in order. Repeat across three or four phrasings and you'll know both the competitive set and the pages holding it up. It costs an hour, and that first hour is the most useful one you'll spend on this — as long as you note, as you go, which of those searches returned no overview at all.
It's the second pass where it stops being an hour and starts misleading you.
- One search per tab. A category hides twenty or thirty buying questions, and expanding and reading each overview's reference chips is slow. You'll quietly narrow the sweep to the three queries you like, which is exactly how a blind spot forms.
- Half your queries may show no overview. Google answers some searches inline and leaves the rest as plain results. By hand the no-overview ones get skipped and forgotten, so months later you can't tell whether a rival left the set or the overview it lived in did.
- No history, so no diff. The question worth answering is whether Globex now appears in four more of your overviews than it did in June — and a browser tab stores neither June nor the count you'd subtract it from.
- Citation order dies in the clipboard. Drag the reference chips into a sheet and the positions scramble or the URLs vanish behind their titles. Cited at 1 and cited at 6 are different results, and position is the first casualty of copy-paste.
- A typed list never reaches a report. A hand-copied set of names isn't a chart in a client deck, a row in a table, or a job that pings you the week a new rival enters the overviews.
Do it with one API call
AgentGEO fetches the overview and returns the raw record: the full prose, plus every cited reference with its title, URL and position. One POST through a managed-scraper engine that's maintained for you, and the same envelope across all six engines — here it's just google_ai_overview.
curl -X POST https://api.agentgeo.org/v1/fetches \
-H "Authorization: Bearer ag_live_..." \
-H "Content-Type: application/json" \
-d '{"query": "best expense management software for a growing startup", "surfaces": ["google_ai_overview"], "country": "US", "language": "en"}'What comes back is a plain JSON envelope — no HTML, no scraping artifacts. For competitor work the intelligence sits in two places: the names Google wrote into the prose, and — denser and more useful — the ordered list of pages it read in order to write it.
{
"id": "run_5f2a9c07be41",
"query": "best expense management software for a growing startup",
"surfaces": ["google_ai_overview"],
"status": "completed",
"recordsDelivered": 1,
"creditsCharged": 1,
"answers": [
{
"surfaceKey": "google_ai_overview",
"status": "delivered",
"answerText": "For a growing startup, Globex is frequently highlighted for corporate cards paired with automatic receipt capture, while Initech is noted for tight QuickBooks and Xero syncing on smaller finance teams ...",
"sources": [
{ "title": "Globex Expense - corporate cards & receipt capture", "url": "https://globex.com/expense", "position": 1 },
{ "title": "Globex vs Initech for startup finance teams", "url": "https://globex.com/compare/initech", "position": 2 },
{ "title": "Best expense management software in 2026", "url": "https://example.com/best-expense-management", "position": 3 }
],
"fetchedAt": "2026-07-18T09:14:22Z",
"latencyMs": 61840
}
]
}The scan itself is short. Keep the competitive set in one dict — display name to root domain — check both places a rival can surface, and carry two counters instead of one: the queries that produced an overview, and the ones that didn't.
import requests
from collections import defaultdict
from urllib.parse import urlparse
KEY = "ag_live_your_key_here"
# The competitive set you track: display name -> root domain.
RIVALS = {"Acme": "acme.com", "Globex": "globex.com", "Initech": "initech.com"}
QUERIES = [
"best expense management software for a growing startup",
"expense management with corporate cards and receipt capture",
"expense software that syncs with QuickBooks",
"how much does expense management software cost per user",
]
def host(url):
# Normalise so globex.com and www.globex.com are the same company.
h = urlparse(url).netloc.lower()
return h[4:] if h.startswith("www.") else h
named = defaultdict(set) # brand -> queries whose prose named it
cited = defaultdict(list) # brand -> (query, position, url) it was cited on
overviews = 0 # queries that actually produced an overview
missing = [] # queries where Google showed no AI Overview
for query in QUERIES:
resp = requests.post(
"https://api.agentgeo.org/v1/fetches",
json={"query": query, "surfaces": ["google_ai_overview"],
"country": "US", "language": "en"}, # search gl= and hl=
headers={"Authorization": f"Bearer {KEY}"},
timeout=200, # an AI Overview SERP round-trip alone runs 40-90s
)
resp.raise_for_status()
for answer in resp.json()["answers"]:
if answer.get("status") == "failed":
# No overview here: zero credits, and NOT a rival leaving the set.
# Keep it out of the denominator instead of scoring it as a loss.
missing.append(query)
continue
overviews += 1 # the denominator a fair share must be read against
text = (answer.get("answerText") or "").lower()
for brand in RIVALS:
if brand.lower() in text:
named[brand].add(query)
for src in answer.get("sources") or []:
h = host(src["url"])
for brand, domain in RIVALS.items():
if h == domain or h.endswith("." + domain):
cited[brand].append((query, src["position"], src["url"]))
# Read every rival's coverage against the overviews that ran, not all queries.
for brand in RIVALS:
hits = cited[brand]
print(f"\n{brand}: named in {len(named[brand])}/{overviews}, "
f"cited on {len({q for q, _, _ in hits})}/{overviews} overviews")
for query, position, url in sorted(hits, key=lambda row: row[1]):
print(f" #{position} {url} ({query})")
print(f"\nno overview for {len(missing)}/{len(QUERIES)} queries: {missing}")Run that over your query set and you get a per-rival grid: how many overviews named each rival, which pages carried them there, and — kept honestly apart — the queries that produced no overview to be counted in. Each delivered record costs one credit; a query with no overview comes back failed at zero, which is exactly why missing is a list rather than a silent skip. While you're shaping the parser, an ag_test_ key returns clearly labelled demo records at zero credits.
The URLs are the payoff. Read the column and each cited page tells you something specific about how you're losing the query:
| The page the overview cited | What it tells you | The move |
|---|---|---|
globex.com/expense at #1 | Google is using a rival's own product page as the reference for the whole category, and putting it first. | Publish the same facts a model can lift — features, limits and numbers in text, not behind a demo request. |
globex.com/compare/initech | A competitor wrote the head-to-head, so it now owns how the category gets framed inside the overview. | Write the comparison they haven't: yours against theirs, on your own domain. |
| A third-party roundup | No vendor is winning this query — an editorial list is supplying the answer instead. | Get into that roundup, or out-answer it with a page narrower and more specific than a list of ten. |
| A rival's docs subdomain | The overview reached for how-to content, so this query is really a setup or integration question. | Answer the same task on docs you control; support pages get cited on intent the sales page can't reach. |
| Your own domain, at #5 | You're inside the reference set but doing little of the writing, and the position won't show on a present/absent flag. | A position problem, not a coverage one — hold your page against whatever sits at #1 to #2. |
This is where competitor coverage on AI Overviews needs two denominators, not one. A rival that shows up in four of your ten queries has not been measured until you know how many of those ten produced an overview at all — Google shows one for some searches and nothing for others. Divide a rival's citations by every query you asked and you understate its real coverage by roughly twenty points; divide by the overviews that actually ran and the number is honest. And when an overview simply doesn't appear, that is not a rival exiting the set — it's a query with no set to be in. Count each rival against the overviews that ran, and keep the no-overview queries in a list of their own.
Let your agent do it
Citations point at pages, and pages are something an agent can go and read — which is why this job belongs in an agent rather than a spreadsheet. Wire the server into Claude Code once:
claude mcp add agentgeo -- npx -y agentgeo-mcp --key ag_live_...agentgeo-mcp speaks MCP over stdio, ships with zero npm dependencies and needs Node.js 18+. It exposes exactly one tool, fetch_raw_answers, with query and surfaces required and country defaulting to "US". After that the sweep is a sentence:
Pull google_ai_overview for our four expense management queries. For Acme, Globex and Initech, tell me which overviews name them and which cite their domain — but read every count against how many of the four produced an overview, not against all four. Then open the page at position 1 for the first query and tell me what it covers that ours doesn't.The records arrive raw. Whether Globex turning up in two extra overviews is a real gain or ordinary churn is a judgment your agent makes with your context — AgentGEO ranks nothing, scores nothing and draws no conclusions, by design. If you'd rather script it, the Python and curl paths hit the identical endpoint.
Common problems
Five things that make competitor data on this surface read wrong, all fixable in the parser or the schedule:
- A missing overview is not a rival leaving the set. When Google shows no AI Overview, the record returns
status: "failed"with "Google returned no AI Overview for this query" at zero credits. Log those queries in their own list. Silently reading them as "cited nobody" makes every rival — and you — look like they're bleeding coverage. - Every rate needs two denominators. Coverage is how many of your queries produced an overview; share is how many of those overviews cited a given rival. Divide by the query set and you understate a rival by about twenty points. Report
cited on X of the overviews that ran, and keep the overview count visible next to it. - A failed AI Overview record is not a snapshot to redeem. The chatbot surfaces hand back a
providerFields.snapshot_idon a slow scrape, which you can retry against for free. AI Overviews come through a different SERP fetch path, so there is no snapshot id here — re-run the query later instead, and accept that it may honestly fail again. - Match domains in
sources[], brand names inanswerText. A third-party review titled "Globex vs Initech" matches a title search for both and was written by neither. Compare the host inurlfor citations, and use a word-boundary regex —re.search(r"\bglobex\b", text, re.I)— for prose mentions. - Locale changes the citation list — and whether there's an overview at all.
countrysets the searchgl=andlanguagesetshl=, so a US-English overview and a German one cite different rivals, and one market can show an overview where the other shows none. Keep a separate log per market rather than one file you can't disentangle later.
Summary
For a one-off read on who Google's overviews favour, run the search yourself — the reference chips are right there and it costs nothing. When the sweep has to cover a query set, repeat weekly, and produce something you can subtract last month from, fetch it instead: one call per query returns the prose and the ordered citation list, forty lines of Python turn that into a per-rival grid, and the URLs in that grid are the real output — a short list of pages you can go and beat. Just count each rival against the overviews that actually ran, and keep the no-overview queries apart, or a quiet week for Google will read as a rival's collapse.
See which rivals Google's overviews name — and which of their pages it reads. Start free — no credit card and run your first competitor sweep in a couple of minutes.
Start for freeFrequently asked questions
- How do I see which competitors Google AI Overviews cite?
- Search the category question your buyers ask — "best expense management software for a growing startup" rather than a branded query — read the overview for every company it names, and expand the reference chips for the domains behind it. For repeatable tracking, POST the same query to
https://api.agentgeo.org/v1/fetcheswith"surfaces": ["google_ai_overview"]and scananswerTextfor each rival's name andsources[]for each rival's domain and position. - What does it mean when a query returns no AI Overview?
- Google doesn't answer every search inline, so the record comes back
status: "failed"with "Google returned no AI Overview for this query" at zero credits. That is not a rival that dropped out — it's a query with no overview to be in. Log those queries separately and count each competitor against the overviews that actually ran, otherwise a market where Google shows fewer overviews will read as every rival losing ground. - Can I track competitor citations in AI Overviews automatically?
- Yes. One HTTP call to
https://api.agentgeo.org/v1/fetcheswith"surfaces": ["google_ai_overview"]returns the overview and its reference list as JSON, so a short script can loop your query set, test every competitor's name and domain, and write a grid to a table on a schedule. You can also connect theagentgeo-mcpserver and have an agent run the same sweep from a plain-language instruction, with no parser to maintain. - Why do AI Overview competitor rates need two denominators?
- Because two different things vary: how many of your queries produced an overview at all (coverage), and how many of those overviews cited a given rival (share). A rival cited in four of ten queries looks weak until you learn only five of the ten produced an overview — then it's four of five. Dividing by the whole query set systematically understates every competitor by roughly twenty points and hides the queries where nobody was cited because nothing was shown.
- Is competitor tracking in AI Overviews different from Perplexity?
- The scan is the same shape, but AI Overviews are the densest citation surface of the six — a delivered record essentially always carries references, where Perplexity is merely near-always. The catch is unique to this surface: many queries return no overview, so you carry a coverage denominator alongside the share. The
surfacesarray takes one to six engine keys, so you can run both and keep the citation columns in separate tables.
Keep reading