Most rank trackers are built on top of fragile scrapers that break every time Google changes its HTML. By using the Scavio API as the data layer, you can build a rank tracker that returns structured position data without maintaining proxies or parsers. This tutorial builds a lightweight Flask API that accepts keyword and domain inputs, checks Google positions via Scavio, stores results in SQLite, and exposes historical ranking data through a REST endpoint.
Prerequisites
- Python 3.10 or higher
- pip install requests flask
- A Scavio API key
- Basic understanding of REST API design
Walkthrough
Step 1: Set up the SQLite database
Create a simple table to store keyword, domain, position, and timestamp for each check.
import sqlite3
def init_db(path: str = "rankings.db") -> None:
conn = sqlite3.connect(path)
conn.execute("""
CREATE TABLE IF NOT EXISTS rankings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
keyword TEXT NOT NULL,
domain TEXT NOT NULL,
position INTEGER,
checked_at TEXT DEFAULT (datetime('now'))
)
""")
conn.commit()
conn.close()Step 2: Check rank via Scavio
Query Google through the Scavio API and scan results for the target domain. Return the position or None.
import requests
def check_rank(keyword: str, domain: str) -> int | None:
r = requests.post(
"https://api.scavio.dev/api/v1/search",
headers={"x-api-key": API_KEY},
json={"query": keyword, "country_code": "us"}
)
r.raise_for_status()
for result in r.json().get("organic_results", []):
if domain in result.get("link", ""):
return result["position"]
return NoneStep 3: Store and retrieve rankings
Save each rank check to SQLite and provide a function to query historical data.
def save_rank(keyword: str, domain: str, position: int | None) -> None:
conn = sqlite3.connect("rankings.db")
conn.execute("INSERT INTO rankings (keyword, domain, position) VALUES (?, ?, ?)",
(keyword, domain, position))
conn.commit()
conn.close()
def get_history(keyword: str, domain: str, days: int = 30) -> list[dict]:
conn = sqlite3.connect("rankings.db")
rows = conn.execute(
"SELECT position, checked_at FROM rankings WHERE keyword=? AND domain=? ORDER BY checked_at DESC LIMIT ?",
(keyword, domain, days)
).fetchall()
conn.close()
return [{"position": r[0], "date": r[1]} for r in rows]Step 4: Expose as a Flask API
Create two endpoints: POST /check to trigger a rank check, and GET /history to retrieve stored positions.
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/check", methods=["POST"])
def check():
data = request.json
pos = check_rank(data["keyword"], data["domain"])
save_rank(data["keyword"], data["domain"], pos)
return jsonify({"keyword": data["keyword"], "domain": data["domain"], "position": pos})
@app.route("/history")
def history():
kw = request.args["keyword"]
domain = request.args["domain"]
return jsonify(get_history(kw, domain))Python Example
import os
import sqlite3
import requests
from flask import Flask, request, jsonify
API_KEY = os.environ.get("SCAVIO_API_KEY", "your_scavio_api_key")
ENDPOINT = "https://api.scavio.dev/api/v1/search"
DB = "rankings.db"
def init_db():
conn = sqlite3.connect(DB)
conn.execute("CREATE TABLE IF NOT EXISTS rankings (id INTEGER PRIMARY KEY AUTOINCREMENT, keyword TEXT, domain TEXT, position INTEGER, checked_at TEXT DEFAULT (datetime('now')))")
conn.commit()
conn.close()
def check_rank(keyword: str, domain: str) -> int | None:
r = requests.post(ENDPOINT, headers={"x-api-key": API_KEY},
json={"query": keyword, "country_code": "us"})
r.raise_for_status()
for res in r.json().get("organic_results", []):
if domain in res.get("link", ""):
return res["position"]
return None
app = Flask(__name__)
@app.route("/check", methods=["POST"])
def check():
d = request.json
pos = check_rank(d["keyword"], d["domain"])
conn = sqlite3.connect(DB)
conn.execute("INSERT INTO rankings (keyword, domain, position) VALUES (?, ?, ?)", (d["keyword"], d["domain"], pos))
conn.commit()
conn.close()
return jsonify({"keyword": d["keyword"], "position": pos})
if __name__ == "__main__":
init_db()
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";
async function checkRank(keyword, domain) {
const res = await fetch(ENDPOINT, {
method: "POST",
headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ query: keyword, country_code: "us" })
});
const data = await res.json();
const match = (data.organic_results || []).find(r => r.link.includes(domain));
return match ? match.position : null;
}
async function main() {
const keywords = ["python api tutorial", "rest api guide"];
const domain = "mysite.com";
for (const kw of keywords) {
const pos = await checkRank(kw, domain);
console.log(`${kw}: ${pos ? "#" + pos : "not ranked"}`);
}
}
main().catch(console.error);Expected Output
POST /check {"keyword": "python api tutorial", "domain": "mysite.com"}
=> {"keyword": "python api tutorial", "position": 6}
GET /history?keyword=python+api+tutorial&domain=mysite.com
=> [{"position": 6, "date": "2026-04-19"}, {"position": 7, "date": "2026-04-18"}]