Monitor Google AI Overviews for your keywords
An AI Overview above the organic results changes what an organic ranking is worth, depending on whether it appears and whether it cites you. To monitor it, request the overview with the Google results for a fixed keyword list, per market, and track three numbers over time: how often an overview appears, how often it cites you, and where it skips pages that already rank.
Definitions
An AI Overview is the AI-generated summary Google shows at the top of some results pages, with links to supporting sources. Google says it shows one when “our systems determine that generative AI can be especially helpful” (AI Overviews in Google Search help, checked 2026-09-17).
- Presence rate: the share of tracked keywords whose results page carried an AI Overview.
- Citation share: among keywords with an overview, the share that cite a URL on your domain.
- Rank gap: keywords where your domain ranks organically but the overview does not cite it.
Step 1: request the overview with the results
{
"query": "project management software",
"country": "US",
"include": { "aioverview": { "markdown": true } }
}
Send it to POST /v1/monitor/google, or as the payload of a task with "taskType": "GOOGLE" for keyword lists.
The request fields that matter for AI Overview tracking:
| Field | Notes |
|---|---|
query |
The search query. Required. |
country or gl |
Result geography (ISO 3166-1 alpha-2). GET /v1/countries?model=aioverview lists the countries where the overview can be requested. |
location or uule |
City-level targeting: a Google canonical City,Region,Country name, or a pre-encoded UULE. Mutually exclusive. |
pages |
1 to 10 result pages; each page after the first is charged. |
include.aioverview |
An object; its presence requests the overview. {"markdown": true} also returns it as Markdown. |
include.paaAioverview |
Fills AI-Overview-type People Also Ask items with their Markdown and sources. |
curl
curl -X POST https://api.answerline.dev/v1/monitor/google \
-H "Authorization: Bearer $ANSWERLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "project management software",
"country": "US",
"include": { "aioverview": { "markdown": true } }
}'
Python (requests)
import os
import requests
API = "https://api.answerline.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['ANSWERLINE_API_KEY']}"}
resp = requests.post(
f"{API}/v1/monitor/google",
headers=HEADERS,
json={
"query": "project management software",
"country": "US",
"include": {"aioverview": {"markdown": True}},
},
timeout=360,
)
resp.raise_for_status()
result = resp.json()["result"]
overview = result.get("aioverview")
print("overview shown:", overview is not None)
TypeScript (@answerline/sdk)
import { Client } from "@answerline/sdk";
interface AiOverview {
text?: string;
markdown?: string;
sources?: { position: number; url: string; label?: string; description?: string; redirectLink?: string }[];
citationPills?: { citationPillId: number; position: number; url: string; domain: string; label: string }[];
relatedLinks?: { citationPillId: number; url: string; domain: string; label: string }[];
}
interface GoogleResult {
organicResults?: { position: number; title: string; link: string; page: number }[];
aioverview?: AiOverview | null;
}
const client = new Client({ apiKey: process.env.ANSWERLINE_API_KEY! });
const { result } = await client.monitor.google<GoogleResult>({
query: "project management software",
country: "US",
include: { aioverview: { markdown: true } },
});
console.log(result.aioverview ? result.aioverview.sources?.map((s) => s.url) : "no overview");
The TypeScript SDK is on npm as @answerline/sdk; the Python examples use requests.
Step 2: read the response
result.aioverview is null when the results page had no AI Overview. Otherwise it holds:
| Field | Meaning |
|---|---|
text |
The overview as plain text. |
markdown |
The overview as Markdown, when include.aioverview.markdown is true. |
sources[] |
The source rail: position (1-based), url, label, description, and redirectLink when Google served the link as a redirect. |
citationPills[] |
The inline citation chips, one entry per chip-and-source pair: citationPillId, position (into aioverview.sources), url, domain, label, optional description and redirectLink. Omitted when there are no chips. |
relatedLinks[] |
Links in a chip’s “related links” flyout that are not in the source rail. They share the chip’s citationPillId and have no position. |
videos[] |
Videos in the overview: url, title, thumbnail, source, platform, date, duration. |
ads[] |
Sponsored ads inside the overview: position, title, url, type, plus domain and description for text ads or price, old_price and store for shopping ads. |
A chip citing several sources appears once per source with a shared citationPillId; group by it to rebuild the chip. Because related links also appear inside markdown, the Markdown can contain more URLs than sources and citationPills; relatedLinks accounts for the extras.
Next to it, result.organicResults lists organic results with position, title, link, displayedLink, snippet, date, page and sitelinks. With include.paaAioverview, result.peopleAlsoAsk items of type AIOVERVIEW also carry markdown and sources: smaller overviews for the follow-up questions, useful for People Also Ask research.
Wherever a redirectLink appears, the sibling url or link already holds the resolved destination. To resolve one yourself, POST /v1/monitor/google/goto with {"url": "<redirectLink>"}; it is free.
Step 3: three numbers per keyword
from urllib.parse import urlsplit
def on_domain(url: str, domain: str) -> bool:
host = urlsplit(url).hostname or ""
return host == domain or host.endswith("." + domain)
def aio_metrics(result: dict, domain: str) -> dict:
overview = result.get("aioverview")
organic = next((r["position"] for r in result.get("organicResults", []) if on_domain(r["link"], domain)), None)
cited = next((s["position"] for s in (overview or {}).get("sources", []) if on_domain(s["url"], domain)), None)
in_chip = any(on_domain(p["url"], domain) for p in (overview or {}).get("citationPills", []))
return {"has_overview": overview is not None, "cited_position": cited, "in_chip": in_chip, "organic_position": organic}
- Presence rate: the share of keywords with
has_overview. Track it per market, since each is a separate results page. - Citation share: among keywords with an overview, the share with a
cited_position.in_chipadds whether the citation is attached to a specific statement rather than only listed in the rail. - Rank gap: keywords where you rank organically but the overview does not cite you. Start with these, since the pages already rank for the query.
To see who is cited instead, group aioverview.sources by host across keywords:
from collections import Counter
def cited_hosts(results: list[dict]) -> list[tuple[str, int]]:
counts = Counter()
for result in results:
for source in (result.get("aioverview") or {}).get("sources", []):
counts[urlsplit(source["url"]).hostname or ""] += 1
return counts.most_common(20)
The hosts at the top of that list are the competitors for the overview, which are not always the ones at the top of the organic results. Competitor analysis covers reporting on this.
Worked example
Illustrative keyword data, computed with the functions above for one market:
| Keyword | has_overview |
cited_position |
organic_position |
Reading |
|---|---|---|---|---|
| A | true | 2 | 4 | Cited and ranking |
| B | true | null | 3 | Rank gap: fix first |
| C | false | null | 1 | No overview to win |
| D | true | null | null | Neither: content gap |
For these four keywords, presence rate is 3/4 = 75%, citation share is 1/3 (A among A, B, D), and the rank gap list is B.
Step 4: run it on a schedule
Submit the keyword list as batches of GOOGLE tasks with an idempotency key per keyword, market and day, and collect results by webhook:
tasks = [
{
"taskType": "GOOGLE",
"payload": {"query": kw, "country": country, "include": {"aioverview": {"markdown": True}}},
"idempotencyKey": f"aio-{kw}-{country}-2026-09-15",
"webhook": {"url": "https://example.com/hooks/aioverview"},
}
for kw in keywords
for country in ("US", "GB")
]
for start in range(0, len(tasks), 500):
requests.post(f"{API}/v1/async/task/batch", headers=HEADERS, json=tasks[start:start + 500], timeout=360)
A batch holds 1 to 500 tasks. The webhook receives the GET /v1/async/task/{id} JSON, with the Google result under response.result. Tracking AI Overviews with async batches and webhooks covers the SDK version and webhook handler in detail, and the rank-tracking pipeline post covers the plumbing. A single run is a snapshot, so compare trends across runs.
What it costs
Per billing, at the Starter tier:
| Request | Price |
|---|---|
| 1 page, no overview | $0.00103 |
1 page with include.aioverview |
$0.00174 |
1 page with include.aioverview and include.paaAioverview |
$0.00174 |
3 pages with include.aioverview |
$0.00316 |
The overview add-on is $0.00071, charged once when Google shows an overview; paaAioverview is free; each extra page adds $0.00071. Worked example: 200 keywords x 2 markets as 1-page tasks with the overview is 400 tasks x $0.00174 = $0.696 per run. See pricing for the tiers and cost planning for cadence trade-offs.
Decision criteria
- Pages: AI Overview metrics need only page 1. Add
pagesonly when you also need organic positions beyond the first ten results for the rank-gap comparison. - Markdown: request
markdown: truewhen you store or diff the overview’s wording; skip it for presence and citation counts, which need onlysources. paaAioverview: turn it on for content research on follow-up questions; leave it off for daily tracking, since it makes responses slower.- Sync or async: synchronous for a one-off check of a keyword; async batches for any tracked list (sync, async or webhooks).
Pitfalls
- Counting
nullas an error. A nullaioverviewis a result: no overview for that page. - Merging markets. Each country is a different results page; aggregate only within one.
- Matching domains with substring checks.
example.comshould not matchnotexample.com; compare hostnames as inon_domain. - Ignoring
relatedLinks. URLs inmarkdownbut not insourcescome from there; do not count them as rail citations. - Single snapshots. Overviews change between runs; track trends (SERP feature change tracking, answer volatility).
For Google’s conversational surface, see AI Mode; for the endpoint’s full field list, the Google Search engine page.
Related: getting AI Overviews as JSON and tracking AI Overviews with async batches and webhooks.
Questions
How much does a Google request with the AI Overview cost?
One results page with include.aioverview costs $0.00174 on every tier. Each page after the first adds $0.00071, so a 3-page task with the AI Overview costs $0.00316.
What is the rank gap on a keyword?
Keywords where your domain ranks organically but the overview does not cite it. Compare organicResults positions with the domains the overview cites for the same keyword and market; that split shows where an existing ranking stops earning visibility.
Does include.paaAioverview cost extra on top of include.aioverview?
No. include.paaAioverview is free; include.aioverview adds $0.00071 once when Google shows an overview. Each hydrated People Also Ask item makes the response take longer.
Can I track AI Overviews for a specific city?
Yes. Send location, a Google canonical name such as Austin,Texas,United States, or a uule with the Google Search request, and treat the location as a dimension in every metric.
How do I resolve redirectLink values in the AI Overview?
The sibling url already holds the resolved destination. When you need to resolve a redirectLink yourself, POST it to /v1/monitor/google/goto, which is free.