Tutorial2026-08-126 min read

How to track competitor visibility in ChatGPT

Tracking competitor visibility in ChatGPT means asking the question your buyers ask and recording which brands come back. You can do it by hand — open ChatGPT, type "best CRM for small business", note who gets listed. To do it every week, across a query set, with citations attached, POST the query to https://api.agentgeo.org/v1/fetches and scan answerText and sources[] for every name on your list. The unit worth tracking isn't your own brand. It's the set — the handful of companies ChatGPT treats as the answer to your category question. This guide covers the manual check, the one-call version in curl and Python, the same job handed to an agent over MCP, and the traps that make competitor data read wrong.

Tutorial

To track competitor visibility in ChatGPT, ask the model your category question and record which brands come back — then repeat it on a schedule so you can watch the set change. Doing that once takes thirty seconds in the ChatGPT window. Doing it every week, across twenty queries, with citations attached and history you can diff, takes one API call and about twenty lines of code.

The manual way (and why it stops working)

Start here, honestly — it costs nothing and it teaches you what these answers actually look like. Open ChatGPT and ask the question a buyer would ask ("best CRM for small business", not "tell me about Acme"). Read the answer end to end and write down every company it names, in the order it names them. Then ask the obvious follow-up — "what are the alternatives?" — and note which names are new. Do that across three or four phrasings of the same intent and you have a rough picture of the competitive set ChatGPT holds for your category.

That picture is real and worth having. It just doesn't survive contact with a reporting cadence:

  • One query at a time. A category has twenty or thirty buying questions behind it. Typing each one and transcribing the names is an afternoon — an afternoon you have to spend again next month.
  • The answer moves. Ask the same question twice and you often get a different shortlist: different order, a name dropped, a new one added. A single reading can't tell you which change is real.
  • No history to diff. The chat window shows you today. The interesting question — has Globex started appearing in three more of our queries than it did in June? — needs June's answers stored somewhere.
  • Citations get lost. When ChatGPT browses, it links the pages it used. Copying those links out by hand, with their order intact, is tedious and the order is the first thing to go.
  • It can't go anywhere. A hand-typed list isn't a chart in a client report, a row in a table, or a job that pings Slack the week a new competitor enters the set.

Do it with one API call

AgentGEO returns the raw record behind that answer — the full text plus every source the engine cited, with positions. One POST, one JSON body, and the same shape across all six engines; here it's just chatgpt.

curl -X POST https://api.agentgeo.org/v1/fetches \
  -H "Authorization: Bearer ag_live_..." \
  -H "Content-Type: application/json" \
  -d '{"query": "best CRM for small business", "surfaces": ["chatgpt"]}'

What comes back is a plain envelope — no HTML, no scraping artifacts, and identical every run, which is what makes the scan easy to write:

{
  "id": "run_84c1f2ab91d3",
  "query": "best CRM for small business",
  "surfaces": ["chatgpt"],
  "status": "completed",
  "recordsDelivered": 1,
  "creditsCharged": 1,
  "answers": [
    {
      "surfaceKey": "chatgpt",
      "status": "delivered",
      "answerText": "For a small business the tools most often suggested are Globex, which leads on automation, and Acme, which is the fastest to set up ...",
      "sources": [
        { "title": "Best CRM software in 2026", "url": "https://example.com/best-crm", "position": 1 },
        { "title": "Globex CRM pricing", "url": "https://globex.com/pricing", "position": 2 }
      ],
      "fetchedAt": "2026-08-12T09:14:22Z",
      "latencyMs": 11840
    }
  ]
}

The scan is the whole job. Keep your competitive set in one dict — display name to root domain — and check both places a competitor can turn up: named in the prose, and cited as a source underneath it.

import requests
from urllib.parse import urlparse

KEY = "ag_live_your_key_here"
QUERY = "best CRM for small business"

# The competitive set you care about: display name -> root domain
BRANDS = {
    "Acme": "acme.com",
    "Globex": "globex.com",
    "Initech": "initech.com",
}


def host(url):
    # Normalise to a bare host so acme.com and www.acme.com match.
    h = urlparse(url).netloc.lower()
    return h[4:] if h.startswith("www.") else h


