---
title: "Build a Weekly AI Brand Monitor with Google Sheets and Three APIs"
description: "A brand-monitoring dashboard can tell you that ChatGPT mentioned a competitor instead of you. So can a spreadsheet and a script."
url: "https://groas.com/post/build-your-own-ai-visibility-monitor-in"
image: "https://groas.com/media/blog/0294c5c19287ab7bc82dd6c269d57addd671dde0b94cb99a9fe6761ef751bfc7.png"
published: "2026-10-06T05:33:15.410Z"
modified: "2026-10-06T05:33:15.865Z"
---

[AI For Google Ads](https://groas.com/category/ai-for-google-ads) · October 6, 2026 · 10 min read

# Build a Weekly AI Brand Monitor with Google Sheets and Three APIs

[DavidFounder & CEO @ groas](https://groas.com/author/david)

![Three mismatched homemade rain gauges on a fence post catch falling paper speech bubbles, a few marked with orange stars, tallied on graph paper.](https://groas.com/media/blog/0294c5c19287ab7bc82dd6c269d57addd671dde0b94cb99a9fe6761ef751bfc7.png)

In this article

1. [What you’ll build, and what you need first](#what-youll-build-and-what-you-need-first)
2. [Step 1: Put buyer questions in Prompts](#step-1-put-buyer-questions-in-prompts)
3. [Step 2: Keep keys out of the grid](#step-2-keep-keys-out-of-the-grid)
4. [Step 3: Add the script and run one test](#step-3-add-the-script-and-run-one-test)
5. [Step 4: Schedule Monday and calculate a rate](#step-4-schedule-monday-and-calculate-a-rate)
6. [Step 5: Read mentions and citations separately](#step-5-read-mentions-and-citations-separately)
7. [Step 6: Check what the sheet cannot see](#step-6-check-what-the-sheet-cannot-see)
8. [Step 7: Verify the job, then change the first thing that matters](#step-7-verify-the-job-then-change-the-first-thing-that-matters)

A brand-monitoring dashboard can tell you that ChatGPT mentioned a competitor instead of you. So can a spreadsheet and a script. I spent years using that combination for Google Ads reporting, and the same approach gives a small business a low-cost way to watch AI answers every week. It also has three ways to lie to you: **API answers are not consumer answers, one run is noise, and a mention is not a citation.**

## What you’ll build, and what you need first

You’ll build a Google Sheet that sends buyer questions to OpenAI with web search, Perplexity Sonar, and Gemini Flash with Google Search grounding. Each week it will record brand and competitor mentions, cited URLs, and the share of successful runs that mention your brand. **The output is a directional monitor, not a transcript of what every buyer sees.**

Start with a fresh Google Sheet and API keys for OpenAI, Perplexity, and Google AI Studio. Enable billing where your accounts require it. At this scale, the bill can be a few dollars a month, but do not treat the draft’s under-$5 estimate as a cap: tokens, search requests, and retries affect the total. Check each provider’s pricing and set a spend limit before you schedule the script.

## Step 1: Put buyer questions in `Prompts`

In the Sheet, create a tab named `Prompts`. Add these headers in row 1:

```text
Prompt | Intent | Target_Brand | Competitors | Target_Domain
```

Enter 20 to 40 complete questions, one per row. Use `Discovery`, `Comparison`, or `Evaluation` in `Intent`. In `Target_Brand`, separate the brand’s common name variants with commas. Do the same for three to five direct rivals in `Competitors`. Put your domain, without `https://`, in `Target_Domain`.

A buyer might ask what software mid-sized dental practices use to automate claims, then compare tools on integration speed and cost, then ask about the drawbacks of a specific vendor. Those are three different prompts. **Do not paste keyword fragments such as `b2b dental billing software` and assume they stand in for buyer questions.**

Expected result: each row gives the script a question, its intent, names to match, and a domain to check against cited links. The common mistake is quietly rewriting prompts after a bad week. Keep the panel stable; otherwise, as our guide to [measuring brand visibility](https://groas.com/post/how-to-measure-your-brand-s-visibility-i-2) explains, you lose the baseline you meant to track.

## Step 2: Keep keys out of the grid

Get your keys from OpenAI’s `platform.openai.com` API keys page, Perplexity’s `perplexity.ai/settings/api`, and Google AI Studio’s `aistudio.google.com` **Get API key** control. Confirm the accounts can make billed requests before testing the monitor.

In the Sheet, open **Extensions > Apps Script**. In the editor, open **Project Settings > Script Properties > Add script property** and add:

```text
OPENAI_API_KEY
PERPLEXITY_API_KEY
GEMINI_API_KEY
```

Paste each key into its corresponding property value. Expected result: the script can retrieve keys through `PropertiesService` without reading them from a cell. **Do not put keys in the Sheet**, even briefly. A viewable cell is a poor place for a secret, and clearing it does not erase revision history.

## Step 3: Add the script and run one test

In **Extensions > Apps Script**, replace the contents of `Code.gs` with the code below. Save it. The three request functions stay separate because web retrieval and citations come back in different shapes: [Sonar provides a citations array](https://perplexity.mintlify.app/mintlify-assets), [Gemini provides grounding metadata](https://cloud.google.com/gemini-enterprise-agent-platform/docs), and [OpenAI’s web-search responses provide citation annotations](https://platform.openai.com/docs/guides/tools-web-search).

**The script limits each execution to nine calls or roughly four minutes**, then schedules a continuation if work remains. That matters because [Apps Script has a six-minute execution limit](https://developers.google.com/apps-script/guides/services/quotas). A single synchronous loop over 40 prompts, three engines, and three runs can stop halfway through.

```javascript
const PROPS = PropertiesService.getScriptProperties();
const ENGINES = ['OpenAI', 'Perplexity', 'Gemini'];

function request(url, key, payload, useQueryKey) {
  const res = UrlFetchApp.fetch(url, {
    method: 'post',
    contentType: 'application/json',
    headers: useQueryKey ? {} : { Authorization: 'Bearer ' + key },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  });
  if (res.getResponseCode() < 200 || res.getResponseCode() >= 300) {
    throw new Error('HTTP ' + res.getResponseCode() + ': ' +
      res.getContentText().slice(0, 200));
  }
  return JSON.parse(res.getContentText());
}

function callEngine(engine, prompt) {
  if (engine === 'Perplexity') {
    const data = request('https://api.perplexity.ai/chat/completions',
      PROPS.getProperty('PERPLEXITY_API_KEY'),
      { model: 'sonar', messages: [{ role: 'user', content: prompt }] });
    return {
      text: data.choices?.[0]?.message?.content || '',
      urls: data.citations || []
    };
  }
  if (engine === 'Gemini') {
    const key = PROPS.getProperty('GEMINI_API_KEY');
    const url = 'https://generativelanguage.googleapis.com/v1beta/models/' +
      'gemini-2.5-flash:generateContent?key=' + encodeURIComponent(key);
    const data = request(url, key, {
      contents: [{ parts: [{ text: prompt }] }],
      tools: [{ googleSearch: {} }]
    }, true);
    const candidate = data.candidates?.[0];
    return {
      text: (candidate?.content?.parts || [])
        .map(part => part.text || '').join('\n'),
      urls: (candidate?.groundingMetadata?.groundingChunks || [])
        .map(chunk => chunk.web?.uri).filter(Boolean)
    };
  }
  const data = request('https://api.openai.com/v1/responses',
    PROPS.getProperty('OPENAI_API_KEY'), {
      model: 'gpt-4o-mini',
      tools: [{ type: 'web_search_preview' }],
      input: prompt
    });
  const blocks = (data.output || []).flatMap(item => item.content || []);
  return {
    text: blocks.map(block => block.text || '').join('\n'),
    urls: blocks.flatMap(block => block.annotations || [])
      .map(annotation => annotation.url).filter(Boolean)
  };
}

function named(text, names) {
  return names.split(',').map(s => s.trim()).filter(Boolean).some(name => {
    const safe = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    return new RegExp('(^|[^a-z0-9])' + safe +
      '(?=$|[^a-z0-9])', 'i').test(text);
  });
}

function cited(urls, domain) {
  const safe = domain.trim().toLowerCase()
    .replace(/^https?:\/\//, '').replace(/\/.*$/, '')
    .replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  if (!safe) return false;
  return urls.some(url => new RegExp(
    '^https?:\\/\\/(?:[^/]+\\.)?' + safe + '(?=[:/?#]|$)', 'i'
  ).test(url));
}

function resultsSheet() {
  const book = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = book.getSheetByName('Raw_Results') ||
    book.insertSheet('Raw_Results');
  if (sheet.getLastRow() === 0) sheet.appendRow([
    'Date', 'Run_Index', 'Engine', 'Prompt', 'Intent',
    'Brand_Mentioned', 'Domain_Cited', 'Competitor_Mentions',
    'Citation_URLs', 'Status'
  ]);
  return sheet;
}

function clearContinuationTriggers() {
  ScriptApp.getProjectTriggers()
    .filter(t => t.getHandlerFunction() === 'continueMonitor')
    .forEach(t => ScriptApp.deleteTrigger(t));
}

function runWeeklyMonitor() {
  clearContinuationTriggers();
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Prompts');
  if (!sheet || sheet.getLastRow() < 2) throw new Error('Fill Prompts first.');
  PROPS.setProperties({
    MONITOR_CURSOR: '0',
    MONITOR_DATE: new Date().toISOString()
  });
  continueMonitor();
}

function continueMonitor() {
  clearContinuationTriggers();
  const lock = LockService.getScriptLock();
  if (!lock.tryLock(1000)) return;
  try {
    const book = SpreadsheetApp.getActiveSpreadsheet();
    const input = book.getSheetByName('Prompts');
    const rows = input.getRange(2, 1, input.getLastRow() - 1, 5)
      .getValues().filter(row => row[0]);
    const output = resultsSheet();
    const total = rows.length * ENGINES.length * 3;
    let cursor = Number(PROPS.getProperty('MONITOR_CURSOR') || 0);
    const started = Date.now();
    let calls = 0;
    while (cursor < total && calls < 9 && Date.now() - started < 240000) {
      const promptIndex = Math.floor(cursor / 9);
      const engineIndex = Math.floor((cursor % 9) / 3);
      const runIndex = cursor % 3 + 1;
      const [prompt, intent, brand, competitors, domain] = rows[promptIndex];
      const engine = ENGINES[engineIndex];
      let mention = '', domainCited = '', rivalNames = '', urls = [];
      let status = 'OK';
      try {
        const answer = callEngine(engine, String(prompt));
        if (!answer.text) throw new Error('Empty answer');
        urls = answer.urls;
        mention = Number(named(answer.text, String(brand)));
        domainCited = Number(cited(urls, String(domain)));
        rivalNames = String(competitors).split(',').map(s => s.trim())
          .filter(name => name && named(answer.text, name)).join(', ');
      } catch (error) {
        status = 'ERROR: ' + String(error.message).slice(0, 180);
      }
      output.appendRow([
        new Date(PROPS.getProperty('MONITOR_DATE')), runIndex, engine,
        prompt, intent, mention, domainCited, rivalNames,
        urls.join(' | '), status
      ]);
      cursor++;
      calls++;
      PROPS.setProperty('MONITOR_CURSOR', String(cursor));
      if (cursor < total) Utilities.sleep(500);
    }
    if (cursor < total) {
      ScriptApp.newTrigger('continueMonitor').timeBased()
        .after(60 * 1000).create();
    }
  } finally {
    lock.releaseLock();
  }
}
```

Select `runWeeklyMonitor` in the editor and click **Run**. Approve the requested permissions. Expected result: a `Raw_Results` tab appears with up to nine rows, followed by continuation runs until the prompt list is complete. Each row has a `Status`; **an API error leaves the mention fields blank rather than recording a false zero.** The common mistake is pressing **Run** again while continuations are pending. That restarts the weekly job, so inspect the rows and triggers first.

The `500`-millisecond pause is spacing, not a guarantee against provider rate limits. If you see `ERROR` rows, check the provider response, key, billing, and limits before interpreting any rate. Gemini grounding links may also use an intermediary URL; a domain that is not visible in the returned URL cannot be counted as your domain by this simple parser. Keep those URLs for inspection rather than calling them citations to your site.

## Step 4: Schedule Monday and calculate a rate

In Apps Script, open **Triggers > Add Trigger**. Choose `runWeeklyMonitor`, then `Time-driven`, `Week timer`, `Every Monday`, and `5am to 6am`. Save. Expected result: Monday starts a fresh job, and the script’s temporary continuation triggers finish it in batches. The mistake is scheduling `continueMonitor` as the weekly trigger: it is a worker, not the job starter.

Create a tab named `Trend_Summary`. In cell `A1`, paste this formula:

```text
=QUERY(Raw_Results!A:J,"SELECT D, C, COUNT(F), AVG(F) WHERE J = 'OK' AND A >= date '"&TEXT(TODAY()-30,"yyyy-mm-dd")&"' GROUP BY D, C LABEL COUNT(F) 'Successful Runs', AVG(F) '30-Day Mention Rate'",1)
```

Format the rate column as a percentage. Expected result: one row per prompt and engine, with a count of successful calls and the share that mentioned your brand. Since `Brand_Mentioned` stores `1` or `0`, its average is the mention rate. **Read the count beside the percentage.** A 100% rate from one successful call is not the same observation as three out of three; an `ERROR` row should not enter either number.

For a single week’s three successful runs, the possible rates are 0%, about 33%, about 67%, and 100%. Do not call three out of three _model consensus_. It is a stronger signal than one out of one, not proof that the next buyer will see the same answer.

## Step 5: Read mentions and citations separately

Open `Raw_Results` and compare `Brand_Mentioned`, `Domain_Cited`, `Competitor_Mentions`, and `Citation_URLs` for the prompts that matter commercially. **A named brand and a cited domain are different events.** The model can name you while linking to a review site or a competitor’s comparison page. It can cite your documentation to answer a factual question without recommending your product. Neither case should be relabelled to make the chart look better.

Filter for prompts where rivals appear and you do not, then inspect the returned URLs and the answer itself. The sheet stores names and links, not the full wording of each recommendation; rerun important prompts manually when context matters. The common mistake is treating a competitor mention as a win for the competitor without checking whether the answer praised it, criticised it, or merely listed it.

## Step 6: Check what the sheet cannot see

Before these numbers enter a leadership deck, make one pass through this checklist. It is the difference between a useful trend and a precise-looking mistake.

1. **Compare API and consumer answers:** Run five prompts in the consumer interfaces alongside the script; note differences rather than treating the API output as the screen a buyer saw.
2. Check repeat counts: confirm three successful runs per prompt and engine before reading a weekly rate.
3. **Keep mentions apart from citations:** inspect the text and links behind a few `1` values in each column.
4. Check recommendation context: a mention can be a warning, an alternative, or a recommendation.
5. Check Google AI Overviews separately: Gemini with grounding is not the Google Search results page.
6. Check competitor context: a rival named beside you may be framed as the stronger choice.

**Recommendation context is the item people skip most.** A high mention rate that mostly records negative or passing references can send months of content work in the wrong direction.

## Step 7: Verify the job, then change the first thing that matters

After the continuations finish, verify that `Raw_Results` contains three `OK` rows for each prompt and engine, with no unexplained gaps. Check a handful of `Citation_URLs` against the returned answers, compare five prompts with consumer interfaces, and confirm that `Trend_Summary` excludes errors. If the totals do not match, fix the job or the inputs before presenting a trend. **The first change after a clean run is the weakest high-intent prompt, not the chart.** Keep its wording fixed and inspect why the answer names a rival, cites someone else, or leaves your brand out.

This is where the sheet stops. It tells you what to investigate; it does not repair a page, change site structure, or earn a citation. If your team has the time to turn those findings into work, keep the cheap monitor. If the findings only become a growing task backlog, [groas earned search](https://groas.com/earned-search) is built for the execution side: specialised models work continuously within commercial guardrails, with a human strategist responsible for direction and accountability. Run the monitor first. Then change what the answers reveal.

## Frequently Asked Questions

### Does this spreadsheet show exactly what buyers see when they ask ChatGPT about my brand?

No. The monitor sends buyer-style questions to APIs from OpenAI, Perplexity, and Gemini and records what those APIs return, so the output is a directional monitor rather than a transcript of what every buyer sees. API answers can differ from consumer answers, which is why the checklist recommends running five prompts in the consumer interfaces alongside the script.

### Should I paste keyword phrases like 'b2b dental billing software' into the Prompts tab?

No. Enter 20 to 40 complete questions, one per row, using Discovery, Comparison, or Evaluation as the intent. Keyword fragments do not stand in for buyer questions; for example, a dental practice might ask what software mid-sized practices use, compare tools on integration speed and cost, and ask about a specific vendor's drawbacks as three separate prompts.

### Where should I store the OpenAI, Perplexity, and Gemini API keys for this monitor?

Store each key as a script property in Apps Script under Project Settings > Script Properties, named OPENAI_API_KEY, PERPLEXITY_API_KEY, and GEMINI_API_KEY. Do not put keys in the Sheet, even briefly, because a viewable cell is a poor place for a secret and clearing it does not erase revision history. The script reads them through PropertiesService.

### Why does the script only make nine calls per run instead of processing all prompts at once?

Apps Script has a six-minute execution limit, and a single synchronous loop over 40 prompts, three engines, and three runs could stop halfway through. The script limits each execution to nine calls or roughly four minutes, then schedules a continuation trigger that resumes the work one minute later until the prompt list is complete.

### What happens to the results when an API call fails during a run?

The row is saved with an ERROR status and the mention fields are left blank rather than recorded as a zero, so failed calls never look like a real 'brand not mentioned' observation. ERROR rows are also excluded from the Trend_Summary rate, which counts only rows where the Status is OK.

### Which function should I set as the weekly trigger, runWeeklyMonitor or continueMonitor?

Schedule runWeeklyMonitor. In Apps Script under Triggers, choose runWeeklyMonitor, Time-driven, Week timer, Every Monday, 5am to 6am. continueMonitor is a worker that the script schedules itself as a temporary continuation trigger, so making it the weekly trigger restarts the job incorrectly. Also avoid pressing Run again while continuations are pending.

### If a prompt shows a 100% mention rate, can I trust that number?

Read the count of successful calls beside the percentage. A 100% rate from one successful call is not the same observation as three out of three, and with a single week's three runs the possible rates are only 0%, about 33%, about 67%, and 100%. Three out of three is a stronger signal than one out of one, but not proof the next buyer sees the same answer.

### What is the difference between a brand mention and a domain citation in the results?

A named brand and a cited domain are different events. A model can name your brand while linking to a review site or a competitor's comparison page, or cite your documentation to answer a factual question without recommending your product. Neither case should be relabelled to make the chart look better, so inspect the returned text and URLs behind each value.

## Related Posts

- [![Clay figure pours a wheelbarrow of articles into a wall funnel whose pipe is disconnected, so the pages pile on the floor beside an unused wrench.](https://groas.com/media/blog/e1a99963f159a4756e614472ee73400643c5900fa7bde718abe8fa563e8cdc77.png)](https://groas.com/post/can-chatgpt-even-read-your-site-a-45-min)
  ### [Can ChatGPT Read Your Site? A 45-Minute Check Before You Write More AEO Content](https://groas.com/post/can-chatgpt-even-read-your-site-a-45-min)
  [October 6, 2026 • 10 min read](https://groas.com/post/can-chatgpt-even-read-your-site-a-45-min)

  [Written by Alexander Perelman](https://groas.com/post/can-chatgpt-even-read-your-site-a-45-min)
- [![Cartoon of a marketer noting 'position two' after one pull of a slot machine whose reels are already spinning to a new result.](https://groas.com/media/blog/038bfefb4237637a1718ff66b379d5bdc42d57615f25797529aa5ef5682a4f7e.png)](https://groas.com/post/i-measured-ai-visibility-like-a-ppc-acco)
  ### [Six PPC Habits That Misread AI Visibility](https://groas.com/post/i-measured-ai-visibility-like-a-ppc-acco)
  [October 6, 2026 • 10 min read](https://groas.com/post/i-measured-ai-visibility-like-a-ppc-acco)

  [Written by David](https://groas.com/post/i-measured-ai-visibility-like-a-ppc-acco)
- [![A tiny worker on a ladder fixes up five model shopfronts while a giant dusty 'take a number' dispenser looms behind, its ticket queue ignored.](https://groas.com/media/blog/35c8ce6c06242ab08178711c5abe08dda0df51ec927a2dd6bc9770aa5f83bdbf.png)](https://groas.com/post/one-strategist-zero-developers-five-clie)
  ### [One Strategist, Zero Developers, Five Domains: A 30-Day AEO Launch Playbook](https://groas.com/post/one-strategist-zero-developers-five-clie)
  [October 6, 2026 • 10 min read](https://groas.com/post/one-strategist-zero-developers-five-clie)

  [Written by David](https://groas.com/post/one-strategist-zero-developers-five-clie)

## The Machines Already Run Search, You Should Probably Own One

[get started free](https://groas.typeform.com/to/xC1bQNUT)

## Structured data

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://graph.groas.com/entity/groas","name":"groas","url":"https://groas.com/","logo":{"@type":"ImageObject","url":"https://groas.com/icon-512.png","width":512,"height":512},"sameAs":["https://www.linkedin.com/company/groas/"]},{"@type":"WebSite","@id":"https://groas.com/#website","name":"groas","url":"https://groas.com/","inLanguage":"en-US","publisher":{"@id":"https://graph.groas.com/entity/groas"}},{"@type":"WebPage","@id":"https://groas.com/post/build-your-own-ai-visibility-monitor-in#webpage","url":"https://groas.com/post/build-your-own-ai-visibility-monitor-in","name":"Build a Weekly AI Brand Monitor with Google Sheets and Three APIs","description":"A brand-monitoring dashboard can tell you that ChatGPT mentioned a competitor instead of you. So can a spreadsheet and a script.","inLanguage":"en-US","isPartOf":{"@id":"https://groas.com/#website"},"about":{"@id":"https://graph.groas.com/entity/groas"},"primaryImageOfPage":{"@type":"ImageObject","url":"https://groas.com/media/blog/0294c5c19287ab7bc82dd6c269d57addd671dde0b94cb99a9fe6761ef751bfc7.png"},"breadcrumb":{"@id":"https://groas.com/post/build-your-own-ai-visibility-monitor-in#breadcrumb"},"dateModified":"2026-10-06T05:33:15.865Z","mainEntity":{"@id":"https://groas.com/post/build-your-own-ai-visibility-monitor-in#article"}},{"@type":"BreadcrumbList","@id":"https://groas.com/post/build-your-own-ai-visibility-monitor-in#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://groas.com/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://groas.com/blog"},{"@type":"ListItem","position":3,"name":"AI For Google Ads","item":"https://groas.com/category/ai-for-google-ads"},{"@type":"ListItem","position":4,"name":"Build a Weekly AI Brand Monitor with Google Sheets and Three APIs","item":"https://groas.com/post/build-your-own-ai-visibility-monitor-in"}]},{"@type":"BlogPosting","@id":"https://groas.com/post/build-your-own-ai-visibility-monitor-in#article","url":"https://groas.com/post/build-your-own-ai-visibility-monitor-in","mainEntityOfPage":{"@id":"https://groas.com/post/build-your-own-ai-visibility-monitor-in#webpage"},"isPartOf":{"@id":"https://groas.com/blog#blog"},"headline":"Build a Weekly AI Brand Monitor with Google Sheets and Three APIs","description":"A brand-monitoring dashboard can tell you that ChatGPT mentioned a competitor instead of you. So can a spreadsheet and a script. I spent years using that combination for Google Ads reporting, and the same approach gives a small business…","datePublished":"2026-10-06T05:33:15.410Z","dateModified":"2026-10-06T05:33:15.865Z","image":{"@type":"ImageObject","url":"https://groas.com/media/blog/0294c5c19287ab7bc82dd6c269d57addd671dde0b94cb99a9fe6761ef751bfc7.png"},"author":{"@id":"https://groas.com/author/david#person"},"publisher":{"@id":"https://graph.groas.com/entity/groas"},"articleSection":"AI For Google Ads","keywords":"Google Ads, AI For Google Ads","wordCount":2055,"inLanguage":"en-US"},{"@type":"Person","@id":"https://groas.com/author/david#person","name":"David","jobTitle":"Founder & CEO @ groas","image":"https://groas.com/media/blog/363be9ad5255816654dac065d01bf1c6063110b400bb38cc199570267232027c.jpg","worksFor":{"@id":"https://graph.groas.com/entity/groas"}},{"@type":"FAQPage","@id":"https://groas.com/post/build-your-own-ai-visibility-monitor-in#faq","mainEntity":[{"@type":"Question","name":"Does this spreadsheet show exactly what buyers see when they ask ChatGPT about my brand?","acceptedAnswer":{"@type":"Answer","text":"No. The monitor sends buyer-style questions to APIs from OpenAI, Perplexity, and Gemini and records what those APIs return, so the output is a directional monitor rather than a transcript of what every buyer sees. API answers can differ from consumer answers, which is why the checklist recommends running five prompts in the consumer interfaces alongside the script."}},{"@type":"Question","name":"Should I paste keyword phrases like 'b2b dental billing software' into the Prompts tab?","acceptedAnswer":{"@type":"Answer","text":"No. Enter 20 to 40 complete questions, one per row, using Discovery, Comparison, or Evaluation as the intent. Keyword fragments do not stand in for buyer questions; for example, a dental practice might ask what software mid-sized practices use, compare tools on integration speed and cost, and ask about a specific vendor's drawbacks as three separate prompts."}},{"@type":"Question","name":"Where should I store the OpenAI, Perplexity, and Gemini API keys for this monitor?","acceptedAnswer":{"@type":"Answer","text":"Store each key as a script property in Apps Script under Project Settings > Script Properties, named OPENAI_API_KEY, PERPLEXITY_API_KEY, and GEMINI_API_KEY. Do not put keys in the Sheet, even briefly, because a viewable cell is a poor place for a secret and clearing it does not erase revision history. The script reads them through PropertiesService."}},{"@type":"Question","name":"Why does the script only make nine calls per run instead of processing all prompts at once?","acceptedAnswer":{"@type":"Answer","text":"Apps Script has a six-minute execution limit, and a single synchronous loop over 40 prompts, three engines, and three runs could stop halfway through. The script limits each execution to nine calls or roughly four minutes, then schedules a continuation trigger that resumes the work one minute later until the prompt list is complete."}},{"@type":"Question","name":"What happens to the results when an API call fails during a run?","acceptedAnswer":{"@type":"Answer","text":"The row is saved with an ERROR status and the mention fields are left blank rather than recorded as a zero, so failed calls never look like a real 'brand not mentioned' observation. ERROR rows are also excluded from the Trend_Summary rate, which counts only rows where the Status is OK."}},{"@type":"Question","name":"Which function should I set as the weekly trigger, runWeeklyMonitor or continueMonitor?","acceptedAnswer":{"@type":"Answer","text":"Schedule runWeeklyMonitor. In Apps Script under Triggers, choose runWeeklyMonitor, Time-driven, Week timer, Every Monday, 5am to 6am. continueMonitor is a worker that the script schedules itself as a temporary continuation trigger, so making it the weekly trigger restarts the job incorrectly. Also avoid pressing Run again while continuations are pending."}},{"@type":"Question","name":"If a prompt shows a 100% mention rate, can I trust that number?","acceptedAnswer":{"@type":"Answer","text":"Read the count of successful calls beside the percentage. A 100% rate from one successful call is not the same observation as three out of three, and with a single week's three runs the possible rates are only 0%, about 33%, about 67%, and 100%. Three out of three is a stronger signal than one out of one, but not proof the next buyer sees the same answer."}},{"@type":"Question","name":"What is the difference between a brand mention and a domain citation in the results?","acceptedAnswer":{"@type":"Answer","text":"A named brand and a cited domain are different events. A model can name your brand while linking to a review site or a competitor's comparison page, or cite your documentation to answer a factual question without recommending your product. Neither case should be relabelled to make the chart look better, so inspect the returned text and URLs behind each value."}}]}]}
```
