seokeywordsgap-analysis

Keyword Gap Finder with SERP API 2026

Find keywords competitors rank for that you don't. SERP API pulls top results for target keywords, diff against your domain. Automated gap analysis.

8 min

A keyword gap finder compares your domain's rankings against a competitor's for a list of target keywords. For each keyword, it checks SERP results to see if your domain appears, if the competitor appears, and at what positions. Keywords where the competitor ranks but you do not are your gaps -- the opportunities you are missing. Total cost: $0.005 per keyword checked.

How It Works

  1. Define your domain and competitor domain
  2. Provide a list of target keywords
  3. For each keyword, query SERP API for top 20 results
  4. Check if your domain and competitor domain appear
  5. Classify: you rank and they do not (your win), they rank and you do not (your gap), both rank (competition), neither ranks (irrelevant)

The Core Script

Python
import requests, os, time

API_KEY = os.environ["SCAVIO_API_KEY"]

def check_serp(keyword: str, num_results: int = 20) -> list:
    """Fetch SERP results for a keyword."""
    resp = requests.post(
        "https://api.scavio.dev/api/v1/search",
        headers={"x-api-key": API_KEY},
        json={"query": keyword, "num_results": num_results},
        timeout=15,
    )
    return resp.json().get("results", [])

def find_domain_position(results: list, domain: str) -> int | None:
    """Find where a domain ranks in results. Returns 1-indexed position or None."""
    for i, r in enumerate(results):
        if domain in r.get("url", ""):
            return i + 1
    return None

def analyze_keyword(keyword: str, my_domain: str, competitor_domain: str) -> dict:
    results = check_serp(keyword)
    my_pos = find_domain_position(results, my_domain)
    comp_pos = find_domain_position(results, competitor_domain)
    if comp_pos and not my_pos:
        gap_type = "gap"
    elif my_pos and not comp_pos:
        gap_type = "your_win"
    elif my_pos and comp_pos:
        gap_type = "competition"
    else:
        gap_type = "neither"
    return {
        "keyword": keyword,
        "my_position": my_pos,
        "competitor_position": comp_pos,
        "gap_type": gap_type,
        "top_result": results[0]["url"] if results else None,
    }

Batch Analysis with Reporting

Python
import csv
from collections import Counter

def run_gap_analysis(
    keywords: list[str],
    my_domain: str,
    competitor_domain: str,
    output_file: str = "keyword_gaps.csv",
) -> dict:
    results = []
    for i, kw in enumerate(keywords):
        result = analyze_keyword(kw, my_domain, competitor_domain)
        results.append(result)
        if (i + 1) % 10 == 0:
            print(f"Processed {i + 1}/{len(keywords)} keywords")
        time.sleep(0.2)  # Gentle pacing

    # Write CSV report
    with open(output_file, "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=[
            "keyword", "my_position", "competitor_position", "gap_type", "top_result"])
        writer.writeheader()
        writer.writerows(results)

    # Summary stats
    types = Counter(r["gap_type"] for r in results)
    gaps = [r for r in results if r["gap_type"] == "gap"]
    gaps.sort(key=lambda r: r["competitor_position"] or 999)
    return {
        "total_keywords": len(keywords),
        "gaps": types.get("gap", 0),
        "your_wins": types.get("your_win", 0),
        "competition": types.get("competition", 0),
        "neither": types.get("neither", 0),
        "top_gaps": gaps[:10],
        "credits_used": len(keywords),
        "cost": f"$" + f"{len(keywords) * 0.005:.2f}",
    }

Example Usage

Python
keywords = [
    "best project management tool",
    "project management for startups",
    "free project management software",
    "agile project management tool",
    "project management vs spreadsheet",
    "remote team project management",
    "project management integrations",
    "kanban board software",
    "project tracking for small teams",
    "task management with time tracking",
]

report = run_gap_analysis(
    keywords=keywords,
    my_domain="myapp.com",
    competitor_domain="competitor.com",
)

print(f"Total keywords: {report['total_keywords']}")
print(f"Gaps (they rank, you don't): {report['gaps']}")
print(f"Your wins (you rank, they don't): {report['your_wins']}")
print(f"Competition (both rank): {report['competition']}")
print(f"API cost: {report['cost']}")
print("\nTop gaps to target:")
for g in report["top_gaps"]:
    print(f"  {g['keyword']} - competitor at #{g['competitor_position']}")

Cost Comparison

  • Ahrefs Content Gap tool: included in $129+/mo plan
  • Semrush Keyword Gap: included in $139.95+/mo plan
  • DIY with SERP API: $0.005/keyword. 100 keywords = $0.50. 1,000 keywords = $5.
  • If you only need gap analysis (not backlinks, site audits, etc.), the DIY approach saves $120+/mo

Prioritizing the Gaps

Python
def prioritize_gaps(gaps: list) -> list:
    """Score gaps by opportunity. Lower competitor position = easier to compete."""
    for g in gaps:
        pos = g["competitor_position"] or 20
        # Competitors ranking 5-15 are the sweet spot: not too hard, not too easy
        if 5 <= pos <= 15:
            g["priority"] = "high"
        elif pos <= 4:
            g["priority"] = "low"  # Competitor dominates, hard to displace
        else:
            g["priority"] = "medium"
    return sorted(gaps, key=lambda g: {"high": 0, "medium": 1, "low": 2}[g["priority"]])

What You Miss vs Ahrefs/Semrush

The DIY approach tells you who ranks where. It does not tell you search volume, keyword difficulty, or backlink requirements to compete. Ahrefs and Semrush pair gap analysis with volume data and difficulty scores, which helps prioritize beyond just position. If you already have volume data from Google Search Console or another source, you can join it with SERP position data to build a more complete picture without the enterprise tool cost.

Automating Weekly Reports

Run the gap analysis weekly on a cron schedule, compare this week's results to last week's, and flag changes: new gaps where the competitor just started ranking, closed gaps where you now rank, and position changes for competitive keywords. A 200-keyword weekly check costs $1/week = $4/mo. Store results in a SQLite database or CSV file and diff them programmatically.