Tutorial7 min read2026-10-02

The AI citation share-of-voice formula

AI citation share of voice is your cited domains divided by all cited domains across a fixed query set: SoV = your_citations / total_citations. That's the whole formula — but every term hides a decision that changes the number. Which denominator (cited domains, or answers that mention you)? How do you normalize a URL before it counts? Do you weight by the position a source held in its answer? And how many runs before the number is a measurement rather than a sample? This post pins down each of those so the figure you report is defensible. It's the reference companion to the runnable pipeline in measure AI share of voice without scraping — that post is the code, this one is the math.

Tutorial

Read this page with an AI

The shortest honest answer: pick a denominator and hold it. Citation share of voice is your brand's cited pages divided by every cited page across the query set; mention share of voice is answers that name your brand divided by answers delivered. Both are legitimate, both are percentages, and they are not the same number — the mistake that ruins these reports is computing one and labelling it the other. State which you used, next to the figure, every time.

Two denominators, two different questions

Share of voice is always a ratio of your presence to total presence. What varies is how you define "presence" — and AI answers give you two clean sources for it in the same record. sources[] supports citation share; answerText supports mention share. They answer different questions and can move in opposite directions in the same run, which is exactly why you can't blend them.

DefinitionNumeratorDenominatorThe question it answers
Citation SoVYour domain's cited pagesAll cited pages across the query setDo your pages earn the link? Hard to game — you either got cited or you didn't.
Mention SoVAnswers whose answerText names your brandAnswers delivered across the query setDoes the model say your name? Broader presence, but a name-check without a link counts.
Weighted citation SoVYour cited pages, scaled by 1/positionAll cited pages, scaled by 1/positionAre you cited high, not just cited? Rewards the first slot over the eighth.

A brand can lead on mention share and trail on citation share — the model likes to name it but doesn't link its pages — or the reverse, where it's cited constantly but rarely named in prose. Both facts are useful; reported as one number they cancel into nonsense. The rule is one denominator per figure, chosen for the claim you're making, printed alongside it.

Normalization comes before counting

Before any division, the URLs have to be reduced to a canonical host, or the denominator is wrong. sources[] returns whatever URL the engine cited, and the same page shows up in incompatible forms across runs and engines. Count them raw and one page becomes three hosts — the citation table inflates for whoever gets linked with tracking parameters and deflates everyone linked cleanly.

normalize.py — the minimum that makes counts trustworthy
from urllib.parse import urlparse, urlunparse


def normalize(url):
    """Reduce a cited URL to (host, clean_url) before it enters any count."""
    parts = urlparse(url)
    host = parts.netloc.lower().removeprefix("www.")  # lowercase, drop www.
    clean = urlunparse((
        parts.scheme,
        host,
        parts.path.rstrip("/"),  # drop trailing slash
        "",                      # params
        "",                      # query string — strip ?ref=, ?utm_ ...
        "",                      # fragment
    ))
    return host, clean


# These three cite the SAME page. Unnormalized, they count as three hosts.
for u in [
    "https://www.acme.com/guide/",
    "https://acme.com/guide",
    "https://acme.com/guide?ref=pplx",
]:
    print(normalize(u))
# -> ('acme.com', 'https://acme.com/guide')  x3
  • Lowercase the host. Acme.com and acme.com are one domain; hosts are case-insensitive, so fold them.
  • Drop the www. prefix. www.acme.com and acme.com resolve to the same brand and must count as one.
  • Strip the query string. ?ref=pplx, ?utm_source=... and friends are tracking noise that fractures a single page into many.
  • Strip the trailing slash. /guide/ and /guide are the same path — normalize the tail so they collapse.
  • Aggregate by host, diagnose by page. Count on the host, but keep the clean URL in a column: eight citations of one domain could be one guide cited eight times or eight scattered posts, and only the URL tells you which.

Normalization is the same for both denominators. For citation SoV you normalize each entry in sources[]; for mention SoV you normalize nothing about URLs but you do want a word-boundary match on the brand name — "Acme" should not match inside "Acmegraph". Different fields, but both need a canonicalization step before the count, or the number lies.

A worked example, end to end

Take one query — "best time tracking software" — fetched once. The engine returned an answer that named Acme and Toggl in prose, and cited six pages. Here is every source after normalization, with the position it held in the answer:

PositionNormalized hostBrand1/position weight
1acme.comAcme1.00
2toggl.comToggl0.50
3acme.comAcme0.33
4g2.comother0.25
5northwind.ioNorthwind0.20
6reddit.comother0.17

Now run the three formulas over this single answer. Note that acme.com was cited twice — after normalization it's one host with two cited pages, which the count must reflect, not collapse to one.

The three numbers, from the same six sources
total cited pages = 6

# Citation SoV — Acme's cited pages / all cited pages
Acme      = 2 / 6 = 33.3%
Toggl     = 1 / 6 = 16.7%
Northwind = 1 / 6 = 16.7%
other     = 2 / 6 = 33.3%

# Mention SoV — this one answer names Acme and Toggl
#   denominator is answers delivered (1 here), so per-brand it is 1/1 or 0/1
Acme      = 1 / 1 = 100%   (named)
Toggl     = 1 / 1 = 100%   (named)
Northwind = 0 / 1 =   0%   (cited, but not named in prose)

