An r/SideProject post lamented paying $29/month for Ahrefs Starter just to check a handful of keywords. Semrush starts at $139.95/month. If you only check 100-200 keywords, a pay-per-query approach with Scavio costs under $1/month. This tutorial builds a self-hosted rank checker.
Prerequisites
- Scavio API key
- Python 3.8+
- SQLite for history tracking
Walkthrough
Step 1: Define keywords and domains to track
List the keywords you want to track and your domain.
config = {
'domain': 'mysite.com',
'keywords': [
'best crm for startups',
'crm software comparison 2026',
'affordable crm tools',
'crm vs spreadsheet',
],
'country': 'us'
}Step 2: Check SERP position for each keyword
Search each keyword via Scavio and find your domain in results.
import requests, os
H = {'x-api-key': os.environ['SCAVIO_API_KEY']}
def check_rank(keyword, domain, country='us'):
data = requests.post('https://api.scavio.dev/api/v1/search',
headers=H,
json={'platform': 'google', 'query': keyword, 'country_code': country}).json()
for r in data.get('organic_results', []):
if domain in r.get('link', ''):
return r.get('position')
return None # Not in top resultsStep 3: Store rank history in SQLite
Track positions over time to see trends.
import sqlite3, datetime
conn = sqlite3.connect('ranks.db')
conn.execute('CREATE TABLE IF NOT EXISTS ranks (date TEXT, keyword TEXT, position INT, url TEXT)')
def save_rank(keyword, position, url=None):
conn.execute('INSERT INTO ranks VALUES (?, ?, ?, ?)',
(datetime.date.today().isoformat(), keyword, position, url))
conn.commit()Step 4: Generate a rank report
Weekly summary showing position changes.
def rank_report():
rows = conn.execute('''SELECT keyword,
(SELECT position FROM ranks r2 WHERE r2.keyword=r1.keyword ORDER BY date DESC LIMIT 1) as current,
(SELECT position FROM ranks r3 WHERE r3.keyword=r1.keyword ORDER BY date DESC LIMIT 1 OFFSET 7) as prev
FROM ranks r1 GROUP BY keyword''').fetchall()
for kw, current, prev in rows:
delta = (prev - current) if prev and current else 0
arrow = '+' if delta > 0 else '-' if delta < 0 else '='
print(f'{arrow} {kw}: #{current} (was #{prev})')Python Example
import os, requests, sqlite3, datetime
H = {'x-api-key': os.environ['SCAVIO_API_KEY']}
conn = sqlite3.connect('ranks.db')
conn.execute('CREATE TABLE IF NOT EXISTS ranks (date TEXT, keyword TEXT, position INT, url TEXT)')
def check_and_save(keyword, domain):
data = requests.post('https://api.scavio.dev/api/v1/search', headers=H,
json={'platform': 'google', 'query': keyword}).json()
for r in data.get('organic_results', []):
if domain in r.get('link', ''):
conn.execute('INSERT INTO ranks VALUES (?,?,?,?)',
(datetime.date.today().isoformat(), keyword, r['position'], r['link']))
conn.commit()
return r['position']
return NoneJavaScript Example
const res = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST',
headers: {'x-api-key': process.env.SCAVIO_API_KEY, 'Content-Type': 'application/json'},
body: JSON.stringify({platform: 'google', query: keyword})
});
const data = await res.json();
const rank = data.organic_results?.findIndex(r => r.link.includes(domain)) + 1 || null;Expected Output
Self-hosted SEO rank checker. 4 keywords checked daily = 4 queries = $0.02/day. Monthly cost: $0.60 vs $29+ for Ahrefs Starter or $139.95 for Semrush Pro.