Google AI Overviews now appear for a large share of informational queries, and getting cited in them drives significant traffic. Traditional rank trackers charge hundreds of dollars to monitor AI Overview citations. You can build your own tracker with a search API that returns AI Overview data in its SERP response. This tutorial builds a citation monitoring pipeline that checks your target keywords, records which domains get cited, and tracks your citation share over time. Each check costs $0.005 via the Scavio API.
Prerequisites
- Python 3.9+ installed
- requests library installed
- A Scavio API key from scavio.dev
- A list of keywords where you expect AI Overviews
Walkthrough
Step 1: Check if a query triggers an AI Overview
Search for a keyword and check whether the SERP response includes an AI Overview section. Not all queries trigger one, so this step filters your keyword list.
import requests, os
API_KEY = os.environ['SCAVIO_API_KEY']
def check_ai_overview(query: str) -> dict:
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': API_KEY, 'Content-Type': 'application/json'},
json={'query': query, 'country_code': 'us'})
data = resp.json()
ai_overview = data.get('ai_overview', {})
has_overview = bool(ai_overview and ai_overview.get('text'))
citations = ai_overview.get('citations', []) if has_overview else []
return {
'query': query,
'has_ai_overview': has_overview,
'citation_count': len(citations),
'cited_domains': [c.get('domain', '') for c in citations]
}
result = check_ai_overview('what is the best crm for startups')
print(f'AI Overview: {result["has_ai_overview"]}, Citations: {result["citation_count"]}')Step 2: Batch check keywords and find citation patterns
Run through your keyword list and collect citation data. Identify which domains get cited most frequently across your target keywords.
import time
from collections import Counter
def batch_citation_check(keywords: list) -> dict:
results = []
domain_counts = Counter()
for kw in keywords:
data = check_ai_overview(kw)
results.append(data)
for domain in data['cited_domains']:
domain_counts[domain] += 1
time.sleep(0.3)
return {
'total_keywords': len(keywords),
'with_ai_overview': sum(1 for r in results if r['has_ai_overview']),
'top_cited_domains': domain_counts.most_common(10),
'details': results
}
keywords = [
'what is the best crm for startups',
'how to choose a crm 2026',
'crm software comparison',
'best free crm tools'
]
report = batch_citation_check(keywords)
print(f'{report["with_ai_overview"]}/{report["total_keywords"]} keywords have AI Overviews')Step 3: Track your domain citation share
Calculate how often your domain appears in AI Overview citations compared to competitors. This is your AI Overview share of voice.
def citation_share(report: dict, your_domain: str) -> dict:
total_citations = sum(count for _, count in report['top_cited_domains'])
your_citations = sum(count for domain, count in report['top_cited_domains']
if your_domain in domain)
share = (your_citations / max(total_citations, 1)) * 100
# Find keywords where you are cited
your_keywords = [r['query'] for r in report['details']
if any(your_domain in d for d in r['cited_domains'])]
# Find keywords where you are NOT cited
missing_keywords = [r['query'] for r in report['details']
if r['has_ai_overview'] and
not any(your_domain in d for d in r['cited_domains'])]
return {
'your_domain': your_domain,
'citation_share': round(share, 1),
'your_citations': your_citations,
'total_citations': total_citations,
'cited_keywords': your_keywords,
'missing_keywords': missing_keywords
}
share = citation_share(report, 'yoursite.com')
print(f'Citation share: {share["citation_share"]}% ({share["your_citations"]}/{share["total_citations"]})')
print(f'Missing from: {share["missing_keywords"]}')Step 4: Store historical data for trend tracking
Save daily citation data to track whether your AEO efforts are working over time. A simple JSON file stores the time series.
import json
from datetime import date
def save_citation_history(report: dict, your_domain: str) -> None:
history_file = 'citation_history.json'
try:
with open(history_file) as f:
history = json.load(f)
except FileNotFoundError:
history = []
share = citation_share(report, your_domain)
history.append({
'date': date.today().isoformat(),
'citation_share': share['citation_share'],
'your_citations': share['your_citations'],
'total_citations': share['total_citations'],
'keywords_checked': report['total_keywords'],
'keywords_with_overview': report['with_ai_overview']
})
with open(history_file, 'w') as f:
json.dump(history, f, indent=2)
print(f'Saved citation data for {date.today()}')
if len(history) > 1:
prev = history[-2]['citation_share']
curr = share['citation_share']
delta = curr - prev
print(f'Trend: {prev}% -> {curr}% ({delta:+.1f}%)')Step 5: Calculate monitoring costs
Budget your AI Overview monitoring based on keyword count and check frequency. Daily checks of 100 keywords cost $15/month.
def monitoring_budget(num_keywords: int, checks_per_day: int = 1) -> dict:
daily_credits = num_keywords * checks_per_day
monthly_credits = daily_credits * 30
monthly_cost = monthly_credits * 0.005
return {
'keywords': num_keywords,
'daily_checks': checks_per_day,
'monthly_credits': monthly_credits,
'monthly_cost': f'${monthly_cost:.2f}',
'plan': '$30/7K' if monthly_credits <= 7000
else '$100/28K' if monthly_credits <= 28000
else '$250/85K',
'vs_semrush': f'Semrush Position Tracking: $139.95+/mo'
}
for n in [50, 100, 500]:
b = monitoring_budget(n)
print(f'{b["keywords"]} keywords: {b["monthly_cost"]}/mo ({b["plan"]} plan)')
print(f'Comparison: Semrush starts at $139.95/mo for similar tracking')Python Example
import os, requests, time, json
from collections import Counter
from datetime import date
API_KEY = os.environ['SCAVIO_API_KEY']
def check_ai_overview(query):
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': API_KEY, 'Content-Type': 'application/json'},
json={'query': query, 'country_code': 'us'})
data = resp.json()
aio = data.get('ai_overview', {})
citations = aio.get('citations', []) if aio.get('text') else []
return {'query': query, 'has_aio': bool(aio.get('text')),
'domains': [c.get('domain', '') for c in citations]}
def main():
keywords = ['best crm startups', 'crm comparison 2026', 'free crm tools']
domain_counts = Counter()
for kw in keywords:
result = check_ai_overview(kw)
print(f'{"+AIO" if result["has_aio"] else "-AIO"} {kw}: {len(result["domains"])} citations')
for d in result['domains']:
domain_counts[d] += 1
time.sleep(0.3)
print(f'\nTop cited domains:')
for domain, count in domain_counts.most_common(5):
print(f' {domain}: {count}')
print(f'Cost: ${len(keywords) * 0.005:.3f}')
if __name__ == '__main__':
main()JavaScript Example
const API_KEY = process.env.SCAVIO_API_KEY;
async function checkAiOverview(query) {
const resp = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST',
headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ query, country_code: 'us' })
});
const data = await resp.json();
const aio = data.ai_overview || {};
return {
query,
hasAio: Boolean(aio.text),
domains: (aio.citations || []).map(c => c.domain || '')
};
}
async function main() {
const keywords = ['best crm startups', 'crm comparison 2026'];
const domainCounts = {};
for (const kw of keywords) {
const r = await checkAiOverview(kw);
console.log(`${r.hasAio ? '+AIO' : '-AIO'} ${kw}: ${r.domains.length} citations`);
r.domains.forEach(d => { domainCounts[d] = (domainCounts[d] || 0) + 1; });
}
console.log('Top domains:', Object.entries(domainCounts).sort((a,b) => b[1]-a[1]).slice(0,5));
}
main().catch(console.error);Expected Output
+AIO what is the best crm for startups: 4 citations
+AIO how to choose a crm 2026: 3 citations
+AIO crm software comparison: 5 citations
-AIO best free crm tools: 0 citations
3/4 keywords have AI Overviews
Citation share: 8.3% (1/12)
Missing from: ['crm software comparison']
50 keywords: $7.50/mo ($30/7K plan)
100 keywords: $15.00/mo ($30/7K plan)
500 keywords: $75.00/mo ($100/28K plan)