# AI Answer Change Alert (`zinin/ai-answer-change-alert`) Actor

Track whether an AI assistant's answer to a query you care about changed since last time — new sources cited, old ones dropped, or the answer rewritten. Grounded via Perplexity Sonar, GPT or Gemini, no API key needed. A scheduled diff, not a one-shot snapshot.

- **URL**: https://apify.com/zinin/ai-answer-change-alert.md
- **Developed by:** [Tim Zinin](https://apify.com/zinin) (community)
- **Categories:** SEO tools, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## AI Answer Change Alert — Did the AI's Answer About Your Market Just Change?

Ask Perplexity or ChatGPT *"best crm for small business"* today, and it cites five sources.
Ask again next week and the wording might be rewritten, a competitor might get cited for the
first time, or a source that used to show up might be gone. This Actor remembers what the
answer looked like last time you ran it and tells you exactly what's different — new
domains cited, old ones dropped, or the answer simply rewritten — so you don't have to read
the whole thing again to notice.

### What you get

- **A diff, not a snapshot.** Every run compares against the answer this Actor saved for
  you the last time you checked the same query × model × language — not just a one-off
  citation list.
- **`changeType` tells you what kind of change it was:** `new-domains` (a source got cited
  that wasn't before), `lost-domains` (one dropped out), `answer-rewritten` (the wording
  changed but the cited domains stayed the same), or `none`.
- **Works out of the box — no API key needed.** LLM access is built in and covered by the
  per-row price. Prefer your own [OpenRouter key](https://openrouter.ai/keys)? Pass it and
  the tokens bill to your account at cost instead, nothing marked up.
- **Any language.** Set `lang` and the grounded model answers natively.
- **One row per check, in the normal case.** Every completed query × model check produces
  one dataset row — filter `changed` yourself (in the dataset view, via the API, or
  downstream) if you only want to see what moved on a scheduled run. On the rare occasion
  this Actor's own storage hiccups right after a billed check, a second, unbilled follow-up
  row can be delivered to warn you about it (see `baselineWriteError` below) — that row is
  never a duplicate charge, just a free warning tied to the check right before it.
- Runs on Apify: schedule it daily or weekly, monitor it, call it from the API or the MCP
  server, export to JSON/CSV/Excel, or push straight into your own pipeline.

### How to run it

1. Click **Try for free** — no card needed on the free plan.
2. Paste your queries into **Queries** and, optionally, extra models into **Models**. No
   API key needed.
3. Press **Start**. The first run for each query × model creates a baseline (`found: true`,
   `baselineCreated: true`) — there's nothing to compare against yet, so `changed` is
   `false`. Schedule the same input to run again later (daily/weekly) and every row after
   that compares against what was saved last time.

### Pricing

Pay-per-event: **$0.005 per GB of memory at run start + $0.05 per query × model check**.
Apify charges the start event once per GB of allocated memory (minimum one), so a default
4 GB run starts at $0.020. A "check" means a completed LLM call, whether the answer changed
or not — **the first run that creates a baseline for a query is billed exactly the same as
every run after it**, because the model was actually called and the citations were actually
parsed either way. The per-check price covers the grounded-search model call itself: you do
not need an OpenRouter account, and if you supply your own key the tokens go to your account
instead. 20 checks (e.g. 20 queries against 1 model) ≈ 0.020 + 20 × 0.05 = **$1.02**. The
most a single run can charge is the schema maximum, 20 queries × 3 models = 60 checks ≈
**$3.02**, no matter how large you set the fields.

Rows where the LLM call itself failed, or where this Actor's own baseline lookup failed
(`found: false`, with an `error`), are always returned for transparency and are **never**
charged — this Actor checks its own deployed pricing at the start of every run and refuses
to proceed (with a loud error, no paid work) if the platform ever charges anything for a
plain dataset write on top of the two named events above, so the "never charged" and the
"$1.81 maximum" claims above hold by construction, not by assumption.

Pass your own `openrouterApiKey` and the LLM tokens bill to your OpenRouter account instead,
at cost — the Actor's row price stays the same either way.

### Input

| Field | Required | What it does |
|---|---|---|
| `queries` | yes | Prompts a real buyer would ask. Up to 20 per run. Each is checked against every model in `models` and compared against its own stored baseline. |
| `models` | no | OpenRouter model IDs to check — grounded options: `perplexity/sonar`, `openai/gpt-4o-mini:online`, `google/gemini-2.0-flash-001:online`. Up to 3 per run. Default `["perplexity/sonar"]`. |
| `lang` | no | Answer language, e.g. `en`, `ru`, `tr`. Default `en`. |
| `openrouterApiKey` | no | Optional. Leave empty to use the built-in LLM access (included in the row price). Pass your own [OpenRouter key](https://openrouter.ai/keys) to have tokens billed to your account instead. |
| `maxConcurrency` | no | Parallel LLM calls, 1–3 (default 1), kept low to respect rate limits and to bound how many LLM calls can be wasted if a run's spend limit is hit mid-run. |

```json
{
    "queries": ["best crm for small business"],
    "models": ["perplexity/sonar"],
    "lang": "en"
}
```

### Output

One row per `query × model` pair, compared against the baseline this Actor saved last time.
Both rows below are real — the first is what a first-ever check for this query looks like,
the second is the same query checked again minutes later, where the model rewrote its
answer without changing which domains it cited:

**First check — no baseline existed yet:**

```json
{
    "query": "best crm for small business",
    "model": "perplexity/sonar",
    "lang": "en",
    "found": true,
    "changed": false,
    "baselineCreated": true,
    "changeType": "none",
    "citedDomains": ["fayedigital.com", "fitsmallbusiness.com", "pcmag.com", "techradar.com", "zapier.com"],
    "addedDomains": [],
    "removedDomains": [],
    "answerSnippet": "The **best CRM for a small business** depends on what you need most, but **Bigin by Zoho CRM** is ranked **best overall** by PCMag for 2026, while **HubSpot CRM** is also widely recommended as a strong all-around option for small teams.[1][4]\n\nIf you want the quickest recommendat",
    "previousCheckedAt": null,
    "baselineWriteError": null,
    "summary": "perplexity/sonar on \"best crm for small business\": first check — baseline created, 5 domain(s) cited.",
    "checkedAt": "2026-07-28T19:20:15.000Z"
}
```

**Same query, checked again — answer rewritten, same 5 domains still cited (`citedDomains` is always sorted, so an identical set always serializes identically):**

```json
{
    "query": "best crm for small business",
    "model": "perplexity/sonar",
    "lang": "en",
    "found": true,
    "changed": true,
    "baselineCreated": false,
    "changeType": "answer-rewritten",
    "citedDomains": ["fayedigital.com", "fitsmallbusiness.com", "pcmag.com", "techradar.com", "zapier.com"],
    "addedDomains": [],
    "removedDomains": [],
    "answerSnippet": "The **best CRM for a small business depends on your priority**, but the strongest all-around picks in current reviews are **HubSpot CRM** and **Bigin by Zoho CRM**.[1][4] If you want the simplest answer: choose **HubSpot CRM** for the best overall small-business experience, or **",
    "previousCheckedAt": "2026-07-28T19:20:15.000Z",
    "baselineWriteError": null,
    "summary": "perplexity/sonar on \"best crm for small business\": answer changed (answer-rewritten) — +0/-0 domain(s) vs 2026-07-28T19:20:15.000Z.",
    "checkedAt": "2026-07-28T19:24:46.330Z"
}
```

| Field | What it means |
|---|---|
| `query` / `model` / `lang` | Echo of the input for this row |
| `found` | Whether the LLM check succeeded |
| `changed` | Whether the answer differs from the stored baseline |
| `baselineCreated` | `true` the first time this query × model × lang is checked — nothing to compare against yet |
| `changeType` | `none`, `new-domains`, `lost-domains`, or `answer-rewritten` |
| `citedDomains` | The domains this run's answer is grounded in |
| `addedDomains` / `removedDomains` | Domains that appeared / disappeared versus the stored baseline |
| `answerSnippet` | First 280 characters of the model's current answer |
| `previousCheckedAt` | Timestamp of the run this one was compared against, or `null` on a first check |
| `summary` | Human-readable one-line summary of the change |
| `baselineWriteError` | Normally `null`. On a rare storage hiccup right after a billed check, saving the new baseline for next time can fail — this is retried a few times automatically before being treated as a real failure. The originally billed row is always delivered as usual (never withheld over this); if saving still fails after retries, a **second, unbilled follow-up row** with this field populated is delivered right after, warning that the next run's comparison may repeat (and re-charge for) the same change until the baseline saves successfully |
| `found: false` | The LLM call failed, or this Actor's own lookup of your stored baseline failed — same shape with an `error` field, no charge either way. A baseline-lookup failure is deliberately never treated as "no baseline yet": if it were, and this check then created a *fresh* baseline over a real one that this Actor simply couldn't read at that moment, whatever change your real baseline was tracking would be lost permanently the next time it compared successfully. |

#### Need the rest of the picture?

These run on the same account, take the same shape of input and bill the same way, so they slot into an existing pipeline without new plumbing.

| Actor | What it does |
|---|---|
| [AI Crawler Access Checker](https://apify.com/zinin/ai-crawler-access-checker) | Check which AI crawlers (GPTBot, ClaudeBot, PerplexityBot, Google-Extended & more) can access your website |
| [AI Overview Citation Tracker](https://apify.com/zinin/ai-overview-tracker) | For each query that matters, see which sources and domains AI assistants cite in their answer — grounded… |
| [B2B Lead Enricher](https://apify.com/zinin/b2b-lead-enricher) | Turn a list of company websites into sales-qualified lead cards: detected tech stack, a rough revenue… |
| [Chotot Vietnam Listings Scraper](https://apify.com/zinin/chotot-listings) | Pull live classified ad listings (real estate, vehicles, electronics, jobs and more) straight from… |
| [Clinical Trials Monitor](https://apify.com/zinin/clinical-trials-monitor) | Watch conditions, drugs or sponsors for new and updated clinical trials |

### FAQ

**Do I need an API key?** No — it works out of the box, LLM access is built in and covered
by the row price. Passing your own OpenRouter key is optional.

**Why was I charged for a run where nothing changed?** The Actor still had to call the
model and parse its citations to know nothing changed — that work is what you're paying
for, not just the alert. This applies to the very first run too (it creates the baseline).

**What this is NOT.** It is not a general web-search monitor — it only tracks what a
*grounded LLM* answers and cites for the exact query you give it, which can itself shift
run to run for reasons unrelated to the underlying sources (the model rephrasing itself).
It does not trust an ungrounded model's citations either: an ungrounded model will simply
produce a thinner `citedDomains` list scraped from its own text.

**Known limitation: overlapping runs on the same queries can still double-bill, rarely.**
Comparisons against the stored baseline are fully serialized only *within* a single run.
Across two overlapping runs (a schedule landing on top of a manual run, or two runs started
close together) this Actor takes out a short best-effort lease per query × model × language
before checking it, and skips (for free, no LLM call) any pair another run's lease still
covers — this closes the common case, and that skipped check costs nothing. But Apify's
key-value store has no atomic compare-and-swap, so if two runs check the lease at close
enough to the same instant, BOTH can find it clear and both proceed — there is no
storage-level fix for that available here. When this Actor detects an existing lease for a
pair (even a stale one from a crashed run) it flags the resulting row with
`concurrentRunSeen: true` in the dataset (not just the raw JSON), so a near-miss is visible.
The tightest possible race — two checks starting at almost the exact same moment, neither
ever seeing the other's lease — leaves no trace on either row; only a duplicate charge and
two identical `changed: true` rows for the same query/model/lang would reveal it. Running
one schedule per query set at a time avoids the situation entirely.

**Why did my run FAIL instead of complete?** In the rare case where this Actor cannot even
manage to warn you about a storage problem after retrying (see `baselineWriteError` above),
it deliberately crashes the run instead of quietly reporting success — a check you were
billed for must never disappear into a log line nobody reads. If you see a FAILED run, check
the run log; retrying the schedule normally resolves it once the underlying storage hiccup
clears.

Found a wrong result, or need a model we don't list? Open an issue on this Actor's page.

***

Built by [zinin](https://apify.com/zinin). Questions? Telegram [@timzinin](https://t.me/timzinin).

# Actor input Schema

## `queries` (type: `array`):

The prompts a real user would ask (e.g. `best crm for small business`). Each query is run against every model in `models` below and compared against the previous run's stored answer for the same query × model × language — you are charged per query × model pair, not per query, whether or not the answer changed.

## `models` (type: `array`):

OpenRouter model IDs to check. Grounded options: `perplexity/sonar`, `openai/gpt-4o-mini:online`, `google/gemini-2.0-flash-001:online`. Every model you add multiplies the number of billed rows: rows = number of queries × number of models. With the limits on both fields, the maximum possible is 20 queries × 3 models = 60 rows.

## `lang` (type: `string`):

Answer language (e.g. en, ru, tr).

## `openrouterApiKey` (type: `string`):

Your OpenRouter API key (https://openrouter.ai/keys). Optional: without it the built-in key is used and the LLM cost is covered by the per-row price. Pass your own key to have tokens billed to your account instead, at cost.

## `maxConcurrency` (type: `integer`):

Parallel LLM calls (kept low to respect rate limits and to bound how many LLM calls can be wasted — uncharged — if a run's spend limit is hit mid-run).

## Actor input object example

```json
{
  "queries": [
    "best crm for small business"
  ],
  "models": [
    "perplexity/sonar"
  ],
  "lang": "en",
  "maxConcurrency": 1
}
```

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "queries": [
        "best crm for small business"
    ],
    "models": [
        "perplexity/sonar"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("zinin/ai-answer-change-alert").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "queries": ["best crm for small business"],
    "models": ["perplexity/sonar"],
}

# Run the Actor and wait for it to finish
run = client.actor("zinin/ai-answer-change-alert").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "queries": [
    "best crm for small business"
  ],
  "models": [
    "perplexity/sonar"
  ]
}' |
apify call zinin/ai-answer-change-alert --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=zinin/ai-answer-change-alert",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/0QHCP20eS8nAf0PnO/builds/2IhqacgX6Y10IczcD/openapi.json
