Ranking drops can go unnoticed for days if you only check manually. By the time you spot a decline from position 3 to position 15, you have already lost significant traffic. This tutorial builds an automated SEO alert pipeline that checks your keyword rankings daily via the Scavio API, compares current positions against previous baselines stored in CSV, and fires an alert when any keyword drops below a configured threshold or falls more than a set number of positions. The pipeline costs about $0.005 per keyword per day, so monitoring 100 keywords runs under $0.50 daily.
Prerequisites
- Python 3.8+ installed
- requests library installed
- A Scavio API key from scavio.dev
- Your domain and a list of target keywords
Walkthrough
Step 1: Configure keywords and alert thresholds
Define keywords to track, your target domain, a position threshold to alert on, and a drop threshold to catch sudden declines.
import os, csv, requests
from datetime import date
API_KEY = os.environ['SCAVIO_API_KEY']
ENDPOINT = 'https://api.scavio.dev/api/v1/search'
HEADERS = {'x-api-key': API_KEY, 'Content-Type': 'application/json'}
TARGET_DOMAIN = 'mysite.com'
POSITION_THRESHOLD = 10
DROP_THRESHOLD = 5
KEYWORDS = [
'web scraping api',
'serp api python',
'amazon price api',
'tiktok data api',
]Step 2: Fetch current rank and load previous baseline
Query the Scavio Google endpoint for each keyword and find your domain's position. Load the last recorded position from a CSV history file for comparison.
def get_rank(keyword):
resp = requests.post(ENDPOINT, headers=HEADERS,
json={'query': keyword, 'country_code': 'us'})
for r in resp.json().get('organic_results', []):
if TARGET_DOMAIN in r.get('link', ''):
return r['position']
return None
def load_baselines(csv_path='rankings_history.csv'):
baselines = {}
try:
with open(csv_path) as f:
rows = list(csv.reader(f))
for row in reversed(rows):
if len(row) >= 3 and row[1] not in baselines and row[2]:
baselines[row[1]] = int(row[2])
except FileNotFoundError:
pass
return baselinesStep 3: Check for drops and generate alerts
Compare current positions to baselines. Alert if the position exceeds the threshold or if the drop since last check exceeds the drop threshold.
def run_alert_pipeline():
baselines = load_baselines()
alerts = []
today = date.today().isoformat()
with open('rankings_history.csv', 'a', newline='') as f:
writer = csv.writer(f)
for kw in KEYWORDS:
rank = get_rank(kw)
writer.writerow([today, kw, rank or ''])
prev = baselines.get(kw)
if rank is None:
alerts.append(f'[GONE] {kw}: not found in top results')
elif rank > POSITION_THRESHOLD:
alerts.append(f'[LOW] {kw}: position {rank} (threshold {POSITION_THRESHOLD})')
elif prev and rank - prev >= DROP_THRESHOLD:
alerts.append(f'[DROP] {kw}: {prev} -> {rank} (dropped {rank - prev} positions)')
else:
print(f'[OK] {kw}: position {rank}')
if alerts:
print('\n--- ALERTS ---')
for a in alerts:
print(a)
return alerts
run_alert_pipeline()Python Example
import os, csv, requests
from datetime import date
API_KEY = os.environ['SCAVIO_API_KEY']
ENDPOINT = 'https://api.scavio.dev/api/v1/search'
HEADERS = {'x-api-key': API_KEY, 'Content-Type': 'application/json'}
DOMAIN = 'mysite.com'
KEYWORDS = ['web scraping api', 'serp api python', 'amazon price api']
def get_rank(kw):
resp = requests.post(ENDPOINT, headers=HEADERS,
json={'query': kw, 'country_code': 'us'}).json()
for r in resp.get('organic_results', []):
if DOMAIN in r.get('link', ''): return r['position']
return None
def load_prev():
prev = {}
try:
with open('rankings.csv') as f:
for row in reversed(list(csv.reader(f))):
if row[1] not in prev and row[2]: prev[row[1]] = int(row[2])
except FileNotFoundError: pass
return prev
def run():
prev = load_prev()
today = date.today().isoformat()
with open('rankings.csv', 'a', newline='') as f:
w = csv.writer(f)
for kw in KEYWORDS:
rank = get_rank(kw)
w.writerow([today, kw, rank or ''])
p = prev.get(kw)
if rank and p and rank - p >= 5:
print(f'[DROP] {kw}: {p} -> {rank}')
elif rank and rank > 10:
print(f'[LOW] {kw}: position {rank}')
elif rank:
print(f'[OK] {kw}: #{rank}')
else:
print(f'[GONE] {kw}')
if __name__ == '__main__': run()JavaScript Example
const fs = require('fs');
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 = ['web scraping api', 'serp api python'];
async function getRank(kw) {
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));
return match ? match.position : null;
}
async function run() {
const today = new Date().toISOString().slice(0, 10);
for (const kw of KEYWORDS) {
const rank = await getRank(kw);
fs.appendFileSync('rankings.csv', `${today},${kw},${rank || ''}\n`);
if (!rank) console.log(`[GONE] ${kw}`);
else if (rank > 10) console.log(`[LOW] ${kw}: position ${rank}`);
else console.log(`[OK] ${kw}: #${rank}`);
}
}
run().catch(console.error);Expected Output
[OK] web scraping api: #3
[OK] serp api python: #5
[DROP] amazon price api: 4 -> 11
--- ALERTS ---
[DROP] amazon price api: 4 -> 11 (dropped 7 positions)