Tutorial

How to Build an SEO Ranking Alert Pipeline

Build a pipeline that checks keyword rankings daily and sends alerts when positions drop below a threshold. Python script with CSV history and email alerts.

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.

Python
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.

Python
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 baselines

Step 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.

Python
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

Python
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

JavaScript
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

JSON
[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)

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 library installed. A Scavio API key from scavio.dev. Your domain and a list of target keywords. 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 pipeline that checks keyword rankings daily and sends alerts when positions drop below a threshold. Python script with CSV history and email alerts.