AnswerLine Start free

, updated , Tutorials · Copilot · Brand Monitoring

Track brand mentions in Copilot answers

To track brand mentions in Copilot answers, send a fixed set of buyer prompts to Microsoft Copilot on a schedule, per market, and store what comes back: the answer and the pages it cites. From each answer you read whether Copilot named your brand and whether it cited your domain, and how early. Over time that tells you whether and how a brand, page or competitor appears.

The fields are documented on the Copilot engine page. If you are comparing ways to collect Copilot answers rather than measuring them, see best Copilot scrapers. For Microsoft’s own developer products (Microsoft 365 Copilot, Copilot Studio, Foundry), see Microsoft Copilot API options.

Why Copilot needs its own measurement

Microsoft’s own documentation says Copilot is grounded in web search results for information-seeking conversations and “provides hyperlinked citations following generated text responses” (Transparency Note for Copilot, checked 2026-09-17). The response below returns those citations as fields.

Copilot’s answers are grounded in Bing rather than Google Search, so the cited pages and recommended products can differ from ChatGPT, Perplexity or Gemini on the same prompt. Whether they differ for your category is an empirical question.

The endpoint and request fields

POST https://api.answerline.dev/v1/monitor/copilot, authenticated with Authorization: Bearer <key> (authentication).

Field Required What it does
prompt yes The prompt sent to Copilot (up to 2,000 characters).
country yes Any country code, for the localized answer. GET /v1/countries?model=copilot lists supported codes.
state no A US state code; implies country US. Copilot answers for the whole US (states).
include.markdown no Adds result.markdown.
include.rawResponse no Adds result.rawResponse, the answer as normalized stream events; include.rawWire returns null on Copilot.

Both include flags default to false. include.html is not available on Copilot: a request that sets it is refused with a 400 and not charged.

Step 1: make a synchronous call

curl

curl -X POST https://api.answerline.dev/v1/monitor/copilot \
  -H "Authorization: Bearer $ANSWERLINE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "best accounting software for freelancers",
    "country": "US",
    "include": { "markdown": true }
  }'

Python (requests)

import os
import requests

API = "https://api.answerline.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['ANSWERLINE_API_KEY']}"}

resp = requests.post(
    f"{API}/v1/monitor/copilot",
    headers=HEADERS,
    json={"prompt": "best accounting software for freelancers", "country": "US", "include": {"markdown": True}},
    timeout=660,  # a synchronous call can wait up to ten minutes
)
resp.raise_for_status()
result = resp.json()["result"]

print(result["text"][:300])
for source in result.get("sources", []):
    print(source["position"], source["url"], source.get("label"))
print("charged:", resp.headers.get("X-Credits-Charged"))

TypeScript (@answerline/sdk)

import { Client } from "@answerline/sdk";

interface CopilotResult {
  text?: string;
  markdown?: string;
  sources?: { position: number; url: string; label?: string; description?: string }[];
  citationPills?: { citationPillId: number; position: number; url: string; domain: string }[];
}

const client = new Client({ apiKey: process.env.ANSWERLINE_API_KEY! });
const { result } = await client.monitor.copilot<CopilotResult>({
  prompt: "best accounting software for freelancers",
  country: "US",
});

for (const s of result.sources ?? []) console.log(s.position, s.url);

The TypeScript SDK is on npm as @answerline/sdk; the Python example calls the HTTP endpoint with requests.

Step 2: read every field in the response

The body is { "success": true, "result": { ... } }. The fields under result:

Field Type Meaning
text string Copilot’s answer as plain text.
markdown string The answer in Markdown, with include.markdown.
links[] array The links in the answer text, in order.
sources[] array Cited sources: position, url, label, description.
citationPills[] array Inline citation chips: one entry per chip and source, with citationPillId, position (into sources), url and domain.
rawResponse[] array The answer as normalized stream events, with include.rawResponse. result.rawWire (with include.rawWire) is null on Copilot.

Copilot results do not carry the searches Copilot ran, product cards or map results. For product and local prompts, measure mentions in text and the pages in sources. The cited pages are described on Copilot sources.

rawResponse

rawResponse follows Copilot’s own format, which can change without notice. Use it for debugging, not as a data source for metrics.

Step 3: turn a response into brand metrics

Three numbers per prompt and market cover most reporting:

  1. Mentioned: your brand name or an alias appears in text.
  2. Cited: a URL on your domain appears in sources, and at which position.
  3. In a chip: your domain sits behind one of the inline citationPills.
import re
from urllib.parse import urlsplit

def on_domain(url: str, domain: str) -> bool:
    host = (urlsplit(url).hostname or "").lower().removeprefix("www.")
    return host == domain or host.endswith("." + domain)

def copilot_metrics(result: dict, aliases: list[str], domain: str) -> dict:
    brand = re.compile(r"\b(" + "|".join(re.escape(a) for a in aliases) + r")\b", re.IGNORECASE)
    cited = [s["position"] for s in result.get("sources", []) if on_domain(s["url"], domain)]
    return {
        "mentioned": bool(brand.search(result.get("text", ""))),
        "best_source_position": min(cited) if cited else None,
        "in_chip": any(on_domain(p["url"], domain) for p in result.get("citationPills", [])),
    }

