Overview
Automated daily reporting on Generative Engine Optimization metrics. Track which keywords trigger AI Overviews, whether your brand is cited, and how competitor citation share changes over time.
Trigger
Daily cron at 07:00 UTC
Schedule
Daily at 07:00 UTC
Workflow Steps
Load keyword portfolio
Read target keywords grouped by category (brand, category, competitor). Each keyword has a target brand name to track citations for.
Query with AI Overview extraction
Search Google via Scavio with ai_overview enabled for each keyword. Extract the full AI Overview text and citation list.
Parse brand citations
Check AI Overview text for brand mentions and citation URLs. Track own brand, competitor brands, and new entrants.
Calculate share-of-voice
For each keyword category, calculate what percentage of AI Overviews cite the target brand vs competitors. Track daily deltas.
Generate and send report
Compile daily GEO/AEO report with citation counts, share-of-voice by category, and alerts for citation gains/losses. Send via email or Slack.
Python Implementation
import requests, os, json
from datetime import date
from collections import defaultdict
H = {'x-api-key': os.environ['SCAVIO_API_KEY']}
def check_aeo_citation(keyword, brands):
r = requests.post('https://api.scavio.dev/api/v1/search', headers=H,
json={'platform': 'google', 'query': keyword, 'ai_overview': True},
timeout=10).json()
aio = r.get('ai_overview', {}) or {}
aio_text = (aio.get('text', '') or '').lower()
citations = aio.get('citations', [])
brand_citations = {}
for brand in brands:
cited_in_text = brand.lower() in aio_text
cited_in_links = any(brand.lower() in (c.get('domain', '') or '').lower() for c in citations)
brand_citations[brand] = cited_in_text or cited_in_links
return {
'keyword': keyword,
'has_aio': bool(r.get('ai_overview')),
'brand_citations': brand_citations,
'citation_count': len(citations),
}
brands = ['OurBrand', 'CompetitorA', 'CompetitorB']
keywords = ['best crm software 2026', 'crm for startups', 'crm comparison 2026']
report = defaultdict(lambda: {'cited': 0, 'total': 0})
for kw in keywords:
result = check_aeo_citation(kw, brands)
if result['has_aio']:
for brand, cited in result['brand_citations'].items():
report[brand]['total'] += 1
if cited:
report[brand]['cited'] += 1
status = ', '.join(b for b, c in result['brand_citations'].items() if c) or 'none'
print(f"{kw}: {status}")
print(f"\n--- Share of Voice ({date.today()}) ---")
for brand, data in report.items():
sov = data['cited'] / data['total'] * 100 if data['total'] > 0 else 0
print(f" {brand}: {sov:.0f}% ({data['cited']}/{data['total']})")JavaScript Implementation
const H = {"x-api-key": process.env.SCAVIO_API_KEY, "Content-Type": "application/json"};
async function checkAeoCitation(keyword, brands) {
const r = await fetch("https://api.scavio.dev/api/v1/search", {
method: "POST", headers: H,
body: JSON.stringify({platform: "google", query: keyword, ai_overview: true})
}).then(r => r.json());
const aio = r.ai_overview || {};
const text = (aio.text || "").toLowerCase();
const citations = aio.citations || [];
const brandCitations = {};
for (const brand of brands) {
const inText = text.includes(brand.toLowerCase());
const inLinks = citations.some(c => (c.domain || "").toLowerCase().includes(brand.toLowerCase()));
brandCitations[brand] = inText || inLinks;
}
return {keyword, hasAio: Boolean(r.ai_overview), brandCitations, citationCount: citations.length};
}
(async () => {
const brands = ["OurBrand", "CompetitorA", "CompetitorB"];
const keywords = ["best crm software 2026", "crm for startups", "crm comparison 2026"];
const report = {};
brands.forEach(b => report[b] = {cited: 0, total: 0});
for (const kw of keywords) {
const result = await checkAeoCitation(kw, brands);
if (result.hasAio) {
for (const [brand, cited] of Object.entries(result.brandCitations)) {
report[brand].total++;
if (cited) report[brand].cited++;
}
const cited = Object.entries(result.brandCitations).filter(([,v]) => v).map(([k]) => k).join(", ") || "none";
console.log(`${kw}: ${cited}`);
}
}
console.log("\n--- Share of Voice ---");
for (const [brand, data] of Object.entries(report)) {
const sov = data.total > 0 ? (data.cited / data.total * 100).toFixed(0) : 0;
console.log(` ${brand}: ${sov}% (${data.cited}/${data.total})`);
}
})();Platforms Used
Web search with knowledge graph, PAA, and AI overviews