# Weighted citation SoV — sum of 1/position for the brand / sum for all
total weight = 1.00 + 0.50 + 0.33 + 0.25 + 0.20 + 0.17 = 2.45
Acme      = (1.00 + 0.33) / 2.45 = 54.3%   # rewarded for holding slot 1
Toggl     = 0.50 / 2.45          = 20.4%
Northwind = 0.20 / 2.45          =  8.2%
other     = (0.25 + 0.17) / 2.45 = 17.1%

Look at what changed. On plain citation share Acme and "other" tie at 33%. Weight by position and Acme jumps to 54% because it owns slot 1, while "other" — cited only in slots 4 and 6 — sinks to 17%. And Northwind, cited once but never named, is 17% on citation share yet 0% on mention share. Three defensible numbers, three different stories, from one answer. That's why the definition belongs next to the figure and why you never average across denominators.

Position-weighting is optional and it's a choice, not a correction. Use 1/position when being cited first genuinely matters more to you than being cited at all — a default recommendation versus an also-ran in a list. If every citation is worth the same to your argument, plain citation share is cleaner and easier to defend. Whatever you pick, apply it identically to every brand and every run.

Want the number computed for your brand before you write the code? Get a free AI-visibility audit → — no card, no account.

Get my free audit

Position is per-answer, not per-run

One subtlety the worked example glosses: position ranks within a single answer only, and it starts at 1. Being position: 1 of four sources is a different outcome from position: 1 of twelve — the first is a third of the citations, the second a twelfth. If you weight by position or trend it over time, you must record each answer's source count alongside it, or a shrinking answer will look like a rank gain that never happened.

  • Store the per-answer source count. Position without the denominator it lived in is uninterpretable later. One column, recorded at fetch time, saves the whole series.
  • Never compare positions across engines directly. A position: 2 on Perplexity (which cites nearly everything) and a position: 2 on ChatGPT (which often cites nothing) are not the same signal. Keep engines in separate series.
  • Empty sources[] from ChatGPT is real data. When ChatGPT didn't browse, it returns no sources — that's the model answering from memory, not a fetch failure. For citation SoV that answer contributes zero to the denominator; for mention SoV it still counts if it named a brand.

Sampling and drift: why one run isn't the answer

The formula is exact; the input is not. A single run of a query set is one draw from a distribution the engines redraw on every ask. Perplexity re-searches each time and its citation set visibly drifts between runs; AI Overviews sometimes doesn't render at all. Treating one run's percentage as "the" share of voice is the statistical error underneath every other one.

  • A query set plus repeated runs is the unit of measurement — not a single fetch. Freeze the query set, run it on a cadence, and average or track the distribution across runs rather than trusting any one.
  • Append and diff; require movement across several runs. A brand moving from 15% to 18% in one run pair is noise until it holds across three or four. Demand consistency before you call a change a trend.
  • Changing the query set starts a new series. Add three easy queries you rank well for and your share jumps with nothing improving. Hold the set for the comparison window; when you must change it, begin a new series rather than continuing the old one.
  • Excluded records must leave both numerator and denominator. A failed record — a missing AI Overview, a scrape that timed out — costs zero credits and contributes nothing. Divide by answers delivered, not queries sent, or you understate every brand at once.

Summary

The AI citation share-of-voice formula is your_citations / total_citations — trivial arithmetic wrapped around four decisions that decide whether it means anything: which denominator (citation or mention), how you normalize a URL before counting, whether you weight by position, and how many runs stand behind the figure. Pin those down, print the definition next to the number, and the metric survives a stakeholder's questions. When you're ready to compute it on a schedule, the runnable pipeline is in measure AI share of voice without scraping, and the endpoint that returns the raw answers is documented on the Python API page.

FAQ

Direct answers to the questions this page raises.

Citation share of voice is your brand's cited pages divided by all cited pages across a fixed query set: SoV = your_citations / total_citations. You compute it from each answer's sources[], after normalizing every URL to a canonical host so one page doesn't count as several. State that you used the citation denominator, because mention share of voice is a different number.

Citation SoV divides your cited domains by all cited domains — it measures whether your pages earn the link. Mention SoV divides answers that name your brand in answerText by answers delivered — it measures whether the model says your name. They come from different fields, can move in opposite directions, and must never be averaged into one figure.

Only if being cited first matters more to you than being cited at all. Weighting each source by 1/position rewards the top slot — useful when you care about default recommendations, not just presence. It's a choice, not a correction: apply it identically to every brand and every run, and note that position ranks within one answer, so record each answer's source count too.

Because the same page arrives in incompatible forms — with www., with tracking parameters, with or without a trailing slash. Counted raw, one page becomes two or three hosts and the denominator skews toward whoever gets linked with query strings. Lowercase the host, drop www., strip the query string and trailing slash before anything enters the count.

More than one — a single run is a sample from a distribution the engines redraw on every ask. Freeze the query set, run it on a cadence, append each run with its timestamp, and require a brand to move consistently across several runs before calling it a trend. Perplexity drifts most; single-run swings are usually noise.

Keep reading

Where this page leads next.

Run these checks on your own brand

Two ways in. Send a URL and a person runs the fetches for you, free — or connect your agent over MCP, on a plan, and run them yourself. Either way you get the raw answers and their citations, never a score.