How to measure share of voice in Perplexity
Share of voice in Perplexity is the share of your category's answers that belong to you rather than your competitors - and on a search-grounded engine you should measure it twice. Mention share is how often each brand is named in answerText. Citation share is how often each brand's domain appears in sources[]. Run a fixed query set, count both, print them side by side.
The gap between the two numbers is usually more useful than either alone: you can be cited without being named, or named entirely from a competitor's page. This guide covers the by-hand version, the one-call version in curl and Python, the agent version over MCP, and the ways a share-of-voice number quietly becomes meaningless.
Read this page with an AI
To measure share of voice in Perplexity, fix a query set and a competitive set, run every query, and count two things per answer: which brands the text names, and which brands' domains appear in the citation list. Divide each brand's count by the total across the set and you have mention share and citation share. Track both - on a search-grounded engine they routinely disagree, and the disagreement is the finding.
The manual way (and why it stops working)
Do this on paper once; it's worth the afternoon. Pick ten questions a buyer would genuinely type. Run each in Perplexity and, for every brand you track, tick two boxes: named in the text, cited in the sources. Twenty rows of ticks later you can divide and get real percentages - and you'll have learned more about how your category gets described than any dashboard would have told you.
Then try to do it again next month:
- Ten queries is the floor, and by hand it's also the ceiling. Share of voice measured on three answers is noise wearing a percentage sign. Thirty queries a month, transcribed by hand, is a job nobody keeps doing past the second cycle.
- Ticking boxes drifts. Long source lists, similar-looking domains, a brand named twice in one answer - manual counting makes small consistent errors, and consistent errors look exactly like trends.
- No denominator discipline. If one answer fails to load and you quietly skip it, every percentage changes base without anyone noticing. A script can skip it and report that it did.
- You can't recompute. Change your mind about what counts as a mention and the entire afternoon happens again. With the raw answers stored, you re-run the counter in a second.
- No trend. A share-of-voice number with no previous number is a fact, not a metric - and it can't go into a monthly report, a client deck, or an alert.
Do it with one API call
Each query is one POST. The response carries everything both metrics need - the answer prose and the structured citation list - so the counting is a loop, not a scraping project.
curl -X POST https://api.agentgeo.org/v1/fetches \
-H "Authorization: Bearer ag_live_..." \
-H "Content-Type: application/json" \
-d '{"query": "simple CRM with email integration", "surfaces": ["perplexity"]}'Now the measurement. Declare the competitive set explicitly, run the query set, and tally mentions and citations into separate counters:
import requests
from collections import Counter
from urllib.parse import urlparse
KEY = "ag_live_your_key_here"
# Share of voice is always share *within a set you declare*. Declare it.
BRANDS = {
"Acme": "acme.com",
"Globex": "globex.com",
"Initech": "initech.com",
}
QUERIES = [
"best CRM for small business",
"CRM software for a 10-person sales team",
"simple CRM with email integration",
"cheapest CRM for startups",
]
def host(url):
h = urlparse(url).netloc.lower()
return h[4:] if h.startswith("www.") else h
mentions, citations = Counter(), Counter()
delivered = skipped = 0
for query in QUERIES:
resp = requests.post(
"https://api.agentgeo.org/v1/fetches",
json={"query": query, "surfaces": ["perplexity"]},
headers={"Authorization": f"Bearer {KEY}"},
timeout=200, # the API waits up to 180s for live surfaces
)
resp.raise_for_status()
for answer in resp.json()["answers"]:
if answer.get("status") == "failed":
skipped += 1 # costs 0 credits, and must not count as a zero
continue
delivered += 1
text = (answer.get("answerText") or "").lower()
hosts = {host(s["url"]) for s in answer.get("sources", [])}
for brand, domain in BRANDS.items():
if brand.lower() in text:
mentions[brand] += 1
if any(h == domain or h.endswith("." + domain) for h in hosts):
citations[brand] += 1
total_mentions = sum(mentions.values()) or 1
total_citations = sum(citations.values()) or 1
print(f"{delivered} answers counted, {skipped} skipped\n")
print(f"{'brand':<10}{'mention share':>15}{'citation share':>16}")
for brand in BRANDS:
m = mentions[brand] / total_mentions
c = citations[brand] / total_citations
print(f"{brand:<10}{m:>15.0%}{c:>16.0%}")That prints a two-column table: mention share and citation share for every brand you track, plus how many answers were actually counted. Four queries against one surface is four delivered records - four credits - and failed records cost nothing, which is exactly why the script skips them instead of scoring them as zero.
The two shares diverging is the result. Mention share high and citation share low means Perplexity is happy to name you but is assembling its answers out of other people's pages - a content problem: you don't own the reference the engine reads. Citation share high and mention share low means your pages are feeding the answer while somebody else's brand is the subject of the sentence - a positioning problem: your own content doesn't put your product forward as the answer. A single blended "visibility score" would have hidden both, which is why AgentGEO returns the raw records and lets you do the arithmetic.
| Pattern | What it usually means | Where to work |
|---|---|---|
| Mentions high, citations low | The category writes about you; you don't publish the page it quotes | Own the source - publish the comparison, the pricing detail, the spec everyone paraphrases |
| Mentions low, citations high | Your pages are useful but don't name your product as the answer | Write for the passage - make the product the subject of the claim |
| Both low | You're outside the retrieval set for these queries | Coverage - publish something that answers this exact question |
| Both high | You own this query set | Widen it - add the adjacent intents you aren't measuring yet |
Let your agent do it
Share of voice is arithmetic over records, which makes it a natural fit for an agent: it can fetch the query set, do the counting, and write the month's summary in one pass. Connect the server once:
claude mcp add agentgeo -- npx -y agentgeo-mcp --key ag_live_...One tool, fetch_raw_answers, over stdio - zero npm dependencies, Node.js 18+. surfaces takes one to six engine keys per call, so the same instruction extends past Perplexity when you want it to. Then the ask:
- "Run these ten queries through Perplexity and give me mention share and citation share for Acme, Globex and Initech."
- "Compare this month's shares against sov-2026-07.json and tell me which movement is big enough to be real."
- "For every query where Globex is cited and we aren't, list the exact URLs Perplexity used."
AgentGEO computes no share-of-voice number itself - it ranks nothing and scores nothing. The definitions stay in your code or your agent, which is the point: the moment a vendor picks the denominator for you, the metric stops being comparable to anything you'd calculate yourself.
Common problems
Share of voice is easy to compute and easy to compute wrongly. The five that matter:
- Share of what, exactly? These percentages are shares within the brand set you chose. Add a fourth competitor and everyone's number drops. Write the tracked set and the query set into the report, or the number can't be interpreted - least of all six months later.
- Small query sets swing violently. With five queries, one changed answer moves share by twenty points. Aim for twenty to thirty queries per category before treating any movement as signal.
- Failed records are not zeros. A record with
status: "failed"cost nothing and told you nothing. Skip it and report the count you skipped; folding it into the denominator makes every brand look worse for a reason that has nothing to do with the market. - Name collisions inflate mention share. Substring matching on a short brand name catches unrelated words and other companies. Use word boundaries, and spot-check the raw
answerTextbehind any brand whose number suddenly jumps. - One market at a time.
countrydefaults to"US"and is the lever that matters here - Perplexity's scraper takes a country and rejects a language field, solanguageonly applies to Google AI Overviews. A German query set is a different market with a different competitive set: keep it as its own table instead of averaging two markets into a number that describes neither.
Summary
Measuring share of voice once, by hand, is a genuinely useful afternoon and you should do it. Measuring it every month, over enough queries to mean something, with both mention and citation counted and last month's numbers to compare against - that's a script or an agent: one call per query, two counters, and a stored history. Keep the definitions in your own code and the number stays yours.
Put a real number on your Perplexity share of voice. Get a free audit - no card and start from the answers themselves.
Get my free audit