Building an AI Citation Tracker in Python
The overlap between Google top-10 rankings and
AI Overview citations dropped from 76% to 38%
in one year (Ahrefs, 2026).
This means ranking well on Google no longer
guarantees appearing in AI answers above your
result. You need to track both signals separately.
This tutorial shows how to build a tracker that
checks all four major AI engines simultaneously.
Why four engines?
Each uses a completely different search index:
| Engine | Index used | Daily queries |
|---|---|---|
| ChatGPT | Bing | ~2.5B prompts |
| Claude | Brave Search | Enterprise-heavy |
| Gemini | ~2B+ | |
| Perplexity | Proprietary | 780M/month |
Empirical overlap between citations: below 20%.
Monitoring one engine captures roughly 1/5th
of the full AI recommendation landscape.
Setup
bash
pip install requests python-dotenv
python
# .env
SERPENT_API_KEY=your_key_here
The tracker
python
import os
import requests
from dataclasses import dataclass
from typing import Optional
from dotenv import load_dotenv
load_dotenv()
@dataclass
class Citation:
engine: str
url: str
position: int
cited_text: Optional[str]
@dataclass
class VisibilityReport:
keyword: str
citations: list[Citation]
scores: dict[str, int]
combined_score: int
class AICitationTracker:
BASE_URL = "https://apiserpent.com/api"
ENGINES = ["chatgpt", "claude", "gemini", "perplexity"]
def __init__(self):
self.api_key = os.getenv("SERPENT_API_KEY")
self.session = requests.Session()
self.session.headers.update({"X-API-Key": self.api_key})
def check_visibility(
self,
keyword: str,
engines: list = None
) -> VisibilityReport:
"""
Check brand citations across AI engines.
Returns structured visibility data.
"""
engines = engines or self.ENGINES
response = self.session.get(
f"{self.BASE_URL}/ai/rank",
params={
"keyword": keyword,
"engines": ",".join(engines)
},
timeout=30
)
response.raise_for_status()
data = response.json()
citations = []
scores = {}
for engine, result in data["results"].items():
scores[engine] = result.get("visibility_score", 0)
for cite in result.get("citations", []):
citations.append(Citation(
engine=engine,
url=cite["url"],
position=cite["position"],
cited_text=cite.get("cited_text")
))
return VisibilityReport(
keyword=keyword,
citations=citations,
scores=scores,
combined_score=data.get("combined_visibility_score", 0)
)
def track_keywords(self, keywords: list[str]) -> dict:
"""Track multiple keywords and return summary."""
results = {}
for kw in keywords:
report = self.check_visibility(kw)
results[kw] = {
"combined_score": report.combined_score,
"scores_by_engine": report.scores,
"total_citations": len(report.citations),
"engines_citing": list({c.engine for c in report.citations})
}
return results
# Usage
tracker = AICitationTracker()
# Single keyword check
report = tracker.check_visibility("best SERP API for developers")
print(f"Combined visibility score: {report.combined_score}/100\n")
for engine, score in report.scores.items():
print(f"{engine}: {score}/100")
engine_cites = [c for c in report.citations if c.engine == engine]
for cite in engine_cites:
print(f" → pos {cite.position}: {cite.url}")
# Multiple keywords
keywords = [
"best rank tracking API",
"google SERP API Python",
"cheapest search API"
]
summary = tracker.track_keywords(keywords)
for kw, data in summary.items():
print(f"\n{kw}")
print(f" Score: {data['combined_score']}/100")
print(f" Cited by: {', '.join(data['engines_citing']) or 'none'}")
Understanding the output
The visibility_score (0-100) per engine is a
composite of:
- Citation presence (yes/no)
- Position of citation vs competitors
- Frequency across similar queries
- Source authority of the cited URL
Track this weekly. A score dropping 10+ points
in a week means a competitor page overtook yours
in that engine's citations.
10 free searches to start
API key at apiserpent.com —
no card required. Pay-as-you-go from $1.22/1K
(Gemini) to $11.11/1K (Perplexity) at default tier.
Drop questions below — happy to discuss
the architecture.
Top comments (0)