Monitor what AI answers say about your competitors
Competitor AI answer monitoring is the practice of running the prompts your buyers ask through AI engines on a schedule and recording which competitors each answer recommends, which of their pages it cites, and when either changes. If a rival starts being described as the best option for enterprise teams, you want to know which prompts that happens on, in which engines and markets, and which page of theirs the engine relied on.
The competitive prompt set
Build the set around buying moments rather than your own brand. Four groups cover most categories:
| Group | Example prompts | What it tells you |
|---|---|---|
| Category | “best |
Who the engine considers the default shortlist |
| Comparison | “ |
How you are positioned head to head, and who is suggested when a buyer is leaving you |
| Problem | “how do I |
Which vendor gets recommended as the solution when no category is named |
| Evaluation | “is |
Tone and the objections the engine repeats |
Keep each prompt’s wording fixed and give it an ID; history is only comparable when the input is identical. Prompt set design covers coverage and size.
What to extract per run, by engine
Field availability differs by engine, and several fields are omitted from the response when empty or not requested, so read them with a default.
| Signal | Question it answers | ChatGPT | Perplexity | Gemini | Copilot | Grok | AI Mode |
|---|---|---|---|---|---|---|---|
| Brands named | Who is recommended, in what order? | entities[]; order from text |
match in text |
match in text |
match in text |
match in text |
match in text |
| Cited pages | Whose content feeds the answer? | sources[] |
sources[] |
sources[] |
sources[] |
sources[] |
sources[] |
| Searches run | Which searches did the engine run, and do you rank for them? | searchQueries[] (empty today) |
none | none | coming | searchQueries[] |
none |
| Product cards | Whose products appear with prices? | inlineProducts[] (shoppingCards[] empty today) |
none | none | coming | none | shoppingCards[] |
Definitions:
- Brands named: ChatGPT returns
entities[], the brands and products the answer names (items havetypeandname). On every engine, including ChatGPT, the order a reader sees is the order of first appearance intext, so compute order from the text. - Cited pages:
sources[]items carryurlandlabelon all six engines. Grok’s sources also include fields likesiteNameandcreator, and can include X posts. - Searches run: the query strings the engine issued while answering. On ChatGPT,
include.searchQueriesis free, but the list comes back empty on the ChatGPT answers served today. - Product cards: the item shapes differ. ChatGPT’s
inlineProducts[]items hold aproductrecord withtitle,priceandoffers[], each offer with amerchant_name; AI Mode cards havetitleandstoreat the top level. Copilot’s shopping cards are coming. Google Search responses also have ashoppingCards[]field.
The pipeline
- Define competitors and aliases. For each rival: brand name, product names, domain(s). Short or generic names need word-boundary matching and occasional manual review.
- Write the prompt set across the four groups above, per market.
- Choose engines and markets. ChatGPT, Gemini and AI Mode answer from 213 countries; Copilot, Perplexity and Grok take any country; Gemini also takes a US state.
- Submit runs as async batches with deterministic
idempotencyKeys and awebhook. One batch request holds up to 500 tasks. - Store the raw result of each completed task (
response.result) keyed by prompt, engine, market and run date. - Extract signals with per-engine field mapping.
- Diff each run against the previous one for the same prompt, engine and market, and alert only on transitions.
Submitting the matrix
curl -X POST https://api.answerline.dev/v1/async/task/batch \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '[
{
"taskType": "CHATGPT",
"idempotencyKey": "comp:c07:chatgpt:US:2026-09-21",
"webhook": { "url": "https://intel.example.com/hooks/answers" },
"payload": {
"prompt": "best invoicing software for freelancers",
"country": "US"
}
},
{
"taskType": "GEMINI",
"idempotencyKey": "comp:c07:gemini:US:2026-09-21",
"webhook": { "url": "https://intel.example.com/hooks/answers" },
"payload": { "prompt": "best invoicing software for freelancers", "country": "US" }
}
]'
Cost for this one prompt at the Starter tier: ChatGPT $0.0006, Gemini $0.0006, so $0.0012 per run. A 60-prompt set run weekly (4 runs) on these two engines is 60 x 4 x $0.0012 = $0.288 a month. Prices are on the billing page.
Extracting signals per engine
import re
from urllib.parse import urlparse
COMPETITORS = {
"you": ["Ledgerly"],
"rival_a": ["InvoiceNinja Pro", "InvoicePro"],
"rival_b": ["BillFox"],
}
SEARCH_FIELD = {"GROK": "searchQueries"}
def brand_order(text):
firsts = {}
for brand, aliases in COMPETITORS.items():
hits = [m.start() for a in aliases for m in re.finditer(rf"\b{re.escape(a)}\b", text, re.IGNORECASE)]
if hits:
firsts[brand] = min(hits)
return [b for b, _ in sorted(firsts.items(), key=lambda kv: kv[1])]
def product_labels(engine, result):
if engine == "AIMODE": # AI Mode cards are flat product entries
return [f"{c.get('title')} @ {c.get('store')}" for c in result.get("shoppingCards", [])]
if engine == "CHATGPT": # inline products, one record each, offers per merchant
products = (i.get("product", {}) for i in result.get("inlineProducts", []))
return [f"{p.get('title')} @ {(p.get('offers') or [{}])[0].get('merchant_name')}" for p in products]
return []
def signals(engine, result):
text = result.get("text") or ""
return {
"order": brand_order(text),
"cited_domains": sorted({urlparse(s.get("url", "")).hostname or "" for s in result.get("sources", [])}),
"cited_urls": [s.get("url") for s in result.get("sources", [])],
"searches": result.get(SEARCH_FIELD[engine], []) if engine in SEARCH_FIELD else None,
"products": product_labels(engine, result),
}
None means the engine does not provide that signal; an empty list means it provides it and returned nothing. Keeping that distinction stops dashboards from showing “0 searches” for Gemini or Perplexity. ChatGPT is left out of SEARCH_FIELD because its searchQueries list comes back empty on the answers served today, and Copilot because its search queries are not returned yet.
Detecting transitions
Alert on changes in state for a fixed prompt, engine and market, not on mentions:
| Transition | Rule | Why it matters |
|---|---|---|
| Rival entered | rival in order now, not in previous run |
They won a buying moment |
| You left | you absent now, present before | Direct loss of visibility |
| Rival overtook you | rival’s index in order now lower than yours, previously higher |
Shortlist order changed |
| Citation displaced | one of your URLs left cited_urls and a rival-domain URL entered |
Their page replaced yours as evidence |
| New search | a string in searches not seen for this prompt before |
A new query to rank for |
Answers vary between runs even when nothing has changed in the world, so require a transition to persist across two consecutive runs before paging anyone, or compare rates over a window. Answer volatility covers how to set that threshold for your data.
def transitions(prev, curr):
events = []
for brand in COMPETITORS:
was, now = brand in prev["order"], brand in curr["order"]
if now and not was:
events.append(("entered", brand))
if was and not now:
events.append(("left", brand))
lost = set(prev["cited_urls"]) - set(curr["cited_urls"])
gained = set(curr["cited_urls"]) - set(prev["cited_urls"])
if lost or gained:
events.append(("citations_changed", sorted(lost), sorted(gained)))
if curr["searches"] is not None and prev["searches"] is not None:
new = set(curr["searches"]) - set(prev["searches"])
if new:
events.append(("new_searches", sorted(new)))
return events
Turning signals into a competitive view
Per-run events feed alerts; a monthly view needs rates. For each competitor, per engine and market:
- Presence rate: runs where the competitor is named, divided by completed runs.
- First-named rate: runs where the competitor appears first in
text, divided by runs where any tracked brand is named. - Citation share: URLs on the competitor’s domains divided by all cited URLs.
- Prompt wins: prompts where the competitor is named in most runs of the period and you are not.
Illustrative example: across 4 weekly runs of a 60-prompt set on Gemini (240 runs), a rival named in 132 runs has a presence rate of 132 / 240 = 55%. If some tracked brand is named in 200 of those runs and the rival is named first in 90, its first-named rate is 90 / 200 = 45%. These numbers are invented to show the arithmetic.
The prompt-wins list is the most useful output for a content team: each entry is a concrete question plus the pages the engine cited for the rival.
Cadence
- Core set weekly: enough points for a monthly trend without paying for noise.
- Launch or pricing-change set daily for a limited window, kept to the prompts naming the rival that launched.
- Markets on rotation if the budget is tight: all markets monthly, top markets weekly.
The trade-offs are in monitoring cadence.
Reading the counterplay
When a competitor starts winning a prompt, the cited URLs are the explanation you can act on. Open the rival page the engine cited and note:
- Format: comparison table, listicle, documentation page, pricing page, forum thread.
- The claim the answer repeated, often a single sentence the engine paraphrased.
- Freshness signals: a visible date, a current year in the title, recent pricing.
- Where it lives: their own domain, or a third-party review or community site.
That page is the brief. If it is on their domain, publish a page that answers the same question more completely and specifically. If it is third-party, the work is outreach or participation there. Then keep the prompt in the set and measure whether the citation moves over the next several runs. How AI engines choose citations and Reddit citations in AI answers go further.
Pitfalls
- Reading
entities[]for competitors on every engine. Only ChatGPT returns it, so the code would report no competitors elsewhere. Match text on the other engines. - Assuming one shopping schema.
inlineProductson ChatGPT andshoppingCardson AI Mode have different item fields. Map each explicitly. - Forgetting ChatGPT’s include flag. Without
include.shopping,shoppingCardsandinlineProductsare absent, not empty; with it,shoppingCardsis empty on the answers served today. - Alerting on single-run flips. Confirm across runs.
- Tracking only prompts that name competitors. Problem prompts show where a rival wins without being compared to anyone.
- Mixing markets. A rival dominant in the UK and absent in the US should not average to “sometimes present”.
The share-of-voice framework turns these signals into reportable numbers, and the competitor analysis use case covers the SERP side. Field details per engine are on the ChatGPT and Gemini engine pages.
Questions
What is competitor AI answer monitoring?
It is running a fixed set of buyer prompts through AI engines on a schedule and recording which competitors each answer names, which of their pages it cites, and when that changes. The output is a per-prompt, per-engine, per-market history you can alert on.
Which engines return the brands named in an answer as structured data?
ChatGPT. Its entities array lists the brands and products an answer names, each with a type and name. On Gemini and the other engines, detect competitors by matching their names and aliases in the answer text or markdown.
Which engines return the searches they ran?
ChatGPT has a searchQueries field (include.searchQueries, free), but it comes back empty on the ChatGPT answers served today. Grok returns searchQueries: its web and X searches. Gemini and Perplexity return none. Copilot's searchQueries is coming.
Which engines return product cards with prices?
Google AI Mode (shoppingCards) and ChatGPT. With include.shopping, ChatGPT's shoppingCards comes back empty on the answers served today, and inlineProducts carries the products from its shopping search, with price, link and merchant offers. Copilot's shopping cards are coming. The product fields inside differ per engine, so map them separately.
How often should competitor prompts be run?
Weekly suits a core prompt set; increase to daily for a limited set around launches or pricing changes. Alert on transitions, such as a competitor entering an answer, rather than on every mention.