AnswerLine Start free

, Brand Monitoring · Grok · News

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

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:

Setup

1. Write the prompt set

Keep it short so it can run often:

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:

  1. 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.
  2. Human review queue: route every verdict-prompt answer whose new-source alert also fired to a person, with the previous and current text side by side.
  3. 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:

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:

  1. Read the new sources: who published, what they claim, whether the claim is accurate.
  2. 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.
  3. 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.
  4. 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

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.

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