Tutorial

How to Build a Custom SEO Dashboard with a Search API

Build a rank tracking dashboard replacing $500/mo enterprise tools. Python backend with daily SERP checks and SQLite history for under $30/mo.

Enterprise SEO tools charge $200-500/month for dashboards you cannot customize. A SERP API feeding your own dashboard gives you exactly the metrics you need at a fraction of the cost. This tutorial builds a Python rank tracking backend that checks keyword positions daily, stores history in SQLite, and exports CSV for any frontend. Tracking 200 keywords daily costs about $1/day on Scavio.

Prerequisites

  • Python 3.8+ installed
  • requests and sqlite3 (built-in)
  • A Scavio API key from scavio.dev
  • Keywords and your target domain

Walkthrough

Step 1: Set up the database and configuration

Create a SQLite database for daily rank snapshots. Define your target domain and keyword list.

Python
import os, sqlite3, requests, json
from datetime import date

API_KEY = os.environ['SCAVIO_API_KEY']
H = {'x-api-key': API_KEY, 'Content-Type': 'application/json'}
DOMAIN = 'mysite.com'
KEYWORDS = ['serp api python', 'web scraping api', 'tiktok data api', 'seo rank tracking api']

def init_db(path='seo_dashboard.db'):
    conn = sqlite3.connect(path)
    conn.execute('''CREATE TABLE IF NOT EXISTS rankings (
        id INTEGER PRIMARY KEY, date TEXT, keyword TEXT,
        position INTEGER, url TEXT, title TEXT)''')
    conn.commit()
    return conn

db = init_db()

Step 2: Fetch rankings for each keyword

Query Google for each keyword and find your domain's position in the organic results.

Python
def check_keyword(keyword):
    resp = requests.post('https://api.scavio.dev/api/v1/search',
        headers=H, json={'query': keyword, 'country_code': 'us'})
    for r in resp.json().get('organic_results', []):
        if DOMAIN in r.get('link', ''):
            return {'position': r['position'], 'url': r['link'], 'title': r.get('title', '')}
    return {'position': None, 'url': '', 'title': ''}

def run_daily_check():
    today = date.today().isoformat()
    for kw in KEYWORDS:
        r = check_keyword(kw)
        db.execute('INSERT INTO rankings (date,keyword,position,url,title) VALUES (?,?,?,?,?)',
            (today, kw, r['position'], r['url'], r['title']))
        status = f"#{r['position']}" if r['position'] else 'NOT FOUND'
        print(f'  {kw}: {status}')
    db.commit()
    print(f'Cost: ${len(KEYWORDS) * 0.005:.3f}')

run_daily_check()

Step 3: Query history and detect rank changes

Pull rank history from SQLite to identify trends, drops, and improvements.

Python
def get_movers(days=7):
    cursor = db.execute('''
        WITH recent AS (
            SELECT keyword, position, date,
                ROW_NUMBER() OVER (PARTITION BY keyword ORDER BY date DESC) as rn
            FROM rankings WHERE position IS NOT NULL
        )
        SELECT a.keyword, a.position as current_pos, b.position as prev_pos,
            b.position - a.position as change
        FROM recent a JOIN recent b ON a.keyword = b.keyword
        WHERE a.rn = 1 AND b.rn = ? AND a.position != b.position
        ORDER BY change DESC
    ''', (min(days, 30),))
    for kw, curr, prev, change in cursor.fetchall():
        d = 'UP' if change > 0 else 'DOWN'
        print(f'  [{d}] {kw}: #{prev} -> #{curr} ({change:+d})')

def export_csv(output='dashboard.csv'):
    import csv
    rows = db.execute('SELECT date, keyword, position, url FROM rankings ORDER BY date DESC, keyword').fetchall()
    with open(output, 'w', newline='') as f:
        w = csv.writer(f)
        w.writerow(['date', 'keyword', 'position', 'url'])
        w.writerows(rows)
    print(f'Exported {len(rows)} rows to {output}')

