Overview
This workflow generates a comprehensive weekly report combining traditional organic rankings with AI Mode citation tracking. It queries target keywords via Scavio with AI Overview extraction, calculates a GEO visibility score, compares week-over-week trends, and outputs a structured report suitable for stakeholder review. Designed for SEO teams and agencies reporting on AI search visibility post-Google I/O 2026.
Trigger
Cron schedule (every Monday at 8 AM UTC)
Schedule
Runs every Monday at 8:00 AM UTC
Workflow Steps
Load keyword set and previous week data
Read target keywords and load last week's report for comparison.
Query keywords with AI Overview
Search each keyword via Scavio with ai_overview enabled. Extract organic rankings and AI Overview text.
Calculate GEO visibility score
Compute citation frequency, position weighting, and composite GEO score.
Compare week-over-week
Calculate changes in organic positions, citation count, and GEO score vs previous week.
Generate report
Output structured JSON report with summary metrics, keyword details, and trend data.
Python Implementation
import requests
import json
from datetime import datetime
from pathlib import Path
API_KEY = "your_scavio_api_key"
DOMAIN = "yourdomain.com"
def generate_report(keywords: list[str]) -> dict:
results = []
for kw in keywords:
res = requests.post(
"https://api.scavio.dev/api/v1/search",
headers={"x-api-key": API_KEY},
json={"platform": "google", "query": kw, "ai_overview": True},
timeout=15,
)
res.raise_for_status()
data = res.json()
ai_text = data.get("ai_overview", {}).get("text", "")
organic_pos = None
for r in data.get("organic", []):
if DOMAIN in r.get("link", ""):
organic_pos = r.get("position")
break
results.append({
"keyword": kw,
"organic_position": organic_pos,
"ai_cited": DOMAIN in ai_text.lower(),
"has_ai_overview": len(ai_text) > 0,
})
cited = sum(1 for r in results if r["ai_cited"])
in_top10 = sum(1 for r in results if r["organic_position"] and r["organic_position"] <= 10)
geo_score = round(cited / len(results), 3) if results else 0
date = datetime.utcnow().strftime("%Y-%m-%d")
report = {
"date": date,
"total_keywords": len(keywords),
"organic_top10": in_top10,
"ai_citations": cited,
"geo_score": geo_score,
"details": results,
}
# Load previous for comparison
prev_path = Path("weekly_report_latest.json")
if prev_path.exists():
prev = json.loads(prev_path.read_text())
report["prev_geo_score"] = prev.get("geo_score", 0)
report["geo_score_change"] = round(geo_score - prev.get("geo_score", 0), 3)
Path(f"weekly_report_{date}.json").write_text(json.dumps(report, indent=2))
prev_path.write_text(json.dumps(report, indent=2))
return report
keywords = json.loads(Path("target_keywords.json").read_text()) if Path("target_keywords.json").exists() else ["best search api 2026", "serp api comparison"]
report = generate_report(keywords)
print(f"Weekly Report: {report['ai_citations']}/{report['total_keywords']} AI citations, GEO score: {report['geo_score']}")JavaScript Implementation
const API_KEY = "your_scavio_api_key";
const DOMAIN = "yourdomain.com";
async function generateReport(keywords) {
const results = [];
for (const kw of keywords) {
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: kw, ai_overview: true }),
});
const data = await res.json();
const aiText = data.ai_overview?.text ?? "";
const match = (data.organic ?? []).find((r) => (r.link ?? "").includes(DOMAIN));
results.push({ keyword: kw, organicPos: match?.position ?? null, aiCited: aiText.toLowerCase().includes(DOMAIN) });
}
const cited = results.filter((r) => r.aiCited).length;
console.log(`Weekly: ${cited}/${keywords.length} AI citations, GEO: ${(cited / keywords.length).toFixed(3)}`);
return results;
}
await generateReport(["best search api 2026", "serp api comparison"]);Platforms Used
Web search with knowledge graph, PAA, and AI overviews