DEV Community

Cover image for Rank Tracker API: How to Build a Multi-Engine Keyword Rank Checker in Python (2026 Guide)
API Serpent
API Serpent

Posted on

Rank Tracker API: How to Build a Multi-Engine Keyword Rank Checker in Python (2026 Guide)

Wants to build their own rank tracker rather than pay $99-500/month for a dashboard tool they can only partly customise?

This guide covers everything you need: what a rank tracker API actually returns, how to query it across multiple search engines, how to handle the data, and how to build a simple scheduled rank checker in under 100 lines of Python.

API used throughout: Serpent API

What a rank tracker API actually returns (and what yours should)

Not all rank tracker APIs return the same data. The minimum viable response for a rank checker contains: keyword, search engine, target domain, rank position (integer), URL of the ranked page, title and snippet.

A modern rank tracker API in 2026 should also return:

  • AI Overview presence flag: is there a generative AI block above organic results for this query?
  • AI Overview cited sources: does your domain appear inside the AI Overview?
  • Pixel Y coordinate: where on the rendered page does your result actually appear?
  • Position 1 can be below the fold if an AI Overview occupies 300px above it.

SERP features above your result: ads, Local Pack, Shopping — how many elements push your result down?
Device and location context: mobile vs desktop rankings differ; country-level targeting affects results

Why pixel position matters specifically in 2026: AI Overviews appear on ~55% of informational Google queries and occupy 200-400px before organic results begin. A rank tracker that only returns "position 1" without telling you whether position 1 is above or below the fold is giving you incomplete data.

The keyword cannibalization problem (and why two rank tracker APIs are better than one)

A critical finding from building rank tracking systems: if you query Google rank position and AI rank citation position separately, you get two different signals that together tell a much more complete story.
Signal 1 — Traditional rank position (this article): where does your domain appear in the organic list? This is what all rank trackers have measured since 2005.
Signal 2 — AI citation position (separate endpoint): does your brand appear in the AI-generated answer above the organic list? A page can rank position 1 organically and not be cited in the AI Overview. A page can rank position 15 and be cited in the AI Overview. These are independent signals.

Building the rank tracker: Python implementation

Here is the complete implementation for a multi-engine keyword rank checker. It queries Google, Bing, Yahoo, and DuckDuckGo simultaneously and returns a structured rank report.

1. Setup and authentication

pip install requests python-dotenv schedule pandas
# config.pyimport osfrom dotenv import load_dotenvload_dotenv()API_KEY = os.getenv("SERPENT_API_KEY")BASE_URL = "https://apiserpent.com/api"

Enter fullscreen mode Exit fullscreen mode

2. The core rank checker function

# rank_tracker.pyimport requestsfrom dataclasses import dataclassfrom typing import Optional@dataclassclass RankResult:    keyword: str    engine: str    domain: str    rank: Optional[int]        # None = not in top 100    url: Optional[str]    pixel_y: Optional[int]     # vertical position on page    above_fold: Optional[bool] # True = visible without scroll    in_ai_overview: bool       # cited in AI Overview    ai_overview_present: bool  # AIO exists for this queryclass RankChecker:    ENGINES = ["google", "bing", "yahoo", "duckduckgo"]    FOLD_THRESHOLD_PX = 600  # typical laptop fold line    def __init__(self, api_key: str):        self.session = requests.Session()        self.session.headers["X-API-Key"] = api_key    def check_rank(        self,        keyword: str,        domain: str,        engine: str = "google",        country: str = "us",        device: str = "desktop"    ) -> RankResult:        """Check rank for a single keyword on a single engine."""        resp = self.session.get(            f"https://apiserpent.com/api/search",            params={                "q": keyword,                "engine": engine,                "gl": country,                "device": device,                "num": 100  # check top 100 results            },            timeout=20        )        resp.raise_for_status()        data = resp.json()        # Find domain in organic results        rank = None        ranked_url = None        pixel_y = None        above_fold = None        for result in data.get("organic_results", []):            if domain.lower() in result.get("link", "").lower():                rank = result["position"]                ranked_url = result["link"]                pos = result.get("pixel_position", {})                pixel_y = pos.get("y")                if pixel_y is not None:                    above_fold = pixel_y < self.FOLD_THRESHOLD_PX                break        # Check AI Overview        aio = data.get("ai_overview", {})        ai_overview_present = bool(aio)        in_ai_overview = any(            domain.lower() in source.get("link", "").lower()            for source in aio.get("sources", [])        )        return RankResult(            keyword=keyword,            engine=engine,            domain=domain,            rank=rank,            url=ranked_url,            pixel_y=pixel_y,            above_fold=above_fold,            in_ai_overview=in_ai_overview,            ai_overview_present=ai_overview_present    
Enter fullscreen mode Exit fullscreen mode

