ScavioScavio
ProductPricingDocs
Sign InGet Started
  1. Home
  2. Workflows
  3. SEO Keyword Gap Detection Workflow
Workflow

SEO Keyword Gap Detection Workflow

Compare SERP positions for your domain vs competitors weekly. Flag keywords where competitors rank in top 10 but your domain does not appear.

Start FreeAPI Docs

Overview

Runs weekly SERP checks for a target keyword list, records which domains appear in top-10 results for each keyword, and identifies gaps where competitor domains rank but the monitored domain does not.

Trigger

Weekly cron (Sundays at 9 PM)

Schedule

Weekly, Sundays at 9 PM (cron: 0 21 * * 0)

Workflow Steps

1

Load keywords and competitor domains

Read keyword list and list of competitor domains from config. These are the queries to check weekly.

2

Fetch SERP results for each keyword

POST to Scavio search API for each keyword. Extract top-10 result URLs and their positions.

3

Record domain presence

For each keyword result, check which tracked domains (your domain + competitors) appear in positions 1-10. Store presence data with position.

4

Identify gap keywords

A gap keyword is one where at least one competitor domain appears in positions 1-10 AND your domain does not appear in top-10 at all.

5

Score and prioritize gaps

Score each gap by competitor ranking position (competitor at #1 = higher priority gap than competitor at #9) and by number of competitors ranking for the keyword.

6

Output gap report

Generate CSV report: keyword, your_position (or 'not ranking'), competitors_ranking (list with positions), gap_score. Sort by gap_score descending.

Python Implementation

Python
import sqlite3
import requests
import csv
from datetime import date
import time

DB_PATH = "keyword_gaps.db"
SCRAVIO_KEY = "YOUR_API_KEY"
MY_DOMAIN = "scavio.com"
COMPETITOR_DOMAINS = ["serpapi.com", "serper.dev", "brightdata.com"]
KEYWORDS = [
    "search api for ai agents",
    "tiktok data api",
    "amazon product search api",
    "serp api python",
]

def init_db():
    conn = sqlite3.connect(DB_PATH)
    conn.executescript("""
        CREATE TABLE IF NOT EXISTS serp_snapshots (
            keyword TEXT, domain TEXT, position INTEGER, date TEXT
        );
    """)
    conn.commit()
    return conn

def fetch_serp(keyword: str) -> list:
    resp = requests.post(
        "https://api.scavio.dev/api/v1/search",
        headers={"x-api-key": SCRAVIO_KEY},
        json={"query": keyword, "platform": "google", "num": 10}
    )
    resp.raise_for_status()
    return resp.json().get("results", [])

def run():
    conn = init_db()
    today = date.today().isoformat()
    gaps = []
    for kw in KEYWORDS:
        results = fetch_serp(kw)
        domain_positions = {}
        for i, r in enumerate(results[:10], start=1):
            url = r.get("url", "")
            for domain in [MY_DOMAIN] + COMPETITOR_DOMAINS:
                if domain in url:
                    if domain not in domain_positions:
                        domain_positions[domain] = i
            conn.execute("INSERT INTO serp_snapshots VALUES (?,?,?,?)",
                         (kw, r.get("url"), i, today))
        my_pos = domain_positions.get(MY_DOMAIN)
        competing = {d: p for d, p in domain_positions.items() if d != MY_DOMAIN}
        if competing and not my_pos:
            gap_score = sum(11 - p for p in competing.values())
            gaps.append({
                "keyword": kw,
                "your_position": "not ranking",
                "competitors": "; ".join(f"{d}@{p}" for d, p in competing.items()),
                "gap_score": gap_score
            })
        conn.commit()
        time.sleep(0.3)
    gaps.sort(key=lambda x: x["gap_score"], reverse=True)
    with open(f"keyword_gaps_{today}.csv", "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=["keyword", "your_position", "competitors", "gap_score"])
        writer.writeheader()
        writer.writerows(gaps)
    print(f"{len(gaps)} gap keywords found. Report: keyword_gaps_{today}.csv")

if __name__ == "__main__":
    run()

JavaScript Implementation

JavaScript
const Database = require('better-sqlite3');
const fetch = require('node-fetch');
const fs = require('fs');
const { stringify } = require('csv-stringify/sync');

const DB_PATH = 'keyword_gaps.db';
const SCRAVIO_KEY = 'YOUR_API_KEY';
const MY_DOMAIN = 'scavio.com';
const COMPETITOR_DOMAINS = ['serpapi.com', 'serper.dev', 'brightdata.com'];
const KEYWORDS = ['search api for ai agents', 'tiktok data api', 'amazon product search api'];

const db = new Database(DB_PATH);
db.exec('CREATE TABLE IF NOT EXISTS serp_snapshots (keyword TEXT, domain TEXT, position INTEGER, date TEXT);');

async function fetchSerp(keyword) {
  const res = await fetch('https://api.scavio.dev/api/v1/search', {
    method: 'POST',
    headers: { 'x-api-key': SCRAVIO_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({ query: keyword, platform: 'google', num: 10 })
  });
  return (await res.json()).results || [];
}

async function run() {
  const today = new Date().toISOString().slice(0, 10);
  const gaps = [];
  for (const kw of KEYWORDS) {
    const results = await fetchSerp(kw);
    const domainPos = {};
    results.slice(0, 10).forEach((r, i) => {
      const url = r.url || '';
      for (const d of [MY_DOMAIN, ...COMPETITOR_DOMAINS]) {
        if (url.includes(d) && !domainPos[d]) domainPos[d] = i + 1;
      }
    });
    const myPos = domainPos[MY_DOMAIN];
    const competing = Object.fromEntries(Object.entries(domainPos).filter(([d]) => d !== MY_DOMAIN));
    if (Object.keys(competing).length && !myPos) {
      const gapScore = Object.values(competing).reduce((s, p) => s + (11 - p), 0);
      gaps.push({ keyword: kw, your_position: 'not ranking', competitors: Object.entries(competing).map(([d,p]) => `${d}@${p}`).join('; '), gap_score: gapScore });
    }
    await new Promise(r => setTimeout(r, 300));
  }
  gaps.sort((a, b) => b.gap_score - a.gap_score);
  fs.writeFileSync(`keyword_gaps_${today}.csv`, stringify(gaps, { header: true }));
  console.log(`${gaps.length} gaps found.`);
}

run().catch(console.error);

Platforms Used

Google

Web search with knowledge graph, PAA, and AI overviews

Frequently Asked Questions

Runs weekly SERP checks for a target keyword list, records which domains appear in top-10 results for each keyword, and identifies gaps where competitor domains rank but the monitored domain does not.

This workflow uses a weekly cron (sundays at 9 pm). Weekly, Sundays at 9 PM (cron: 0 21 * * 0).

This workflow uses the following Scavio platforms: google. Each platform is called via the same unified API endpoint.

Yes. Scavio's free tier includes 50 credits on signup with no credit card required. That is enough to test and validate this workflow before scaling it.

SEO Keyword Gap Detection Workflow

Compare SERP positions for your domain vs competitors weekly. Flag keywords where competitors rank in top 10 but your domain does not appear.

Get Your API KeyRead the Docs
ScavioScavio

Real-time search API for AI agents. Search every platform, not just Google.

Product

  • Features
  • Pricing
  • Dashboard
  • Affiliates

Developers

  • Documentation
  • API Reference
  • Quickstart
  • MCP Integration
  • Python SDK

Alternatives

  • Tavily Alternative
  • SerpAPI Alternative
  • Firecrawl Alternative
  • Exa Alternative

Tools

  • JSON Formatter
  • cURL to Code
  • Token Counter
  • All Tools

© 2026 Scavio. All rights reserved.

Featured on TAAFT
Terms of ServicePrivacy Policy