How to Build a GEO Monitoring Tool With AI APIs

A Python script that sends buyer questions to Claude and Perplexity through their APIs with web search, records brand and competitor mentions and cited URLs, and saves the results for trend tracking.

You can automate part of your AI visibility tracking by sending your buyer questions to AI models through their APIs with web search enabled, recording whether your brand and competitors are mentioned, and saving the URLs each answer cites. Anthropic’s API offers a web search tool for Claude, and Perplexity’s API returns answers with citations. One important limit: API answers are not identical to what people see in the consumer apps, which may use different models, settings and personalisation. Treat API results as a consistent trend signal, and keep occasional manual checks in the apps.

What the tool does

  1. QuestionsA fixed list of buyer questions.
  2. RunsEach question sent several times to each API.
  3. ParseBrand and competitor mentions, cited URLs.
  4. SaveOne CSV row per run, per day.
  5. ReportShare of voice and top cited pages over time.

API results versus app results

API Consumer app
Model The one you choose Whatever the app uses, which can change
Personalisation and memory None unless you add it May use the user’s history and settings
Location As configured The user’s location
Consistency Repeatable settings Varies by user and session

That is why the API is good for trends and competitor comparisons, and manual app checks are still needed to see what customers see. The manual method is in tracking brand mentions for free.

The script

It uses Anthropic’s Messages API with the web search tool, and Perplexity’s chat completions API. Set API keys and model names as environment variables, using each provider’s current model list.

#!/usr/bin/env python3
"""Run buyer questions through AI APIs with web search and record mentions and cited URLs.
Needs: pip install anthropic requests. Set ANTHROPIC_API_KEY, PERPLEXITY_API_KEY,
CLAUDE_MODEL and PERPLEXITY_MODEL (see each provider's current model list)."""
import csv, datetime, os, re, time
import anthropic, requests

BRAND = r"\bbrilad\b"                       # regex for your brand name and variants
COMPETITORS = {"Competitor A": r"\bcompetitor a\b", "Competitor B": r"\bcompetitor b\b"}
QUESTIONS = [
    "Which agencies in India offer generative engine optimisation?",
    "How can a B2B company get cited by ChatGPT?",
]
RUNS = 3

def ask_claude(question):
    client = anthropic.Anthropic()
    msg = client.messages.create(
        model=os.environ["CLAUDE_MODEL"], max_tokens=1024,
        tools=[{"type": "web_search_20250305", "name": "web_search", "max_uses": 5}],
        messages=[{"role": "user", "content": question}],
    )
    text, urls = [], set()
    for block in msg.content:
        if block.type == "text":
            text.append(block.text)
            for c in (getattr(block, "citations", None) or []):
                if getattr(c, "url", None):
                    urls.add(c.url)
    return " ".join(text), sorted(urls)

def ask_perplexity(question):
    r = requests.post(
        "https://api.perplexity.ai/chat/completions",
        headers={"Authorization": f"Bearer {os.environ['PERPLEXITY_API_KEY']}"},
        json={"model": os.environ["PERPLEXITY_MODEL"],
              "messages": [{"role": "user", "content": question}]},
        timeout=60,
    )
    r.raise_for_status()
    data = r.json()
    text = data["choices"][0]["message"]["content"]
    urls = data.get("citations") or [s.get("url") for s in data.get("search_results", []) if s.get("url")]
    return text, sorted(set(urls))

def main():
    today = datetime.date.today().isoformat()
    with open(f"geo_runs_{today}.csv", "w", newline="", encoding="utf-8") as f:
        w = csv.writer(f)
        w.writerow(["date", "assistant", "question", "run", "brand_mentioned", "competitors", "cited_urls"])
        for name, ask in [("Claude API", ask_claude), ("Perplexity API", ask_perplexity)]:
            for q in QUESTIONS:
                for run in range(1, RUNS + 1):
                    text, urls = ask(q)
                    low = text.lower()
                    comps = [c for c, rx in COMPETITORS.items() if re.search(rx, low)]
                    w.writerow([today, name, q, run, int(bool(re.search(BRAND, low))), "; ".join(comps), " ".join(urls)])
                    time.sleep(1)

if __name__ == "__main__":
    main()

API response formats change over time. If a field is missing, check the provider’s current documentation and adjust the parsing. Other providers that offer web search in their APIs can be added as further functions in the same pattern.

Running it regularly

  • Schedule it weekly or monthly with cron or a CI scheduler, and store the CSV files together.
  • Keep the question list fixed so trends are comparable; add new questions as a separate group.
  • Load the CSVs into a sheet or dashboard; see the Looker Studio dashboard guide.

Costs and limits

  • Each run uses model tokens and, for web search, per-search charges. Check current pricing; 20 questions, 3 runs and 2 providers is 120 calls per cycle.
  • Respect rate limits; the script pauses between calls.
  • Store API keys in environment variables or a secrets manager, never in the code.

Reading the results

  • Share of voice per question group and provider, month by month
  • Which of your URLs are cited, and for which questions
  • Which third-party domains are cited alongside competitors, for listing and PR opportunities

See competitor analysis in AI answers for how to act on them.

Frequently asked questions

Can we track ChatGPT this way?

OpenAI’s API offers web search for its models, but API results differ from the ChatGPT app. Add it as another function if useful, and keep checking the app manually.

Is a commercial AI visibility tool better?

Commercial tools add dashboards, more providers and larger question sets. This script suits teams that want control and transparency at low cost.

How many runs per question?

At least three, since answers vary. More runs give steadier numbers at higher cost.

Sources

  1. Anthropic API documentation, web search tool
  2. Perplexity API documentation, chat completions and citations