3. Multi-engine rank report

    def check_all_engines(        self,        keyword: str,        domain: str,        engines: list = None    ) -> list[RankResult]:        """Check rank across multiple engines simultaneously."""        import concurrent.futures        engines = engines or self.ENGINES        results = []        with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:            futures = {                executor.submit(                    self.check_rank, keyword, domain, engine                ): engine                for engine in engines            }            for future in concurrent.futures.as_completed(futures):                try:                    results.append(future.result())                except Exception as e:                    print(f"Error on {futures[future]}: {e}")        return results    def bulk_check(        self,        keywords: list[str],        domain: str,        engines: list = None    ) -> dict:        """        Check multiple keywords across engines.        Returns dict keyed by keyword.        """        return {            kw: self.check_all_engines(kw, domain, engines)            for kw in keywords        }

Enter fullscreen mode Exit fullscreen mode

4. Reporting and output

import pandas as pdfrom datetime import datetimedef results_to_dataframe(    results: dict,    domain: str) -> pd.DataFrame:    """Convert rank results to a pandas DataFrame."""    rows = []    for keyword, engine_results in results.items():        for r in engine_results:            rows.append({                "date": datetime.now().date(),                "keyword": r.keyword,                "engine": r.engine,                "domain": domain,                "rank": r.rank or "Not in top 100",                "url": r.url or "",                "pixel_y": r.pixel_y,                "above_fold": r.above_fold,                "in_ai_overview": r.in_ai_overview,                "ai_overview_present": r.ai_overview_present            })    return pd.DataFrame(rows)# Usage examplefrom config import API_KEYchecker = RankChecker(API_KEY)keywords = [    "rank tracker api",    "keyword ranking api",    "google rank checker api",    "serp tracker google yahoo bing"]print(f"Checking {len(keywords)} keywords across 4 engines...")results = checker.bulk_check(keywords, "apiserpent.com")df = results_to_dataframe(results, "apiserpent.com")df.to_csv(f"rank_report_{datetime.now().date()}.csv", index=False)# Summaryfor kw, engine_results in results.items():    print(f"\n{kw}:")    for r in engine_results:        status = f"rank {r.rank}" if r.rank else "not found"        fold = "above fold" if r.above_fold else "below fold" if r.above_fold is False else "n/a"        aio = "in AIO" if r.in_ai_overview else ("AIO present" if r.ai_overview_present else "no AIO")        print(f"  {r.engine:12} → {status:20} {fold:12} {aio}")

Enter fullscreen mode Exit fullscreen mode

5. Scheduled daily tracking

import scheduleimport timedef daily_rank_check():    """Run daily at 6am and save results."""    checker = RankChecker(API_KEY)    results = checker.bulk_check(KEYWORDS, TARGET_DOMAIN)    df = results_to_dataframe(results, TARGET_DOMAIN)    # Append to master CSV    master_path = "rank_history.csv"    try:        existing = pd.read_csv(master_path)        pd.concat([existing, df]).to_csv(master_path, index=False)    except FileNotFoundError:        df.to_csv(master_path, index=False)    print(f"[{datetime.now()}] Daily rank check complete. {len(df)} rows saved.")schedule.every().day.at("06:00").do(daily_rank_check)while True:    schedule.run_pending()    time.sleep(60)

Enter fullscreen mode Exit fullscreen mode

Cost calculation for your rank tracker

Before building, estimate your monthly API cost. The formula:

# Cost calculatorkeywords = 500       # keywords you trackengines = 4          # Google, Bing, Yahoo, DuckDuckGochecks_per_day = 1   # daily trackingdays = 30monthly_calls = keywords * engines * checks_per_day * days# = 500 × 4 × 1 × 30 = 60,000 calls# Serpent API Scale tier: $0.03 per 10,000 pagescost_per_call = 0.03 / 10000 * 10  # $0.03 per 10K, so per 1K = $0.003monthly_cost = monthly_calls * cost_per_call / 1000print(f"Monthly calls: {monthly_calls:,}")print(f"Monthly cost: ${monthly_cost:.2f}")# → Monthly calls: 60,000# → Monthly cost: $0.18

Enter fullscreen mode Exit fullscreen mode

Decision matrix: when to use a rank tracker API vs a rank tracking tool

Factor Use an API Use a dashboard tool
Custom dashboard Yes ✓ — build your own No — use theirs
Multiple clients Yes ✓ — white label possible Expensive at scale
Technical setup Required — code needed No code needed
Data ownership Yes ✓ — export freely Locked in platform
Cost at 500 keywords Serpent: ~$0.18/mo Tools: $99-500/mo
AI Overview tracking Yes ✓ — returned in base response Rare, often add-on
Pixel positions Yes ✓ — unique to Serpent Not available anywhere

Top comments (0)