GEO metrics vs SEO metrics: what maps and what doesn't
Most SEO metrics don’t carry over to AI answers: there is no ranked list of ten links to hold a position in, no impression count per answer, and no click-through rate you can observe from outside. The intent behind each metric (presence, prominence, share, traffic) does carry over, and each can be rebuilt from the structured fields an answer returns. Those fields differ by engine, so the tables below give the field per engine.
Definitions
- Answer: one engine’s response to one prompt in one market at one time.
- Mention: your brand name appears in the answer text.
- Citation: a URL on your domain appears in the answer’s
sources[](orcitationPills[]). - Answer block: a structured unit in the answer other than prose, such as shopping cards, place cards or ads.
- Fan-out query: a web search the engine ran while producing the answer, where the engine exposes it.
The translation table
| SEO metric | GEO counterpart | How well it maps |
|---|---|---|
| Average position | Citation rate, plus first cited position in sources[] |
Partial. Being cited is binary and measurable; whether earlier source positions get more attention is not publicly established |
| Impressions | Mention rate over a fixed prompt set | Partial. You measure presence in a sample of prompts you chose, not in real user traffic |
| CTR | AI referral sessions per cited page | Weak. Only clicks are counted, and only where a source is identifiable (how) |
| Share of voice | Citation share and mention share against named competitors | Good. Both are fractions of a known denominator |
| SERP feature presence | Answer block presence (shoppingCards, places/map, ads) |
Good, but field names and shapes differ per engine |
| Keyword rank tracking | Fan-out query coverage in Google organic results | New. Possible only where the engine exposes its searches |
| (none) | Sentiment and framing of the mention | New. Requires reading the text |
The first row deliberately leaves out the order of ChatGPT’s entities[]: no public evidence shows that its order reflects preference.
Formulas
Compute every metric per engine and per market over a fixed prompt set P run at time t. Let A be the set of completed answers.
- Mention rate = |answers in A that mention the brand| / |A|
- Citation rate = |answers in A with at least one source URL on your domain| / |A|
- Citation share = (number of source entries on your domain, summed over A) / (number of source entries, summed over A)
- Mention share (vs a competitor set C) = mentions of your brand / mentions of any brand in C ∪ {you}, counted once per answer per brand
- First cited position = for each answer that cites you, the smallest
positionamong your entries insources[]; report the median, and report it alongside citation rate, never alone - Block presence rate (block b) = |answers where block b contains your brand or domain| / |answers where block b is present|
- Fan-out coverage = |fan-out queries where your domain appears in Google
organicResults[]| / |fan-out queries| - Referral yield (page u) = AI referral sessions to u / number of answers citing u in the period
Block presence uses answers that contain the block as its denominator; otherwise an engine that rarely shows shopping cards looks like a failure. Citation share counts entries, so an answer with twenty sources dilutes your share more than one with four; that is intended, since you own less of that answer’s evidence.
For the share-of-voice variants and competitor weighting, see the share of voice framework.
Where each engine returns the data
All fields are under result in a synchronous response, or response.result for a completed async task. Many optional fields are omitted rather than returned empty, so read them with a default. “Empty today” marks ChatGPT fields that exist but come back empty on the ChatGPT answers served today.
| Signal | ChatGPT | Perplexity | Gemini | Copilot | Grok | AI Mode | Google Search |
|---|---|---|---|---|---|---|---|
| Answer text | text |
text |
text |
text |
text |
text |
aioverview.text |
| Cited sources | sources[] |
sources[] |
sources[] |
sources[] |
sources[] |
sources[] |
aioverview.sources[] |
| Inline citations | citationPills[] (0-based id) |
citationPills[] (1-based) |
citationPills[] (1-based) |
citationPills[] (1-based) |
none | citationPills[] (1-based) |
aioverview.citationPills[] |
| Fan-out queries | searchQueries[] (free; empty today) |
none | none | coming | searchQueries[] |
none | n/a |
| Entities | entities[] |
none | none | none | none | none | knowledgeGraph (different concept) |
| Shopping | inlineProducts[] (with include.shopping); shoppingCards[] empty today |
none | none | coming | none | shoppingCards[], inlineProducts[] |
shoppingCards[] |
| Places | map[] |
none | places[] |
coming | none | places[], map[] |
localResults[] |
| Ads | ads[] array (with include.ads; empty today) |
none | none | none | none | ads object: {title, ads[]} |
ads[] array, aioverview.ads[] |
| Organic ranking | n/a | n/a | n/a | n/a | n/a | n/a | organicResults[].position |
Shapes differ inside the same concept. A ChatGPT shopping product title is inlineProducts[].product.title (shoppingCards[] is empty today: ChatGPT answers show no merchant cards); on AI Mode, shoppingCards[].title. The engine pages list every field.
Computing the metrics from stored answers
This function takes one stored answer and returns the per-answer facts the formulas need, handling the per-engine differences in one place so aggregation code stays engine-agnostic.
import re
from urllib.parse import urlsplit
def on_domain(url: str, domain: str) -> bool:
host = urlsplit(url).netloc.lower().removeprefix("www.")
return host == domain or host.endswith("." + domain)
def product_titles(engine: str, r: dict) -> list[str]:
if engine == "CHATGPT":
return [p.get("product", {}).get("title", "") for p in r.get("inlineProducts", [])]
if engine == "AIMODE":
return [c.get("title", "") for c in r.get("shoppingCards", [])] + [p.get("title", "") for p in r.get("inlineProducts", [])]
return []
def answer_facts(engine: str, r: dict, brand: str, domain: str) -> dict:
if engine == "GOOGLE":
r = r.get("aioverview") or {}
pattern = re.compile(rf"\b{re.escape(brand)}\b", re.IGNORECASE)
sources = r.get("sources", [])
mine = [s for s in sources if on_domain(s.get("url", ""), domain)]
titles = product_titles(engine, r)
ads = r.get("ads", [])
ad_items = ads.get("ads", []) if isinstance(ads, dict) else ads # object on AI Mode, array elsewhere
return {
"mentioned": bool(pattern.search(r.get("text") or "")),
"sources_total": len(sources),
"sources_mine": len(mine),
"first_cited_position": min((s["position"] for s in mine if "position" in s), default=None),
"shopping_present": bool(titles),
"shopping_mine": any(pattern.search(t) for t in titles),
"ads_present": bool(ad_items),
"fan_out": r.get("searchQueries", []),
}
Aggregate per engine and market: mention rate is the mean of mentioned; citation rate is the mean of sources_mine > 0; citation share is sum(sources_mine) / sum(sources_total); shopping presence rate is sum(shopping_mine) / sum(shopping_present). The Google branch reads the AI Overview only; skip answers where aioverview is null when computing AI Overview rates, and count them for an “AI Overview shown” rate instead. Ad brand matching needs its own per-engine accessor (ChatGPT ads[].brand.name, empty on the answers served today; AI Mode ads.ads[].store; Google ads[].domain); see tracking ads in AI answers.
What doesn’t map
- Framing. The same mention can recommend you, compare you neutrally, or warn against you. Sentiment has no SEO counterpart, and it needs the text rather than a field.
- Run-to-run variation. The same prompt can produce different sources and brands minutes apart, so a single run is one sample. Plan sample sizes and cadence and read answer volatility before trusting a week-over-week change.
- Several engines per prompt. Averaging ChatGPT and Gemini into one “AI visibility” number hides that they cite different sources and return different blocks. Report per engine first; blend only with explicit weights.
- The query layer. Where an engine returns fan-out queries, you see searches you never tracked. Google documents that AI Overviews and AI Mode may issue multiple related searches, which it calls query fan-out (AI features and your website, checked 2026-09-17).
The two-layer dashboard
Keep both pipelines. The retrieval layer is Google Search: organicResults[] positions for your keywords and for the fan-out queries of engines that return them. ChatGPT’s list comes back empty on the answers served today (chatgpt query fan-out). The answer layer is the monitor endpoints: mention rate, citation rate and share, block presence, per engine and market.
Cost per run at Starter-tier prices for an illustrative set of 100 prompts: ChatGPT $0.0006 and Gemini $0.0006, so $0.0012 per prompt and $0.12 per run. Adding 3 Google queries per prompt (an illustrative number) at $0.00103 each adds 100 × 3 × $0.00103 = $0.309, for $0.429 per run. Cost planning covers cadence choices.
The migration map walks the full move of an SEO reporting stack. For field details, start at the AI visibility tracking use case.
Questions
What is the GEO equivalent of search ranking position?
There is no exact equivalent. The closest measurable proxies are whether your domain is cited at all (citation rate), the position of your first cited source in the answer's sources list. Neither has been shown publicly to behave like a ranking, so report them as descriptive measures.
What is the difference between citation rate and citation share?
Citation rate is the fraction of answers that cite your domain at least once. Citation share is the fraction of all cited sources, across those answers, that are yours. A brand can have a high citation rate and a low share if it is cited once in answers that list many sources.
Which engines return an entities list?
Only ChatGPT. Its entities field is an array of type and name pairs for the brands and products the answer names. On Gemini and the other engines, measure mentions by matching your brand in the answer text.
Are ads and places returned the same way on every engine?
No. Ads are an array on ChatGPT and Google Search but an object with a title and an ads array on AI Mode; ChatGPT's ads array comes back empty on the answers served today. Places are returned as places and map on AI Mode, and as map on ChatGPT, with different item fields; Copilot's map results are coming.
Should I stop tracking Google rankings once I track AI answers?
Keep both. Google documents that AI Overviews and AI Mode may run multiple related searches to build a response, so classic rankings for those queries still describe part of the candidate pool, while answer monitoring describes what was said and cited.