Tutorial2026-07-216 min read

How to track competitor visibility in Microsoft Copilot

Tracking competitor visibility in Microsoft Copilot means running the questions your buyers actually ask and recording which rivals come back. Copilot runs a live Bing search before it answers, so most replies arrive with a citation list — which hands you something the other chatbots often withhold: the URL of the page each rival is winning on. One query is a browser tab at copilot.microsoft.com. A query set, every week, with the citation list intact and last month's runs to subtract from this month's, is one POST to https://api.agentgeo.org/v1/fetches and about thirty lines of code. This guide covers the manual sweep, the API version in curl and Python, the same job handed to an agent over MCP, and the five ways competitor data quietly goes wrong.

Tutorial

To track competitor visibility in Microsoft Copilot, run your category questions and record two things per rival: whether the answer names them in answerText, and whether their domain appears in sources[]. Do it once by hand to learn the shape of your category. Do it on a schedule with one API call per query, because the finding is never a single answer — it's which names entered and left the set since last time.

The manual way (and why it stops working)

Open copilot.microsoft.com — the consumer web assistant, not the one in your editor — and ask the question a buyer would ask: "best password manager for a remote-first company", not "tell me about Acme". Read the answer and write down every company it names. Then do the part that makes this engine worth checking at all: Copilot footnotes its reply with the Bing results it read, so note whose domains are cited, in order. Repeat across three or four phrasings and you'll know both the competitive set and the pages holding it up. It costs an hour and nothing else, and it's the best hour you'll spend on this.

The trouble starts on the second pass.

  • One question per tab. A category has twenty or thirty buying questions behind it, and Copilot's cited footnotes run long enough that reading each one properly is slow. You'll quietly narrow the sweep to the three queries you like, which is exactly how a blind spot forms.
  • Every ask is a fresh Bing search. Copilot retrieves before it writes, so the cited set genuinely moves between runs. One reading is a sample of something in motion, and it can't separate a real entrance from ordinary churn.
  • No history, so no diff. The interesting question is whether Globex has appeared in four more of your queries than it did in June. Answering it needs June stored somewhere, and a browser tab stores nothing.
  • Citations lose their order in transit. Copy a footnote list into a sheet and the positions scramble or the URLs vanish behind their titles. Cited at 1 and cited at 8 are different results, and position is the first casualty of copy-paste.
  • It never reaches a report. A hand-typed list of names isn't a chart in a client deck, a row in a table, or a job that pings you the week a new competitor enters the set.

Do it with one API call

AgentGEO fetches the answer and returns the raw record: the full text, plus every cited source with its title, URL and position. One POST through the managed-scraper engine, and the same envelope across all six engines — here it's just copilot.

curl -X POST https://api.agentgeo.org/v1/fetches \
  -H "Authorization: Bearer ag_live_..." \
  -H "Content-Type: application/json" \
  -d '{"query": "best password manager for a remote-first company", "surfaces": ["copilot"], "country": "US"}'

What comes back is a plain JSON envelope — no HTML, no scraping artifacts. Notice where the competitive intelligence actually sits: not only in the prose, but in whose pages Bing served up for the engine to write from.

{
  "id": "run_5f0a7c3d9e21",
  "query": "best password manager for a remote-first company",
  "surfaces": ["copilot"],
  "status": "completed",
  "recordsDelivered": 1,
  "creditsCharged": 1,
  "answers": [
    {
      "surfaceKey": "copilot",
      "status": "delivered",
      "answerText": "For a remote-first company Globex is the manager most often recommended for its SSO and passkey support, while Initech comes up for smaller teams that want simple admin controls without an enterprise contract ...",
      "sources": [
        { "title": "Globex Password Manager - business plans", "url": "https://globex.com/business", "position": 1 },
        { "title": "Globex vs Initech for distributed teams", "url": "https://globex.com/compare/initech", "position": 2 },
        { "title": "Best team password managers in 2026", "url": "https://example.com/best-password-managers", "position": 3 }
      ],
      "fetchedAt": "2026-07-21T09:14:22Z",
      "latencyMs": 12180
    }
  ]
}