resp = requests.post(
    "https://api.agentgeo.org/v1/fetches",
    json={"query": QUERY, "surfaces": ["chatgpt"]},
    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":
        print("no record this run — retry later")
        continue

    text = (answer.get("answerText") or "").lower()
    hosts = {host(s["url"]) for s in answer.get("sources", [])}

    print(f"\n{answer['surfaceKey']} — {QUERY}")
    for brand, domain in BRANDS.items():
        named = brand.lower() in text
        cited = any(h == domain or h.endswith("." + domain) for h in hosts)
        print(f"  {brand:<10} named={str(named):<5} cited={cited}")

    missing = [b for b in BRANDS if b.lower() not in text]
    print(f"  not named at all: {', '.join(missing) or 'none'}")

Run it and you get a small grid: who ChatGPT named, who it cited, and the column that usually matters most — who is on your list and appears in neither. Wrap the whole thing in a loop over your query set and you have a weekly competitor report. Each delivered record costs one credit; records that come back failed cost nothing.

Checking by handOne API call
Queries per passAs many as you'll typeYour whole query set, in a loop
Competitor setWhoever you remember to look forAn explicit list, checked identically every run
CitationsCopied by hand, order usually lostsources[] with title, URL and position
HistoryScreenshots in a folderStored runs you can subtract from each other
Where it ends upA doc someone reads onceA report, a table, a cron job, an alert

Track the set, not your own row. Whether ChatGPT named Acme on a given Tuesday is close to a coin flip and you can't act on it. Whether the set of brands it returns across your twenty category queries has drifted over six weeks is a trend, and it points at something you can fix. Store every run and treat a name entering or leaving the set as the event worth reporting — that's the signal a single snapshot can never give you.

Let your agent do it

The same endpoint is exposed over MCP, so an agent that already knows your competitors can run the sweep itself — no parser to write. One command wires it into Claude Code:

claude mcp add agentgeo -- npx -y agentgeo-mcp --key ag_live_...

agentgeo-mcp speaks MCP over stdio, ships with zero npm dependencies and needs Node.js 18+. It exposes exactly one tool, fetch_raw_answers: query and surfaces are required, country defaults to "US" and language to "en". After that you just describe the check:

  • "Fetch what ChatGPT answers for 'best CRM for small business' and list every company it names, in the order it names them."
  • "Run our five category queries through ChatGPT and tell me which of Acme, Globex and Initech show up in each — named, cited, or neither."
  • "Compare today's competitor set against the one in geo-log.md and tell me who entered and who dropped out."

The records arrive raw. Whether Globex appearing in two more queries actually matters is a judgment your agent makes with your context — AgentGEO computes no scores, ranks nothing and draws no conclusions, by design.

Common problems

Most bad competitor data comes from five places. All five are fixable in the parser or the schedule.

  • Brand names collide with English. A substring test for a short name fires on unrelated words and on other companies that share a prefix. Match on a word boundary — re.search(r"\bacme\b", text, re.I) — and keep an explicit list of the collisions you already know about.
  • One run is a sample, not a reading. Chatbot answers vary between identical calls. Before you report that a competitor vanished, fetch the same query two or three times, or on two consecutive days, and see whether it holds.
  • Phrasing changes the set. "Best CRM for small business" and "top CRM tools for small teams" can return different companies. Freeze a query set that mirrors how your buyers actually phrase it, and change it deliberately rather than casually — a query edit invalidates the comparison to last month.
  • Named in the text is not cited in the sources. A competitor in answerText with nothing matching in sources[] usually means the model knows them without reading them today. Track the two columns separately; they call for completely different responses.
  • Some chatbot fetches run long. A request waits up to 180 seconds, and a scrape that exceeds that budget comes back with status: "failed" at zero credits and a providerFields.snapshot_id. A follow-up call with that id and the same single surface redeems the finished answer rather than paying to scrape it again.

Summary

For a one-off gut check, open ChatGPT and read the answer — it's free and it's fast. The moment the check has to repeat, cover a query set, or land in a report, hand it to the API or to an agent: one call returns the answer text and its sources, twenty lines of Python turn that into a competitor grid, and stored runs turn the grid into the only thing that really matters here — the trend.

See which competitors ChatGPT names for your category. [Start free — no credit card](/onboarding) and run your first sweep in a couple of minutes.

Start for free

Frequently asked questions

How do I see which competitors ChatGPT recommends?
Ask ChatGPT the category question your buyers ask — "best CRM for small business" rather than a branded query — and write down every company it names. For repeatable tracking, POST the same query to https://api.agentgeo.org/v1/fetches with "surfaces": ["chatgpt"] and scan the returned answerText for each competitor's name and sources[] for each competitor's domain.
Can you track competitor mentions in ChatGPT automatically?
Yes. One HTTP call returns the raw answer and its cited sources as JSON, so a short script can loop your query set, test each competitor's name and domain, and write the result to a table on a schedule. You can also connect the AgentGEO MCP server and have an AI agent run the sweep in plain language instead of writing the parser.
Why does ChatGPT name different competitors each time I ask?
Generated answers vary between runs, and the model may or may not browse on a given call, so the shortlist genuinely moves. That's why a single check is a sample rather than a measurement: fetch the same query repeatedly, store every run, and judge the trend across a query set instead of reacting to one answer.
Does ChatGPT show which websites it used for an answer?
When it browses, it links the pages it drew on. AgentGEO returns those as a structured sources[] array — each entry with title, url and position — so you can check whether a competitor's domain was actually cited rather than just mentioned in the prose. Perplexity is search-grounded and cites on essentially every answer, which makes it a denser citation surface.
How much does it cost to track competitors across a query set?
One delivered record costs one credit, so a twenty-query sweep against one engine is twenty credits; records that fail cost nothing. The free tier needs no credit card, paid usage is billed by consumption with a spend cap rather than per seat, and ag_test_ keys return clearly labelled demo records at zero credits while you build the script.

Keep reading