Overview
This workflow generates weekly SEO reports for all agency clients from raw SERP data. It tracks keyword rankings, monitors SERP feature changes, identifies competitor movements, and produces white-labeled reports in the agency's branding. Replaces per-client tool licenses with a flat API cost.
Trigger
Cron schedule (every Monday at 6:00 AM UTC)
Schedule
Runs every Monday at 6:00 AM UTC
Workflow Steps
Load client configurations
Read each client's domain, tracked keywords, and competitor domains from the agency's client database.
Track rankings for each client
Call Scavio for each client's keywords to get current positions, SERP features, and competitor presence.
Compare against previous week
Diff current rankings against stored baselines to identify improvements, drops, and feature changes.
Generate per-client reports
Build a structured report per client with ranking summary, notable changes, and competitor movements.
Deliver reports
Send reports via email, upload to client portal, or push to white-label dashboard.
Python Implementation
import requests
import json
from pathlib import Path
from datetime import datetime
API_KEY = "your_scavio_api_key"
CLIENTS = [
{"name": "Client A", "domain": "clienta.com", "keywords": ["seo agency austin", "local seo services texas", "small business seo"]},
{"name": "Client B", "domain": "clientb.com", "keywords": ["saas marketing strategy", "b2b content marketing", "demand generation"]},
]
def track_keyword(keyword: str, domain: str) -> dict:
res = requests.post(
"https://api.scavio.dev/api/v1/search",
headers={"x-api-key": API_KEY},
json={"platform": "google", "query": keyword, "num": 20},
timeout=15,
)
res.raise_for_status()
data = res.json()
position = None
for item in data.get("organic", []):
if domain in item.get("link", ""):
position = item.get("position")
break
return {
"keyword": keyword,
"position": position,
"has_ai_overview": bool(data.get("ai_overview")),
"top_competitors": [r.get("link", "").split("/")[2] if "/" in r.get("link", "") else "" for r in data.get("organic", [])[:5]],
}
def generate_client_report(client: dict) -> dict:
results = [track_keyword(kw, client["domain"]) for kw in client["keywords"]]
ranked = [r for r in results if r["position"]]
avg_pos = sum(r["position"] for r in ranked) / max(len(ranked), 1)
return {
"client": client["name"],
"domain": client["domain"],
"date": datetime.utcnow().strftime("%Y-%m-%d"),
"keywords_tracked": len(results),
"keywords_ranking": len(ranked),
"avg_position": round(avg_pos, 1),
"top_ranking": min((r["position"] for r in ranked), default=None),
"ai_overview_exposure": sum(1 for r in results if r["has_ai_overview"]),
"results": results,
}
def run():
date = datetime.utcnow().strftime("%Y-%m-%d")
total_credits = sum(len(c["keywords"]) for c in CLIENTS)
for client in CLIENTS:
report = generate_client_report(client)
Path(f"report_{client['name'].lower().replace(' ', '_')}_{date}.json").write_text(json.dumps(report, indent=2))
print(f"{report['client']}: {report['keywords_ranking']}/{report['keywords_tracked']} ranking, avg #{report['avg_position']}")
print(f"\nTotal credits used: {total_credits}")
if __name__ == "__main__":
run()JavaScript Implementation
const API_KEY = "your_scavio_api_key";
const CLIENTS = [
{ name: "Client A", domain: "clienta.com", keywords: ["seo agency austin", "local seo services texas"] },
{ name: "Client B", domain: "clientb.com", keywords: ["saas marketing strategy", "b2b content marketing"] },
];
async function trackKeyword(keyword, domain) {
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, num: 20 }),
});
if (!res.ok) throw new Error(`scavio ${res.status}`);
const data = await res.json();
let position = null;
for (const item of data.organic ?? []) {
if (item.link?.includes(domain)) { position = item.position; break; }
}
return { keyword, position, hasAiOverview: !!data.ai_overview };
}
for (const client of CLIENTS) {
const results = [];
for (const kw of client.keywords) results.push(await trackKeyword(kw, client.domain));
const ranked = results.filter((r) => r.position);
const avg = ranked.reduce((s, r) => s + r.position, 0) / Math.max(ranked.length, 1);
console.log(`${client.name}: ${ranked.length}/${results.length} ranking, avg #${Math.round(avg * 10) / 10}`);
}Platforms Used
Web search with knowledge graph, PAA, and AI overviews