Build a GEO Citation Tracker: Monitor Whether ChatGPT, Perplexity, and Google AI Overviews Cite Your Site (Python + n8n)
Your rank tracker tells you where you sit on the blue links. It says nothing about whether ChatGPT recommended you to a buyer this morning, whether Perplexity cited your pricing page in its answer, or whether Google’s AI Overview pulled your definition to the top of the SERP and quietly removed the click. For a growing share of high-intent queries, the answer is now generated, not ranked — and if your monitoring stack only watches positions, you are flying blind on the surface that increasingly decides whether anyone hears your brand at all.
This is the gap that Generative Engine Optimization (GEO) measurement is meant to close. In this post we’ll build a practical GEO citation tracker: a Python pipeline that fires a fixed panel of queries at the major answer engines, parses out which domains they cite, and logs whether your domain showed up — so you can chart AI visibility over time the same way you chart keyword rankings. It’s depth-first and code-first, the way we like it. No vanity dashboards, just a repeatable measurement loop you can wire into n8n and run on a schedule.
Why “are we cited?” is the only AI-search KPI that matters yet
Traditional SEO metrics assume a list of ranked results and a user who clicks one. Answer engines break both assumptions. ChatGPT and Perplexity synthesize a paragraph and footnote a handful of sources; Google’s AI Overview composes an answer and links a small set of supporting pages above the organic results. In all three, the unit of visibility is no longer “position 3” — it’s whether your domain appears in the citation set at all, and secondarily, in what order and with what surrounding sentiment.
That makes the first GEO metric refreshingly binary. For a given query on a given engine, you are either cited or you are not. Track that boolean across a panel of queries over time and you get a share-of-citation curve — the AI-era analogue of share-of-voice. It’s noisy (answer engines are non-deterministic), but with a fixed query panel and enough sampling it becomes a real, trendable signal. Everything fancier — citation rank, sentiment, which competitor displaced you — is a refinement on top of that boolean.
A word of honesty before we write code: these engines change their interfaces, rate-limit aggressively, and don’t all offer clean public APIs. Treat what follows as a measurement scaffold you adapt as the surfaces shift, not a fire-and-forget script. That caveat is the whole reason to own the pipeline rather than buy a black-box “AI visibility score” you can’t audit.
Step 1: Define a query panel that reflects real intent
Your tracker is only as good as the queries you feed it. Don’t dump your whole keyword export in. Pick 30–100 questions that (a) a real prospect would ask an assistant in natural language, and (b) you have a legitimate claim to answer. Phrase them conversationally — “what’s the best way to monitor backlinks automatically?” beats “backlink monitoring tool” — because that’s how people actually prompt.
QUERY_PANEL = [
"best way to automate backlink monitoring",
"how to track keyword rankings with the GSC API",
"n8n vs zapier for SEO workflows",
"how to generate schema markup at scale with python",
# ...30-100 conversational, intent-rich queries
]
# Your domain(s) and known competitor domains, normalized.
OWN_DOMAINS = {"seoautomationclub.com"}
COMPETITORS = {"ahrefs.com", "semrush.com", "zapier.com"}
Group queries by funnel stage in a spreadsheet column so you can later slice citation share by “informational” vs “commercial” intent. AI Overviews behave very differently across those buckets — informational queries trigger them far more often — and you’ll want that breakdown when you report.
Step 2: Query the engines and capture the raw answer
There are two honest paths to collecting answers. The clean path is an official or documented API where one exists — for example, calling a model provider’s API for the “ChatGPT-style” answer, or using a search API that returns AI-Overview blocks. The messier path is rendering the live SERP or answer page in a headless browser and reading the citation links out of the DOM. For Google AI Overviews specifically, there is no first-party visibility API, so most teams render the page through a managed scraping layer — this is exactly the job we sized up in our teardown of Bright Data vs Apify vs ScraperAPI for SEO pipelines. Whichever you use, the principle is identical: get the answer text plus its outbound citation URLs.
Here’s the shape of a provider-agnostic collector. Each engine adapter returns a normalized record so the rest of the pipeline doesn’t care where the answer came from:
import datetime, urllib.parse
def normalize_domain(url: str) -> str:
netloc = urllib.parse.urlparse(url).netloc.lower()
return netloc[4:] if netloc.startswith("www.") else netloc
def make_record(engine, query, answer_text, citation_urls):
domains = [normalize_domain(u) for u in citation_urls]
return {
"ts": datetime.datetime.utcnow().isoformat(),
"engine": engine,
"query": query,
"answer_text": answer_text,
"cited_domains": domains,
"own_cited": any(d in OWN_DOMAINS for d in domains),
"own_rank": next((i + 1 for i, d in enumerate(domains)
if d in OWN_DOMAINS), None),
"competitors_cited": [d for d in domains if d in COMPETITORS],
}
The two fields that earn their keep are own_cited (your boolean visibility) and own_rank (your position within the citation list, since being source #1 is worth more than being source #6). Note we capture answer_text too — you’ll want it for the sentiment pass later, and for spot-checking that the engine actually answered the question rather than refusing or hallucinating.
Step 3: Sample, don’t single-shot
Answer engines are stochastic. Run the same query twice and you may get a different citation set. If you record a single response per query you’ll mistake noise for movement and file false alarms every Monday. The fix is cheap: query each engine N times (3–5 is usually enough) and aggregate.
from collections import Counter
def citation_share(records, engine, query, n_samples):
runs = [r for r in records
if r["engine"] == engine and r["query"] == query]
own_hits = sum(1 for r in runs if r["own_cited"])
comp_counter = Counter(
d for r in runs for d in r["competitors_cited"])
return {
"engine": engine,
"query": query,
"own_citation_rate": round(own_hits / n_samples, 2),
"top_competitor": comp_counter.most_common(1),
}
Now your unit of measurement is a rate — “cited in 4 of 5 runs” — not a fragile single observation. Average those rates across the whole panel and you have a defensible AI-visibility score for the day. Trend that number, not the individual queries, and the noise washes out.
Step 4: Persist, then trend
Append every raw record to durable storage — a BigQuery table or even a partitioned Parquet file works — keyed by date, engine, and query. Trending logic then lives in SQL, where it belongs:
-- daily own-citation rate per engine
SELECT
DATE(ts) AS day,
engine,
AVG(CAST(own_cited AS INT64)) AS own_citation_rate,
COUNTIF(own_rank = 1) / COUNT(*) AS top_source_rate
FROM geo_citations
GROUP BY day, engine
ORDER BY day DESC;
Point Looker Studio at that table and you get a free dashboard: one line per engine, citation rate on the Y axis, time on the X. The moment a content refresh lands or a competitor publishes a stronger asset, you see the curve bend. This is the same pattern we used when comparing rank-tracker backends in GSC API vs SEMrush API vs Ahrefs API — own the data layer, keep the visualization disposable.
Step 5: Schedule it and let an agent triage the deltas
Wrap the collector in an n8n workflow: a Cron node fires the panel each morning, a Code node runs the sampling loop, a database node appends the records, and an IF node pings Slack only when a query’s citation rate drops below a threshold or a competitor newly appears. That last filter is what keeps the system from becoming background noise — you only hear about it when AI visibility actually moves.
If you want to go further, hand the daily delta to an agent. We walked through giving Claude live, structured access to your own SEO data in building an MCP server for Google Search Console; the same pattern applies here. Expose your geo_citations table through a tool, and an agent can answer “which queries did we lose AI visibility on this week, and which page should we strengthen?” without you writing the SQL each time.
Results, takeaways, and the honest limits
Teams who stand this up usually learn three things fast. First, their AI-citation footprint looks nothing like their ranking footprint — pages that rank page-two get cited, and page-one winners sometimes get ignored, because answer engines reward clearly-structured, directly-answering passages over raw authority. Second, citation rate is volatile day to day but stable week to week, which is exactly why the sampling-and-trending discipline matters. Third, the act of building the query panel is itself a content audit: every question you can’t credibly answer is a brief waiting to be written.
The limits are real and worth stating plainly. Engines change their surfaces without notice, headless rendering of AI Overviews can break overnight, and non-determinism means you’re measuring a distribution, not a fact. Don’t over-engineer sentiment scoring before the basic boolean pipeline is solid, and don’t present a single day’s number as gospel. Build the measurement scaffold, watch the trend, and adapt the adapters as the engines evolve.
The strategic point stands regardless of which engine wins: the search surface is fragmenting from one ranked list into several generated answers, and you can’t optimize what you don’t measure. A citation tracker is how you start measuring it — with code you own and can audit, not a score you have to trust. If you found this useful, bookmark SEOAutomationClub and check back for weekly automation playbooks like this one.
FAQs
What is GEO and how is it different from SEO?
Generative Engine Optimization (GEO) is the practice of getting your content cited and surfaced inside AI-generated answers — ChatGPT, Perplexity, Google AI Overviews — rather than ranked in a list of blue links. It overlaps heavily with SEO (clear structure, authority, and crawlability still help) but the success metric shifts from ranking position to whether your domain appears in the answer’s citation set.
Can I track Google AI Overview citations with an API?
There is no first-party Google API that reports AI Overview citations today. Most teams capture them by rendering the live SERP through a headless browser or a managed scraping layer and parsing the citation links out of the AI Overview block. Treat any such collector as something you’ll need to maintain as the interface changes.
How many queries should my GEO tracking panel have?
Start with 30–100 conversational, intent-rich questions you can credibly answer, grouped by funnel stage. That’s enough to produce a stable share-of-citation trend without overwhelming rate limits. Expand the panel as you validate which queries actually drive business value.
Why sample each query multiple times?
Answer engines are non-deterministic — the same prompt can return different citations on each run. Sampling each query 3–5 times per cycle and recording a citation rate instead of a single observation turns that noise into a trendable signal and prevents false-alarm alerts.
