<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: MUHAMMET İLBAŞ</title>
    <description>The latest articles on DEV Community by MUHAMMET İLBAŞ (@muhammetilbas).</description>
    <link>https://dev.to/muhammetilbas</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3987407%2Fcdd9b498-0331-4e56-9b1b-156089ab0bed.jpg</url>
      <title>DEV Community: MUHAMMET İLBAŞ</title>
      <link>https://dev.to/muhammetilbas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/muhammetilbas"/>
    <language>en</language>
    <item>
      <title>How I measure brand visibility across 5 LLMs without lying to myself (the engineering)</title>
      <dc:creator>MUHAMMET İLBAŞ</dc:creator>
      <pubDate>Tue, 16 Jun 2026 12:28:11 +0000</pubDate>
      <link>https://dev.to/muhammetilbas/how-i-measure-brand-visibility-across-5-llms-without-lying-to-myself-the-engineering-5b0f</link>
      <guid>https://dev.to/muhammetilbas/how-i-measure-brand-visibility-across-5-llms-without-lying-to-myself-the-engineering-5b0f</guid>
      <description>&lt;p&gt;"Does ChatGPT recommend my product?" sounds like a yes/no question. It is not. It's a &lt;em&gt;statistics&lt;/em&gt; question wearing a yes/no costume, and treating it like a boolean is how every naive brand-tracking script quietly lies to the person who wrote it.&lt;/p&gt;

&lt;p&gt;I spent a few months building a system that asks ChatGPT, Claude, Gemini, Perplexity, and Grok the same category questions on a schedule and reports whether a brand shows up. This post is the engineering — the four places where the naive version is wrong, and what I did instead. No marketing. Just the parts that were genuinely hard to get right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 1: the output is a distribution, not a value
&lt;/h2&gt;

&lt;p&gt;Here's the first thing that breaks people. Ask the same model the same brand question twice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; What are the best tools for team note-taking?
run 1 → [Notion, Obsidian, Evernote]
run 2 → [Notion, Coda, Obsidian, Microsoft OneNote]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same prompt. Same model. Different answer. Not a bug — sampling, plus live retrieval variance on web-connected modes. Which means a single call gives you one draw from a distribution, and reporting it as "we rank #2!" is like measuring a coin by flipping it once and declaring it 100% heads.&lt;/p&gt;

&lt;p&gt;So the unit of measurement can't be one response. It has to be a &lt;strong&gt;mention rate over N runs&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;brands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ask_and_extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# list of brands, ranked
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;entity_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;brands&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;   &lt;span class="c1"&gt;# 0.0 .. 1.0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How big does &lt;code&gt;n&lt;/code&gt; need to be? You're estimating a proportion &lt;code&gt;p&lt;/code&gt;, so the standard error is &lt;code&gt;sqrt(p*(1-p)/n)&lt;/code&gt;. At &lt;code&gt;n=10&lt;/code&gt; your error bar is roughly ±15 points — fine for "are we at 0% or 60%," useless for "did we go from 52% to 58%." I settled on running enough samples to stabilize the &lt;em&gt;trend&lt;/em&gt; and then smoothing across days with a moving average, because day-to-day jitter will otherwise generate fake "wins" and "losses" that send you chasing noise. The score you report should move slower than the underlying randomness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; if your brand tracker returns a number from a single API call, it's not measuring visibility. It's sampling noise and rounding it to a story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 2: if your prompt names the brand, you've already cheated
&lt;/h2&gt;

&lt;p&gt;My first prompts looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How good is {{brand}} for team note-taking?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The visibility numbers were fantastic. They were also meaningless. By naming the brand, I primed the model to talk about it — I was measuring "will the model discuss a thing I explicitly handed it" (almost always yes), not "does the model bring this brand up on its own" (the only thing that matters).&lt;/p&gt;

&lt;p&gt;The fix is &lt;strong&gt;brand-blind prompts&lt;/strong&gt;. Ask the category question the way a stranger with no knowledge of your product would, and check the unprimed answer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# wrong — primed
"Is {{brand}} a good note-taking app?"

# right — brand-blind
"What are the best note-taking apps for a small team in 2026?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sounds obvious written down. In practice almost every "track your AI mentions" script I've looked at smuggles the brand into the prompt somewhere, and the resulting dashboards are self-congratulatory fiction. If you build one rule into your own version, make it this one: the brand name never appears in the question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 3: "did it mention us" is an entity-resolution problem, not a substring search
&lt;/h2&gt;