Roll these up into a mention rate, a citation rate and an average best position across the prompt set. Mentions and citations are separate events with separate fixes; mentions vs citations explains why to track both. For share-of-voice formulas, see the AI share of voice framework, and for how many prompts a rate needs, how to measure brand mentions.

Step 4: schedule it with async tasks

For anything recurring, submit tasks instead of holding synchronous connections. A task takes the same body as payload, with taskType: "COPILOT":

curl -X POST https://api.answerline.dev/v1/async/task/batch \
  -H "Authorization: Bearer $ANSWERLINE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "taskType": "COPILOT",
      "payload": { "prompt": "best accounting software for freelancers", "country": "US" },
      "idempotencyKey": "copilot-accounting-freelancers-US-2026-11-11",
      "webhook": { "url": "https://example.com/hooks/answers" }
    },
    {
      "taskType": "COPILOT",
      "payload": { "prompt": "best accounting software for freelancers", "country": "GB" },
      "idempotencyKey": "copilot-accounting-freelancers-GB-2026-11-11",
      "webhook": { "url": "https://example.com/hooks/answers" }
    }
  ]'
import hashlib

def copilot_tasks(prompts: list[str], countries: list[str], day: str, hook: str) -> list[dict]:
    tasks = []
    for prompt in prompts:
        slug = hashlib.sha1(prompt.encode()).hexdigest()[:12]
        for country in countries:
            tasks.append({
                "taskType": "COPILOT",
                "payload": {"prompt": prompt, "country": country},
                "idempotencyKey": f"copilot-{slug}-{country}-{day}",
                "webhook": {"url": hook},
            })
    return tasks

tasks = copilot_tasks(prompts, ["US", "GB"], "2026-11-11", "https://example.com/hooks/answers")
for start in range(0, len(tasks), 500):
    batch = requests.post(f"{API}/v1/async/task/batch", headers=HEADERS, json=tasks[start:start + 500], timeout=660).json()
    for item in batch["results"]:
        if not item["success"]:
            print(tasks[start + item["index"]]["idempotencyKey"], item["error"]["code"])

A batch holds 1 to 500 tasks, and each item succeeds or fails on its own (VALIDATION_ERROR, RESOURCE_ALREADY_EXISTS or INSUFFICIENT_CREDITS). The webhook body is the same JSON as GET /v1/async/task/{id}, with the Copilot result under response.result. Verify the Webhook-Signature header before storing anything (verify webhook signatures), and dedupe by task.id, since deliveries can repeat or arrive out of order.

The key is derived from prompt, market and day, so re-sending the same submission after a timeout never creates a task twice; idempotency keys explained covers the pattern.

What it costs

Per billing, at the Starter tier:

Request Price
Copilot request $0.00274
include.markdown or include.rawResponse +0

A worked example: 50 prompts in 2 countries is 50 x 2 x $0.00274 = $0.274 per run, or $1.10 a month at a weekly cadence. A new account’s 100 free requests cover one run; after that you top up your balance (see pricing). For budgeting across engines and cadences, see estimating your monitoring bill.

A task reserves its maximum cost in dollars (credits.creditsToCharge) and is charged the actual cost (credits.creditsCharged) only on success. Failed tasks are not charged.

Sync or async: decision criteria

Situation Use
A person is waiting for the answer (a dashboard “check now” button, debugging a prompt) Synchronous call
A scheduled prompt set, any size Async batch with a webhook
Fewer than a handful of tasks, no public endpoint to receive webhooks Async tasks and polling GET /v1/async/task/{id}

The trade-offs in more depth are in sync calls, async tasks or webhooks.

Pitfalls

Where Copilot fits in a monitoring program

Run the same prompt set on Copilot, ChatGPT and Gemini, then compare citation and mention rates engine by engine. A brand that is cited on one engine and absent on another points to a retrieval-layer gap you can investigate through the sources of each. Prompt design matters more than engine count; start with designing a prompt set, and see the AI visibility tracking and competitor analysis use cases.

Field list and a live request example are on the Copilot engine page; the quickstart gets you to a first call.

Questions

How do I track brand mentions in Copilot?

Run a fixed set of buyer prompts through Copilot on a schedule, per market, and store each answer. For every answer record whether the text names your brand and whether sources cites your domain, and at what position. Report those rates across runs, not from a single answer.

Which fields does a Copilot result carry?

text, markdown (with include.markdown), links, sources and citationPills. Copilot results do not carry the searches it ran, shopping cards or map results, so for commerce prompts measure brand mentions in text and the sources cited.

Can I build metrics on Copilot's rawResponse?

Use it for debugging only. rawResponse follows Copilot's own format, which can change without notice, so build metrics from the typed fields instead.

Does the response include Copilot's inline citation chips?

Yes. result.citationPills lists them; group entries by citationPillId to rebuild a chip, and each entry's position points into result.sources, which lists every page the answer cites, in order.

Should I use synchronous calls or async tasks for Copilot?

Use synchronous calls for interactive checks and debugging. Use async tasks for scheduled prompt sets: they queue with no count limit (about 100,000 typical tasks per account) and can be submitted 500 at a time.

Try it on your own prompts

100 free requests on any engine, no card. One POST returns the answer, sources and citations as JSON.

Keep reading