Generative Engine Optimization (GEO) measures how visible your brand is in AI-generated search results. This tutorial builds an automated reporting pipeline that tracks your GEO visibility across keywords, compares against competitors, and generates weekly reports. Each keyword scan costs $0.005.
Prerequisites
- Python 3.8+
- requests library
- A Scavio API key from scavio.dev
- Target keywords and competitor domains
Walkthrough
Step 1: Define GEO visibility metrics
Measure multiple signals that indicate how well your content ranks in generative results.
import os, requests, json
from datetime import datetime
from collections import defaultdict
API_KEY = os.environ['SCAVIO_API_KEY']
SH = {'x-api-key': API_KEY, 'Content-Type': 'application/json'}
BRAND = 'scavio.dev'
COMPETITORS = ['tavily.com', 'serpapi.com']
KEYWORDS = [
'best search api for ai agents',
'web search api comparison',
'mcp search tool setup',
'serp api free tier',
'search api for rag pipeline',
]
def geo_score_keyword(keyword, domain):
data = requests.post('https://api.scavio.dev/api/v1/search',
headers=SH, json={'query': keyword, 'country_code': 'us'}, timeout=10).json()
ai = data.get('ai_overview', data.get('answer_box', {}))
organic = data.get('organic_results', [])
featured = data.get('featured_snippet', {})
paa = data.get('people_also_ask', [])
domain_l = domain.lower()
# Score components (0-100 each)
ai_score = 30 if (ai and domain_l in json.dumps(ai).lower()) else 0
org_pos = next((i+1 for i, r in enumerate(organic) if domain_l in r.get('link', '').lower()), None)
org_score = max(0, 30 - (org_pos - 1) * 3) if org_pos else 0
feat_score = 20 if (featured and domain_l in json.dumps(featured).lower()) else 0
paa_score = 20 if any(domain_l in json.dumps(q).lower() for q in paa) else 0
total = ai_score + org_score + feat_score + paa_score
return {'keyword': keyword, 'total': total, 'ai': ai_score, 'organic': org_score,
'featured': feat_score, 'paa': paa_score, 'organic_pos': org_pos}
print(f'GEO Visibility Scan for {BRAND}\n')
scores = [geo_score_keyword(kw, BRAND) for kw in KEYWORDS]
for s in scores:
pos = f'#{s["organic_pos"]}' if s['organic_pos'] else '-'
print(f' {s["keyword"][:40]:40} | GEO: {s["total"]:3} | Pos: {pos}')
avg = sum(s['total'] for s in scores) / len(scores)
print(f'\nAverage GEO Score: {avg:.0f}/100 | Cost: ${len(KEYWORDS) * 0.005:.3f}')Step 2: Compare against competitors
Run the same GEO scan for competitor domains and rank them.
def competitive_geo(keywords, brand, competitors):
all_domains = [brand] + competitors
domain_scores = defaultdict(list)
for kw in keywords:
for domain in all_domains:
score = geo_score_keyword(kw, domain)
domain_scores[domain].append(score)
print(f'\n=== GEO Competitive Analysis ===')
rankings = []
for domain, keyword_scores in domain_scores.items():
avg = sum(s['total'] for s in keyword_scores) / len(keyword_scores)
ai_pct = sum(1 for s in keyword_scores if s['ai'] > 0) / len(keyword_scores) * 100
rankings.append({'domain': domain, 'avg_geo': avg, 'ai_citation_pct': ai_pct})
rankings.sort(key=lambda x: x['avg_geo'], reverse=True)
for i, r in enumerate(rankings, 1):
marker = ' <-- you' if r['domain'] == brand else ''
print(f' {i}. {r["domain"]:25} | GEO: {r["avg_geo"]:5.1f} | AI cited: {r["ai_citation_pct"]:.0f}%{marker}')
return rankings
rankings = competitive_geo(KEYWORDS, BRAND, COMPETITORS)
print(f'\nTotal cost: ${len(KEYWORDS) * (1 + len(COMPETITORS)) * 0.005:.3f}')Step 3: Generate the weekly GEO report
Compile all data into a formatted weekly report with trends and recommendations.
def weekly_geo_report(brand, scores, rankings):
print(f'\n{"=" * 60}')
print(f' WEEKLY GEO VISIBILITY REPORT')
print(f' Brand: {brand} | Date: {datetime.now().strftime("%Y-%m-%d")}')
print(f'{"=" * 60}')
avg_geo = sum(s['total'] for s in scores) / len(scores)
ai_cited = sum(1 for s in scores if s['ai'] > 0)
print(f'\n Overall GEO Score: {avg_geo:.0f}/100')
print(f' AI Citations: {ai_cited}/{len(scores)} keywords')
print(f' Competitive Rank: {next((i+1 for i, r in enumerate(rankings) if r["domain"] == brand), "?")} of {len(rankings)}')
# Breakdown
print(f'\n Score Breakdown:')
print(f' AI Overview: {sum(s["ai"] for s in scores)/len(scores):.0f}/30')
print(f' Organic: {sum(s["organic"] for s in scores)/len(scores):.0f}/30')
print(f' Featured: {sum(s["featured"] for s in scores)/len(scores):.0f}/20')
print(f' People Ask: {sum(s["paa"] for s in scores)/len(scores):.0f}/20')
# Opportunities
weak = [s for s in scores if s['total'] < 30]
if weak:
print(f'\n Improvement Opportunities:')
for s in weak:
print(f' - {s["keyword"][:40]} (GEO: {s["total"]})')
print(f'\n Weekly cost: ${len(KEYWORDS) * (1 + len(COMPETITORS)) * 0.005 * 7:.2f}')
weekly_geo_report(BRAND, scores, rankings)Python Example
import os, requests, json
SH = {'x-api-key': os.environ['SCAVIO_API_KEY'], 'Content-Type': 'application/json'}
def geo_check(keyword, domain):
data = requests.post('https://api.scavio.dev/api/v1/search',
headers=SH, json={'query': keyword, 'country_code': 'us'}, timeout=10).json()
ai = data.get('ai_overview', data.get('answer_box', {}))
cited = domain.lower() in json.dumps(ai).lower() if ai else False
org = next((i+1 for i, r in enumerate(data.get('organic_results', [])) if domain in r.get('link', '')), None)
print(f'{keyword[:35]:35} | AI: {cited} | Organic: {org or "-"}')
geo_check('best search api 2026', 'scavio.dev')JavaScript Example
const SH = { 'x-api-key': process.env.SCAVIO_API_KEY, 'Content-Type': 'application/json' };
const data = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST', headers: SH,
body: JSON.stringify({ query: 'best search api 2026', country_code: 'us' })
}).then(r => r.json());
const ai = data.ai_overview || data.answer_box || {};
const cited = JSON.stringify(ai).toLowerCase().includes('scavio');
console.log(`GEO visibility: AI cited=${cited}`);Expected Output
GEO Visibility Scan for scavio.dev
best search api for ai agents | GEO: 60 | Pos: #3
web search api comparison | GEO: 30 | Pos: #5
mcp search tool setup | GEO: 50 | Pos: #2
serp api free tier | GEO: 27 | Pos: #7
search api for rag pipeline | GEO: 47 | Pos: #4
Average GEO Score: 43/100 | Cost: $0.025
=== GEO Competitive Analysis ===
1. scavio.dev | GEO: 43.0 | AI cited: 40%
2. serpapi.com | GEO: 38.0 | AI cited: 20%
3. tavily.com | GEO: 32.0 | AI cited: 20%
============================================================
WEEKLY GEO VISIBILITY REPORT
Brand: scavio.dev | Date: 2026-05-21
============================================================
Overall GEO Score: 43/100
AI Citations: 2/5 keywords
Competitive Rank: 1 of 3
Weekly cost: $0.53