The scan itself is short. Keep the competitive set in one dict — display name to root domain — and record both places a rival can turn up: named in the prose, and cited underneath it.

import requests
from collections import defaultdict
from urllib.parse import urlparse

KEY = "ag_live_your_key_here"

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

QUERIES = [
    "best password manager for a remote-first company",
    "team password manager with SSO and admin controls",
    "password manager with passkey support for a whole company",
]


def host(url):
    # Normalise so globex.com and www.globex.com are the same company.
    h = urlparse(url).netloc.lower()
    return h[4:] if h.startswith("www.") else h


named = defaultdict(list)   # brand -> queries whose prose named it
cited = defaultdict(list)   # brand -> (query, position, url) it was cited for

for query in QUERIES:
    resp = requests.post(
        "https://api.agentgeo.org/v1/fetches",
        # copilot honours country but not language and not web_search.
        json={"query": query, "surfaces": ["copilot"], "country": "US"},
        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"]:
        # A slow Bing scrape comes back failed at zero credits (see the gotchas).
        if answer.get("status") == "failed":
            print(f"{query!r}: no record this run")
            continue

        text = (answer.get("answerText") or "").lower()
        sources = answer.get("sources") or []

        for brand, domain in RIVALS.items():
            if brand.lower() in text:
                named[brand].append(query)
            for src in sources:
                h = host(src["url"])
                if h == domain or h.endswith("." + domain):
                    cited[brand].append((query, src["position"], src["url"]))

for brand in RIVALS:
    print(f"\n{brand}")
    print(f"  named in {len(named[brand])}/{len(QUERIES)} answers")
    if not cited[brand]:
        print("  cited: never")
        continue
    print(f"  cited {len(cited[brand])} time(s) - the pages doing the work:")
    for query, position, url in sorted(cited[brand], key=lambda row: row[1]):
        print(f"    #{position}  {url}   ({query})")

Run that over your query set and you get a per-rival summary: how many answers named them, and the exact pages that carried them there. Each delivered record costs one credit, and records that come back failed cost nothing — so a three-query sweep against one surface is three credits at most. While you're shaping the parser, an ag_test_ key returns clearly labelled demo records at zero credits.

The URLs are the point. Read the column and each entry tells you something different about how you're losing the query:

The page Copilot citedWhat it tells youThe move
globex.com/businessBing served a rival's own business page as the reference for what the category offers.Publish plan details a model can quote — seat tiers, SSO and passkey support in text, not behind a demo request.
globex.com/compare/initechA competitor wrote the head-to-head, and now owns how the whole category gets framed.Write the comparison they haven't: yours against theirs, on your own domain.
A third-party roundupNo vendor is winning this query — an editorial list is doing the work instead.Get into that roundup, or out-answer it with a page narrower and more specific than a list of ten.
A forum or community threadCopilot fell back on opinion because nobody published a real answer.The gap is written out for you in the thread's question. Answer it somewhere you control.
Your own domain, at position 6You're inside Bing's set but doing very little of the writing.A ranking problem, not a coverage one — compare your page against whatever sits at 1 to 3.

This is what makes competitor tracking on Copilot different in kind rather than just in volume. Because it runs a live Bing search before it writes, a delivered answer usually arrives with its evidence attached: you don't only learn that Globex owns the query, you often get the address of the page that won it. Those footnotes are, in effect, a Bing-ranked shortlist of the content beating you — in order, specific, and small enough to open and read this week. On a chatbot that only links when it happens to browse, you'd be guessing at brand strength; here you're usually reading the document.

Let your agent do it

Citations point at pages, and pages are something an agent can go and read — which is why this job belongs in an agent rather than a spreadsheet. Wire the server into Claude Code once:

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, with query and surfaces required and country defaulting to "US". After that the sweep is a sentence:

Run our four password-manager queries through Copilot. For each of Acme, Globex and Initech tell me whether the answer names them and whether their domain is cited. Then open the top-cited competitor page and tell me what it covers that ours doesn't.

The records arrive raw. Whether Globex turning up in two extra queries is a trend or a wobble is a judgment your agent makes with your context — AgentGEO ranks nothing, scores nothing and draws no conclusions, by design. If you'd rather script it, the Python and curl paths hit the identical endpoint.

Common problems

Five things that make competitor data read wrong, all fixable in the parser or the schedule:

  • Match domains in sources[], brand names in answerText. A third-party review titled "Globex vs Initech" matches a title search for both and was written by neither. Compare the host in url for citations, and use a word-boundary regex — re.search(r"\bglobex\b", text, re.I) — for mentions.
  • One run is a sample. Copilot runs a fresh Bing search on every ask, so the cited set moves on its own. Fetch the same query twice, or on two consecutive days, before you report that a rival entered or left the set.
  • Decide early how a domain cited twice counts. Copilot often footnotes several pages from one site. Counting them once per answer and counting each appearance are both defensible; switching convention mid-quarter silently rewrites every number you already reported.
  • This is the consumer Copilot, not the one in your editor. The surface is copilot.microsoft.com — built on OpenAI models and grounded in Bing — not GitHub Copilot or the M365 Copilot inside your Office tenant. They read different indexes, so don't fold their results into one column.
  • Long scrapes come back failed. A request waits up to 180 seconds; a scrape that runs past that budget returns status: "failed" at zero credits with a providerFields.snapshot_id. A follow-up call with that id and the same single surface redeems the finished answer instead of paying to scrape it again.

Summary

For a one-off read on who Copilot favours, ask it yourself — the footnotes are right there and it costs nothing. When the sweep has to cover a query set, repeat weekly, and produce something you can subtract last month from, fetch it instead: one call per query returns the prose and the ordered citation list, thirty lines of Python turn that into a per-rival grid, and the URLs in that grid are the real output — a short list of Bing-ranked pages you can go and beat.

See which rivals Copilot names, and open the exact pages Bing feeds it. Run your first sweep in a couple of minutes. Start free — no credit card

Start for free

Frequently asked questions

How do I see which competitors Microsoft Copilot recommends?
Ask Copilot the category question your buyers ask — "best password manager for a remote-first company" rather than a branded query — and write down every company it names, plus the domains in its cited footnotes. For repeatable tracking, POST the same query to https://api.agentgeo.org/v1/fetches with "surfaces": ["copilot"] and scan answerText for each rival's name and sources[] for each rival's domain.
Can I track competitor mentions in Copilot automatically?
Yes. One HTTP call returns the answer and its citation list as JSON, so a short script can loop your query set, test every competitor's name and domain, and write a grid to a table on a schedule. You can also connect the agentgeo-mcp server and have an AI agent run the same sweep from a plain-language instruction, with no parser to maintain.
Does Copilot show which pages it used for an answer?
Usually. Copilot grounds its replies in a live Bing search, so most delivered records carry a numbered citation list — denser and more reliable than Gemini, broadly comparable to Perplexity. AgentGEO returns those as a structured sources[] array with title, url and position on each entry, so you can see which competitor page fed the answer rather than only which competitor was named.
Is competitor tracking in Copilot different from Perplexity?
The scan is identical and both surfaces cite densely, so the workflow carries straight over. Copilot draws its citations from Bing while Perplexity runs its own retrieval, so the two can surface a different competitive set for the same question. The surfaces array takes one to six engine keys, so POST to https://api.agentgeo.org/v1/fetches with ["copilot", "perplexity"] and keep the citation columns in separate tables.
How much does it cost to track competitors across a query set?
One delivered record costs one credit, so a twenty-query sweep against a single engine is twenty credits, and failed records 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 labelled demo records at zero credits while you build the script.

Keep reading