Overview
This workflow tracks AI Overview presence weekly for a list of target keywords. It checks whether AI Overviews exist for each keyword, whether your brand is mentioned, which competitors appear, and how the content has changed since last week. The output is a structured report showing AIO visibility trends over time.
Trigger
Cron schedule (every Monday at 9 AM UTC)
Schedule
Runs every Monday at 9 AM UTC
Workflow Steps
Load target keywords
Read the keyword list and brand names to track from configuration.
Query Google with AI Overview enabled
Call Scavio with ai_overview: true for each keyword to capture the AI Overview content.
Parse AI Overview content
Extract text, cited sources, and brand mentions from the AI Overview response field.
Compare against last week
Diff current AI Overview presence and content against stored history.
Detect changes
Flag keywords where brand appeared, disappeared, or where AIO content changed significantly.
Generate weekly AIO report
Compile all findings into a structured report with visibility score and change log.
Python Implementation
import requests
import json
from pathlib import Path
from datetime import datetime
API_KEY = "your_scavio_api_key"
BRAND = "yourbrand"
COMPETITORS = ["competitor1", "competitor2"]
def check_aio(keyword: str) -> dict:
res = requests.post(
"https://api.scavio.dev/api/v1/search",
headers={"x-api-key": API_KEY},
json={"platform": "google", "query": keyword, "ai_overview": True},
timeout=15,
)
res.raise_for_status()
data = res.json()
aio = data.get("ai_overview")
result = {
"keyword": keyword,
"has_aio": bool(aio),
"brand_mentioned": False,
"competitors_mentioned": [],
"aio_text": "",
}
if aio:
text = aio.get("text", "")
result["aio_text"] = text[:500]
result["brand_mentioned"] = BRAND.lower() in text.lower()
result["competitors_mentioned"] = [c for c in COMPETITORS if c.lower() in text.lower()]
return result
def run():
keywords = json.loads(Path("aio_keywords.json").read_text())
history_path = Path("aio_history.json")
history = json.loads(history_path.read_text()) if history_path.exists() else {}
current = {}
changes = []
for kw in keywords:
result = check_aio(kw)
current[kw] = result
prev = history.get(kw, {})
if prev:
if prev.get("brand_mentioned") and not result["brand_mentioned"]:
changes.append({"keyword": kw, "type": "brand_disappeared"})
elif not prev.get("brand_mentioned") and result["brand_mentioned"]:
changes.append({"keyword": kw, "type": "brand_appeared"})
if not prev.get("has_aio") and result["has_aio"]:
changes.append({"keyword": kw, "type": "aio_added"})
elif prev.get("has_aio") and not result["has_aio"]:
changes.append({"keyword": kw, "type": "aio_removed"})
history_path.write_text(json.dumps(current, indent=2))
# Calculate visibility score
total_with_aio = sum(1 for r in current.values() if r["has_aio"])
brand_visible = sum(1 for r in current.values() if r["brand_mentioned"])
visibility_score = round((brand_visible / max(total_with_aio, 1)) * 100)
report = {
"date": datetime.utcnow().strftime("%Y-%m-%d"),
"keywords_tracked": len(keywords),
"keywords_with_aio": total_with_aio,
"brand_visible_in": brand_visible,
"visibility_score": visibility_score,
"changes": changes,
}
Path(f"aio_report_{report['date']}.json").write_text(json.dumps(report, indent=2))
print(f"AIO Visibility: {visibility_score}% ({brand_visible}/{total_with_aio} AIOs mention brand)")
print(f"Changes this week: {len(changes)}")
for c in changes:
print(f" {c['keyword']}: {c['type']}")
if __name__ == "__main__":
run()JavaScript Implementation
const API_KEY = "your_scavio_api_key";
const BRAND = "yourbrand";
const COMPETITORS = ["competitor1", "competitor2"];
async function checkAio(keyword) {
const res = await fetch("https://api.scavio.dev/api/v1/search", {
method: "POST",
headers: { "x-api-key": API_KEY, "content-type": "application/json" },
body: JSON.stringify({ platform: "google", query: keyword, ai_overview: true }),
});
if (!res.ok) throw new Error(`scavio ${res.status}`);
const data = await res.json();
const aio = data.ai_overview;
const result = { keyword, hasAio: !!aio, brandMentioned: false, competitorsMentioned: [], aioText: "" };
if (aio) {
const text = aio.text ?? "";
result.aioText = text.slice(0, 500);
result.brandMentioned = text.toLowerCase().includes(BRAND.toLowerCase());
result.competitorsMentioned = COMPETITORS.filter((c) => text.toLowerCase().includes(c.toLowerCase()));
}
return result;
}
async function run() {
const fs = await import("fs/promises");
const keywords = JSON.parse(await fs.readFile("aio_keywords.json", "utf8"));
let history = {};
try { history = JSON.parse(await fs.readFile("aio_history.json", "utf8")); } catch {}
const current = {};
const changes = [];
for (const kw of keywords) {
const result = await checkAio(kw);
current[kw] = result;
const prev = history[kw] ?? {};
if (prev.brandMentioned && !result.brandMentioned) changes.push({ keyword: kw, type: "brand_disappeared" });
else if (!prev.brandMentioned && result.brandMentioned) changes.push({ keyword: kw, type: "brand_appeared" });
if (!prev.hasAio && result.hasAio) changes.push({ keyword: kw, type: "aio_added" });
else if (prev.hasAio && !result.hasAio) changes.push({ keyword: kw, type: "aio_removed" });
}
await fs.writeFile("aio_history.json", JSON.stringify(current, null, 2));
const totalWithAio = Object.values(current).filter((r) => r.hasAio).length;
const brandVisible = Object.values(current).filter((r) => r.brandMentioned).length;
const visibilityScore = Math.round((brandVisible / Math.max(totalWithAio, 1)) * 100);
const date = new Date().toISOString().slice(0, 10);
const report = { date, keywordsTracked: keywords.length, keywordsWithAio: totalWithAio, brandVisibleIn: brandVisible, visibilityScore, changes };
await fs.writeFile(`aio_report_${date}.json`, JSON.stringify(report, null, 2));
console.log(`AIO Visibility: ${visibilityScore}% (${brandVisible}/${totalWithAio} AIOs mention brand)`);
console.log(`Changes: ${changes.length}`);
for (const c of changes) console.log(` ${c.keyword}: ${c.type}`);
}
run();Platforms Used
Web search with knowledge graph, PAA, and AI overviews