Brand crisis monitoring: catch it in AI answers first
A brand crisis can surface in AI answers and news results before it settles into the rankings people check first, and a monitor that watches those surfaces on a short cycle gives a comms team time to prepare. The setup is small: a handful of prompts and one news query, run every few hours as an async batch, with alerts on new sources and changed verdicts rather than on every mention.
No engine publishes how fast it picks up a new story, so measure that from your own history. The monitor below relies only on the fields each response returns.
Definitions
- Early-warning surface: a source that tends to reflect new information before the general web results a stakeholder searches.
- Verdict prompt: a question whose answer is a judgment about the brand, such as “is
trustworthy?” or “should I buy from ?”. - New source: a URL or domain cited in the current cycle’s answer that was not cited in any recent cycle for the same prompt and engine.
- Verdict change: a material difference in how a verdict prompt’s answer characterizes the brand compared with the previous cycle.
The surfaces to watch
| Surface | Task type | What to read | Signal |
|---|---|---|---|
| Google News | GOOGLE_NEWS |
newsResults[] (title, link, source, date, position) |
New articles and new publishers for the brand query |
| ChatGPT | CHATGPT |
sources[], text, entities[] |
Verdict wording; new cited pages; new named entities |
| Gemini | GEMINI |
sources[], text |
Verdict wording; new cited pages |
| Grok | GROK |
sources[], text, searchQueries[] (its web and X searches) |
Verdict wording; new cited pages, which can include X posts |
| Perplexity | PERPLEXITY |
sources[], text |
Verdict wording; new cited pages |
Two cautions:
- Google News returns a ranked list of articles. There is no story-cluster field, so “a story is building” has to be computed: more articles matching the brand query than last cycle, more distinct publishers, or articles with recent dates moving up in
position. entities[]is returned only by ChatGPT. On the other engines, look for people, products or incident terms intext.
Setup
1. Write the prompt set
Keep it short so it can run often:
- “what happened with
?” on ChatGPT - “is
trustworthy?” on Gemini (a verdict prompt) <brand>as the Google News query
Add a product name or executive name as a second news query if those are likely targets.
2. Submit each cycle as a batch
The batch body is a JSON array of tasks. Each task gets an idempotencyKey derived from brand, prompt, engine and the time window, and a webhook for delivery.
curl -X POST https://api.answerline.dev/v1/async/task/batch \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '[
{
"taskType": "GOOGLE_NEWS",
"priority": 10,
"idempotencyKey": "crisis:acme:news:google_news:US:2026-09-17T12",
"webhook": { "url": "https://comms.example.com/hooks/crisis" },
"payload": { "query": "Acme", "country": "US" }
},
{
"taskType": "GEMINI",
"priority": 10,
"idempotencyKey": "crisis:acme:trustworthy:gemini:US:2026-09-17T12",
"webhook": { "url": "https://comms.example.com/hooks/crisis" },
"payload": { "prompt": "is Acme trustworthy?", "country": "US" }
},
{
"taskType": "CHATGPT",
"priority": 10,
"idempotencyKey": "crisis:acme:happened:chatgpt:US:2026-09-17T12",
"webhook": { "url": "https://comms.example.com/hooks/crisis" },
"payload": { "prompt": "what happened with Acme?", "country": "US" }
}
]'
priority runs from 1 to 10 and orders tasks only within your own queue, so crisis tasks start ahead of large weekly monitoring batches on the same account. If the scheduler retries this request, tasks already created come back as RESOURCE_ALREADY_EXISTS per item and nothing is duplicated. See idempotency keys explained.
3. Receive and verify results
Each finished task is POSTed to the webhook with the same JSON as GET /v1/async/task/{taskId}. Verify the Webhook-Signature header over the raw body before parsing (how), respond 2xx within 15 seconds, and do the analysis after acknowledging. Deliveries can repeat and arrive out of order, so dedupe by task.id.
4. Diff against recent cycles
from urllib.parse import urlparse
def domain(url):
host = urlparse(url or "").hostname or ""
return host[4:] if host.startswith("www.") else host
def cycle_signals(stored_task):
task = stored_task["task"]
if task["status"] != "COMPLETED":
return None
result = stored_task["response"]["result"]
if task["taskType"] == "GOOGLE_NEWS":
items = result.get("newsResults", [])
return {
"urls": {i.get("link") for i in items},
"publishers": {i.get("source") for i in items if i.get("source")},
"text": None,
}
sources = result.get("sources", [])
return {
"urls": {s.get("url") for s in sources},
"publishers": {domain(s.get("url")) for s in sources},
"text": result.get("text") or "",
}
def alerts(history, current):
"""history: signals from the last N cycles for the same prompt, engine and market."""
seen_urls = set().union(*(h["urls"] for h in history)) if history else set()
seen_pubs = set().union(*(h["publishers"] for h in history)) if history else set()
out = []
new_urls = current["urls"] - seen_urls
new_pubs = current["publishers"] - seen_pubs
if new_urls:
out.append(("new_sources", sorted(u for u in new_urls if u)))
if new_pubs:
out.append(("new_publishers", sorted(new_pubs)))
return out
Comparing against the last several cycles rather than only the previous one reduces alerts from sources that drop in and out between runs.
Google News date is a display string (for example “2 hours ago”), not a timestamp. Store it as returned alongside the time your task completed, and compute article age from both if you need it.
5. Schedule the cycle
A scheduler builds the batch for the current window, checks the balance, and submits:
import os
from datetime import datetime, timezone
import requests
API = "https://api.answerline.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['API_KEY']}", "Content-Type": "application/json"}
HOOK = "https://comms.example.com/hooks/crisis"
CYCLE_USD = 0.0014 # dollars at Starter: GOOGLE_NEWS 0.0002 + GEMINI 0.0006 + CHATGPT 0.0006
WATCH = [
("GOOGLE_NEWS", "news", {"query": "Acme"}),
("GEMINI", "trustworthy", {"prompt": "is Acme trustworthy?"}),
("CHATGPT", "happened", {"prompt": "what happened with Acme?"}),
]
def run_cycle(country="US", hours=4):
now = datetime.now(timezone.utc)
window = now.replace(hour=now.hour - now.hour % hours, minute=0, second=0, microsecond=0)
stamp = window.strftime("%Y-%m-%dT%H")
balance = requests.get(f"{API}/v1/credits", headers=HEADERS, timeout=30).json()
if balance.get("remaining", 0) < CYCLE_USD: # remaining is in dollars
raise RuntimeError("balance too low for a full cycle")
tasks = [
{
"taskType": engine,
"priority": 10,
"idempotencyKey": f"crisis:acme:{name}:{engine.lower()}:{country}:{stamp}",
"webhook": {"url": HOOK},
"payload": {**payload, "country": country},
}
for engine, name, payload in WATCH
]
r = requests.post(f"{API}/v1/async/task/batch", json=tasks, headers=HEADERS, timeout=60)
r.raise_for_status()
for item in r.json().get("results", []):
code = (item.get("error") or {}).get("code")
if code and code != "RESOURCE_ALREADY_EXISTS":
print("task", item["index"], code)
Because the key is derived from the window, running the scheduler twice inside the same four hours is harmless: the second submission creates nothing. A RESOURCE_ALREADY_EXISTS item is expected on retry and is not an error. Retrying task creation after a 5xx or timeout is safe for the same reason; see API errors and retries.
6. Detect verdict changes
Verdict change is the hardest signal to automate. Three approaches, in increasing effort:
- Term watch: alert when the answer to a verdict prompt contains incident terms it did not contain last cycle (“lawsuit”, “recall”, “breach”, “outage”, “investigation”). Cheap and noisy; tune the list to your sector.
- Human review queue: route every verdict-prompt answer whose new-source alert also fired to a person, with the previous and current
textside by side. - Classifier: score each answer with your own sentiment or stance model and alert on a change in label across two consecutive cycles.
Treat any single-cycle flip with suspicion; answers vary between runs even when nothing has happened.
What it costs
Starter prices per request: Google News $0.0002, Gemini $0.0006, ChatGPT $0.0006. One cycle of the batch above:
- $0.0002 + $0.0006 + $0.0006 = $0.0014 per cycle
- Every 4 hours is 6 cycles a day: $0.0014 x 6 = $0.0084 a day
- A 30-day month: $0.0084 x 30 = $0.252, a 30-day spend in the Starter tier
Adding a second Google News query adds $0.0002 x 6 x 30 = $0.036 a month. A second page of Google News results is free. New accounts get 100 free requests, which cover 33 full cycles of three tasks, enough to trial the setup before topping up. Details on the billing page; tier prices on pricing.
During an active incident, shorten the cycle for the prompts that matter and leave the rest on the normal schedule, rather than tightening everything.
What comms does with the lead time
When an alert fires:
- Read the new sources: who published, what they claim, whether the claim is accurate.
- Check reach on the surface it came from. A single post cited in one answer is different from the same claim appearing in several Google News publishers.
- Prepare the response where it can be cited: a dated statement on your own site, an FAQ update, a correction request to the publisher if the claim is wrong.
- Keep the prompts running and watch whether your statement enters
sources[]on the engines, and whether the verdict prompt’s wording changes.
The same data documents the incident afterwards: which surfaces carried the story, in what order your monitor saw it, and when your response started being cited.
Pitfalls
- Assuming a speed you have not measured. Keep timestamps for when each surface first showed a new source, and use your own history to set expectations.
- Treating Google News as clustered. Build the story view from article lists across runs.
- Reading
entities[]outside ChatGPT. It is not returned by other engines. - Random idempotency keys. They do not prevent duplicate cycles on retry. Derive them from the time window.
- Paging on every negative word. Alert on new sources and persistent changes; review the rest in a queue.
- Monitoring only the brand name. Stories can start under a product name, an executive’s name or a misspelling.
Related: the adverse media screening, brand protection and news monitoring use cases, and Google News API.
Field details are on the ChatGPT and Google News engine pages.
Questions
Which surfaces should a brand crisis monitor watch?
A practical set is Google News for new articles, and ChatGPT and Gemini for how answers to questions like "is the brand trustworthy?" describe you. Add Grok, whose cited sources can include X posts, and Perplexity for the same verdict prompts.
Does Google News data include story clusters?
No. Google News results are a list of articles with position, title, link, snippet, source and date. To see a story building, compare the result sets across runs and count new articles and new publishers.
How much does a crisis monitoring cycle cost?
One ChatGPT, one Google News and one Gemini task cost $0.0006 + $0.0002 + $0.0006 = $0.0014 at the Starter tier. Running that every 4 hours for a 30-day month is $0.0014 x 6 x 30 = $0.252.
Why use async batches with idempotency keys for crisis monitoring?
A scheduler that retries a failed submission must not queue the same cycle twice. Keys derived from brand, prompt, engine and time window make a retry return RESOURCE_ALREADY_EXISTS for tasks already created instead of duplicating them.
What should trigger an alert?
A state change against the previous cycle: a cited URL or domain not seen before, a new Google News article or publisher for the brand query, or a change in how a verdict prompt answers. A single negative word in an answer is too noisy to page on.