&lt;p&gt;The tempting implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;mentioned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;# please don't
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every one of these fails it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aliases:&lt;/strong&gt; &lt;code&gt;Zene&lt;/code&gt;, &lt;code&gt;zene.ai&lt;/code&gt;, &lt;code&gt;tryzene&lt;/code&gt;, &lt;code&gt;Zene app&lt;/code&gt; — one entity, many surface forms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Substring collisions:&lt;/strong&gt; a brand named &lt;code&gt;Coda&lt;/code&gt; matches &lt;code&gt;decoder&lt;/code&gt;, &lt;code&gt;codable&lt;/code&gt;, and the musical term &lt;em&gt;coda&lt;/em&gt;. Short brand names are a minefield.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Negation / framing:&lt;/strong&gt; &lt;code&gt;"I wouldn't recommend X"&lt;/code&gt; contains the brand but is the opposite of a win. And being listed &lt;strong&gt;first&lt;/strong&gt; is wildly different from a grudging parenthetical at the end.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I stopped parsing free text and instead forced &lt;strong&gt;structured output&lt;/strong&gt; — make the model return the ranked entities directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;SCHEMA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;brands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;array&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rank&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;integer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sentiment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;enum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;positive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;neutral&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;negative&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rank&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;brands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…then run an alias table + normalized matching on &lt;code&gt;name&lt;/code&gt;, not on the raw paragraph:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ALIASES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zene&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zene&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zene.ai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tryzene&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zene app&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;entity_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extracted&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ALIASES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;extracted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you also get rank and sentiment for free, which turns "are we mentioned" into "where, and how favorably." Budget real time for this layer — it's the quiet difference between a tracker that's right and one that &lt;em&gt;feels&lt;/em&gt; right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 4: five engines, on a schedule, without setting money on fire
&lt;/h2&gt;

&lt;p&gt;Once it works for one model, you want all five — and they don't behave the same. The big split:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Memory-only answers&lt;/strong&gt; reflect &lt;em&gt;training data&lt;/em&gt;: the slow, internet-wide accumulation of how often and how positively a brand appears. A new brand is invisible here for a long time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web-connected answers&lt;/strong&gt; (Perplexity, ChatGPT search, Gemini, Grok-with-browsing) do live retrieval and will surface a brand that barely exists in training data — &lt;em&gt;if&lt;/em&gt; its content ranks and reads well right now.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tracking them as one blended number hides the most useful signal you have: the gap between your web-on and web-off visibility tells you whether your problem is &lt;strong&gt;content&lt;/strong&gt; (fixable this month) or &lt;strong&gt;reputation&lt;/strong&gt; (a longer game).&lt;/p&gt;

&lt;p&gt;Two engineering notes that saved the budget:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run engines concurrently, not sequentially.&lt;/strong&gt; A scan is N questions × M engines × K samples; doing it serially is brutal latency. Fan out per engine and gather.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web search is the cost bomb.&lt;/strong&gt; Browsing-enabled calls cost multiples of a plain completion. Cap tool/search uses per call, cache aggressively on a freshness window (the web doesn't change in 10 minutes), and degrade to cheaper engines when you hit a budget ceiling. "Scan everything, every hour" will bankrupt you for zero added signal.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Bonus: citations are a free dataset, use them
&lt;/h2&gt;

&lt;p&gt;When web-connected engines answer, they cite sources. Those citations are a literal list of the domains the model currently trusts for a topic. Extract and aggregate them across a category's scans and you get a ranked map of &lt;em&gt;where to earn presence&lt;/em&gt; — the directories, comparisons, reviews, and threads the AI actually pulls from. I denormalize cited domains out of every result for this; it's one of the most-used outputs of the whole thing. Don't throw the citation array away — it's better targeting data than any keyword tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this all rolls up to
&lt;/h2&gt;

&lt;p&gt;If you take nothing else: &lt;strong&gt;AI search visibility is an observable, instrumentable system.&lt;/strong&gt; Sample over time instead of once, control your inputs (brand-blind prompts), separate your signals (web-on vs web-off), resolve entities instead of grepping strings, and watch the trend rather than the draw. Do that and you get an honest number. Skip any one of them and you get a flattering lie.&lt;/p&gt;

&lt;p&gt;I packaged all of this into a product called &lt;a href="https://tryzene.com" rel="noopener noreferrer"&gt;Zene&lt;/a&gt; — scheduled brand-blind scans across the five engines, entity-level mention resolution, per-engine trends, competitor comparison, and the cited-domain map — but the four problems above are the actual content. Whether you use the tool or roll your own, those are the traps.&lt;/p&gt;

&lt;p&gt;One last, unrelated-but-related warning, since this is a builder crowd: the moment you have a domain, you'll get emails selling "1,000 forum backlinks" to boost this stuff. Don't. Those links land you next to replica-watch and pirated-movie spam, and no model — or search engine — trusts a brand keeping that company. The signal that moves AI visibility is the same boring, durable thing it always was: be genuinely worth quoting, and be corroborated by sources that aren't you.&lt;/p&gt;

&lt;p&gt;Run a brand-blind scan on your own product. If the number's ugly, congrats — you found a real problem while it's still early enough to matter.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>marketing</category>
      <category>seo</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