get_movers(7)
export_csv()

Step 4: Schedule daily checks

Add a cron job to run the checker daily with duplicate prevention.

Python
def already_checked_today():
    today = date.today().isoformat()
    return db.execute('SELECT COUNT(*) FROM rankings WHERE date = ?', (today,)).fetchone()[0] > 0

if __name__ == '__main__':
    db = init_db()
    if already_checked_today():
        print('Already checked today.')
    else:
        print(f'{date.today()}: Running daily check...')
        run_daily_check()
        get_movers(7)

# crontab -e
# 0 6 * * * cd /path/to && python seo_dashboard.py >> dashboard.log 2>&1

Python Example

Python
import os, sqlite3, requests
from datetime import date

API_KEY = os.environ['SCAVIO_API_KEY']
H = {'x-api-key': API_KEY, 'Content-Type': 'application/json'}
DOMAIN = 'mysite.com'
KEYWORDS = ['serp api python', 'web scraping api']

db = sqlite3.connect('seo.db')
db.execute('CREATE TABLE IF NOT EXISTS ranks (date TEXT, keyword TEXT, position INT)')

for kw in KEYWORDS:
    data = requests.post('https://api.scavio.dev/api/v1/search',
        headers=H, json={'query': kw, 'country_code': 'us'}).json()
    pos = next((r['position'] for r in data.get('organic_results', []) if DOMAIN in r.get('link', '')), None)
    db.execute('INSERT INTO ranks VALUES (?,?,?)', (date.today().isoformat(), kw, pos))
    print(f'{kw}: #{pos}' if pos else f'{kw}: not found')
db.commit()

JavaScript Example

JavaScript
const Database = require('better-sqlite3');
const API_KEY = process.env.SCAVIO_API_KEY;
const H = { 'x-api-key': API_KEY, 'Content-Type': 'application/json' };
const DOMAIN = 'mysite.com';
const KEYWORDS = ['serp api python', 'web scraping api'];

const db = new Database('seo.db');
db.exec('CREATE TABLE IF NOT EXISTS ranks (date TEXT, keyword TEXT, position INT)');

async function run() {
  const today = new Date().toISOString().slice(0, 10);
  const insert = db.prepare('INSERT INTO ranks VALUES (?, ?, ?)');
  for (const kw of KEYWORDS) {
    const data = await fetch('https://api.scavio.dev/api/v1/search', {
      method: 'POST', headers: H, body: JSON.stringify({ query: kw, country_code: 'us' })
    }).then(r => r.json());
    const match = (data.organic_results || []).find(r => r.link.includes(DOMAIN));
    insert.run(today, kw, match ? match.position : null);
    console.log(match ? `${kw}: #${match.position}` : `${kw}: not found`);
  }
}
run().catch(console.error);

Expected Output

JSON
2026-05-18: Running daily check...
  serp api python: #4
  web scraping api: #7
  tiktok data api: #2
  seo rank tracking api: #11
Cost: $0.020

  [UP] tiktok data api: #5 -> #2 (+3)
  [DOWN] seo rank tracking api: #8 -> #11 (-3)
Exported 28 rows to dashboard.csv

Related Tutorials

Frequently Asked Questions

Most developers complete this tutorial in 15 to 30 minutes. You will need a Scavio API key (free tier works) and a working Python or JavaScript environment.

Python 3.8+ installed. requests and sqlite3 (built-in). A Scavio API key from scavio.dev. Keywords and your target domain. A Scavio API key gives you 250 free credits per month.

Yes. The free tier includes 250 credits per month, which is more than enough to complete this tutorial and prototype a working solution.

Scavio has a native LangChain package (langchain-scavio), an MCP server, and a plain REST API that works with any HTTP client. This tutorial uses the raw REST API, but you can adapt to your framework of choice.

Start Building

Build a rank tracking dashboard replacing $500/mo enterprise tools. Python backend with daily SERP checks and SQLite history for under $30/mo.