An SEO audit dashboard consolidates ranking data, SERP feature presence, and competitor positions into a single view that updates on demand. Commercial tools like Ahrefs or SEMrush charge hundreds per month for this capability. This tutorial builds a lightweight Flask dashboard that queries the Scavio API for each keyword, computes metrics, and renders them as a JSON API that can feed any frontend. The dashboard tracks your rank, competitor ranks, featured snippet ownership, AI overview presence, and People Also Ask coverage.
Prerequisites
- Python 3.10 or higher
- pip install requests flask
- A Scavio API key
- A domain and keyword list to audit
Walkthrough
Step 1: Define the audit configuration
Set up the target domain, competitor domains, and keywords to track. This configuration drives the entire dashboard.
CONFIG = {
"domain": "mysite.com",
"competitors": ["competitor-a.com", "competitor-b.io"],
"keywords": [
"api documentation best practices",
"rest api design guide",
"graphql vs rest 2026",
]
}Step 2: Audit a single keyword
Fetch SERP data for a keyword and extract all relevant metrics: rank, competitor ranks, SERP features, and AI overview status.
def audit_keyword(keyword: str, config: dict) -> dict:
r = requests.post(ENDPOINT, headers={"x-api-key": API_KEY},
json={"query": keyword, "country_code": "us", "ai_overview": True})
r.raise_for_status()
data = r.json()
organic = data.get("organic_results", [])
my_rank = next((r["position"] for r in organic if config["domain"] in r.get("link", "")), None)
comp_ranks = {}
for c in config["competitors"]:
comp_ranks[c] = next((r["position"] for r in organic if c in r.get("link", "")), None)
return {
"keyword": keyword,
"my_rank": my_rank,
"competitor_ranks": comp_ranks,
"has_featured_snippet": bool(data.get("featured_snippet")),
"has_ai_overview": bool(data.get("ai_overview")),
"paa_count": len(data.get("people_also_ask", [])),
"total_results": len(organic),
}Step 3: Build the dashboard API
Create a Flask endpoint that runs the full audit and returns results as JSON.
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/audit")
def audit():
results = [audit_keyword(kw, CONFIG) for kw in CONFIG["keywords"]]
summary = {
"domain": CONFIG["domain"],
"keywords_tracked": len(results),
"keywords_ranked": sum(1 for r in results if r["my_rank"]),
"avg_position": round(sum(r["my_rank"] for r in results if r["my_rank"]) / max(sum(1 for r in results if r["my_rank"]), 1), 1),
"ai_overview_pct": f"{sum(1 for r in results if r['has_ai_overview']) / len(results) * 100:.0f}%",
}
return jsonify({"summary": summary, "keywords": results})Step 4: Run the dashboard
Start the Flask server and access the audit at http://localhost:5000/audit.
if __name__ == "__main__":
app.run(port=5000, debug=True)Python Example
import os
import requests
from flask import Flask, jsonify
API_KEY = os.environ.get("SCAVIO_API_KEY", "your_scavio_api_key")
ENDPOINT = "https://api.scavio.dev/api/v1/search"
CONFIG = {"domain": "mysite.com", "competitors": ["comp-a.com"], "keywords": ["api design guide", "rest vs graphql"]}
def audit(kw: str) -> dict:
r = requests.post(ENDPOINT, headers={"x-api-key": API_KEY},
json={"query": kw, "country_code": "us", "ai_overview": True})
r.raise_for_status()
d = r.json()
organic = d.get("organic_results", [])
rank = next((r["position"] for r in organic if CONFIG["domain"] in r.get("link", "")), None)
return {"keyword": kw, "rank": rank, "ai_overview": bool(d.get("ai_overview")), "paa": len(d.get("people_also_ask", []))}
app = Flask(__name__)
@app.route("/audit")
def run_audit():
results = [audit(kw) for kw in CONFIG["keywords"]]
return jsonify(results)
if __name__ == "__main__":
app.run(port=5000)JavaScript Example
const API_KEY = process.env.SCAVIO_API_KEY || "your_scavio_api_key";
const ENDPOINT = "https://api.scavio.dev/api/v1/search";
const http = require("http");
const CONFIG = { domain: "mysite.com", keywords: ["api design guide", "rest vs graphql"] };
async function audit(kw) {
const res = await fetch(ENDPOINT, {
method: "POST",
headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ query: kw, country_code: "us", ai_overview: true })
});
const d = await res.json();
const organic = d.organic_results || [];
const rank = organic.find(r => r.link.includes(CONFIG.domain))?.position || null;
return { keyword: kw, rank, aiOverview: !!d.ai_overview, paa: (d.people_also_ask || []).length };
}
http.createServer(async (req, res) => {
if (req.url === "/audit") {
const results = await Promise.all(CONFIG.keywords.map(audit));
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(results, null, 2));
}
}).listen(5000);Expected Output
GET /audit
{
"summary": {
"domain": "mysite.com",
"keywords_tracked": 3,
"keywords_ranked": 2,
"avg_position": 5.5,
"ai_overview_pct": "67%"
},
"keywords": [
{"keyword": "api design guide", "my_rank": 4, "has_ai_overview": true, "paa_count": 4},
{"keyword": "rest vs graphql", "my_rank": 7, "has_ai_overview": true, "paa_count": 3},
{"keyword": "graphql vs rest 2026", "my_rank": null, "has_ai_overview": false, "paa_count": 2}
]
}