Google AI Overviews now appear on over 30% of commercial queries, and brands cited in those overviews capture clicks without ranking in the traditional top 10. Tracking whether your brand appears in AI Overviews requires checking each target keyword with the include_ai_overview parameter and parsing the response for brand mentions. This tutorial builds a daily GEO visibility tracker at $0.005 per keyword check.
Prerequisites
- Python 3.8+
- requests library
- A Scavio API key from scavio.dev
- A list of target keywords to monitor
Walkthrough
Step 1: Set up the GEO tracking configuration
Define your brand terms and target keywords to monitor for AI Overview citations.
import os, requests, json, sqlite3
from datetime import datetime
API_KEY = os.environ['SCAVIO_API_KEY']
SH = {'x-api-key': API_KEY, 'Content-Type': 'application/json'}
BRAND_TERMS = ['scavio', 'scavio.dev', 'scavio api']
KEYWORDS = ['best serp api 2026', 'search api for agents', 'google search api python',
'web scraping alternative', 'ai agent search tool']
db = sqlite3.connect('geo_visibility.db')
db.execute('''CREATE TABLE IF NOT EXISTS checks (
keyword TEXT, checked_at TEXT, has_ai_overview INTEGER,
brand_cited INTEGER, citation_text TEXT, position INTEGER
)''')
db.commit()
print(f'Tracking {len(KEYWORDS)} keywords for {len(BRAND_TERMS)} brand terms')Step 2: Check AI Overview presence and brand citations
Query each keyword with include_ai_overview and parse for brand mentions.
def check_keyword(keyword, brand_terms):
data = requests.post('https://api.scavio.dev/api/v1/search',
headers=SH, json={'query': keyword, 'country_code': 'us',
'include_ai_overview': True}).json()
ao = data.get('ai_overview', {})
ao_text = json.dumps(ao).lower() if ao else ''
cited = any(b in ao_text for b in brand_terms)
org = data.get('organic_results', [])
pos = next((r['position'] for r in org
if any(b in r.get('link', '').lower() for b in brand_terms)), None)
now = datetime.now().isoformat()
db.execute('INSERT INTO checks VALUES (?,?,?,?,?,?)',
(keyword, now, 1 if ao else 0, 1 if cited else 0,
ao_text[:500] if cited else '', pos))
db.commit()
return {'keyword': keyword, 'has_ao': bool(ao), 'cited': cited, 'position': pos}
for kw in KEYWORDS:
r = check_keyword(kw, BRAND_TERMS)
status = 'CITED' if r['cited'] else ('AO present' if r['has_ao'] else 'No AO')
print(f' {kw:35} | {status:12} | Organic: #{r["position"] or "-"}')Step 3: Generate a daily visibility report
Aggregate check results into a daily GEO visibility score.
def daily_report():
today = datetime.now().strftime('%Y-%m-%d')
rows = db.execute(
"SELECT keyword, has_ai_overview, brand_cited, position FROM checks WHERE checked_at LIKE ?",
(f'{today}%',)).fetchall()
total = len(rows)
ao_count = sum(1 for r in rows if r[1])
cited_count = sum(1 for r in rows if r[2])
avg_pos = [r[3] for r in rows if r[3]]
print(f'\nGEO Visibility Report - {today}')
print(f' Keywords checked: {total}')
print(f' AI Overviews present: {ao_count}/{total} ({ao_count/total*100:.0f}%)')
print(f' Brand cited in AO: {cited_count}/{total} ({cited_count/total*100:.0f}%)')
if avg_pos:
print(f' Avg organic position: {sum(avg_pos)/len(avg_pos):.1f}')
print(f' Cost: ${total * 0.005:.3f}')
return {'date': today, 'total': total, 'ao_rate': ao_count/total, 'citation_rate': cited_count/total}
daily_report()Step 4: Track citation trends over time
Compare daily scores to detect GEO visibility gains or losses.
def trend_report(days=7):
rows = db.execute(
'SELECT DATE(checked_at) as d, AVG(has_ai_overview), AVG(brand_cited) FROM checks GROUP BY d ORDER BY d DESC LIMIT ?',
(days,)).fetchall()
print(f'\nGEO Visibility Trend ({len(rows)} days):')
for date, ao_rate, cite_rate in rows:
bar_ao = '#' * int(ao_rate * 20)
bar_cite = '#' * int(cite_rate * 20)
print(f' {date} | AO: {ao_rate*100:5.1f}% {bar_ao:20} | Cited: {cite_rate*100:5.1f}% {bar_cite:20}')
if len(rows) >= 2:
change = (rows[0][2] - rows[-1][2]) * 100
direction = 'UP' if change > 0 else 'DOWN' if change < 0 else 'FLAT'
print(f' Citation rate {direction} {abs(change):.1f}pp over {len(rows)} days')
trend_report()Python Example
import os, requests, json
SH = {'x-api-key': os.environ['SCAVIO_API_KEY'], 'Content-Type': 'application/json'}
def check_geo(keyword, brand):
data = requests.post('https://api.scavio.dev/api/v1/search',
headers=SH, json={'query': keyword, 'country_code': 'us', 'include_ai_overview': True}).json()
ao = data.get('ai_overview', {})
cited = brand.lower() in json.dumps(ao).lower() if ao else False
print(f'{keyword}: AO={bool(ao)}, Brand cited={cited}. Cost: $0.005')
check_geo('best serp api 2026', 'scavio')JavaScript Example
const SH = { 'x-api-key': process.env.SCAVIO_API_KEY, 'Content-Type': 'application/json' };
async function checkGeo(keyword, brand) {
const data = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST', headers: SH,
body: JSON.stringify({ query: keyword, country_code: 'us', include_ai_overview: true })
}).then(r => r.json());
const ao = data.ai_overview || {};
const cited = JSON.stringify(ao).toLowerCase().includes(brand.toLowerCase());
console.log(`${keyword}: AO=${!!data.ai_overview}, Cited=${cited}`);
}
checkGeo('best serp api 2026', 'scavio').catch(console.error);Expected Output
Tracking 5 keywords for 3 brand terms
best serp api 2026 | CITED | Organic: #4
search api for agents | AO present | Organic: #6
google search api python | No AO | Organic: #8
web scraping alternative | AO present | Organic: #12
ai agent search tool | CITED | Organic: #5
GEO Visibility Report - 2026-05-19
Keywords checked: 5
AI Overviews present: 4/5 (80%)
Brand cited in AO: 2/5 (40%)
Avg organic position: 7.0
Cost: $0.025