AI Brand Watch
Everything you need to run, schedule and consume Brand Watch — as a human or as an agent.
Quickstart
- Open AI Brand Watch on the Apify Store (free Apify account; free plan credits cover a trial run).
- Enter your brand name. That's the minimum — Desvela discovers the category questions, competitors and your domain, and freezes them as a prompt set. To go fully manual, override with your own
queries,competitorsanddomain. - Run it once — this audit run establishes your baseline (no alerts yet, by design). Every later run reuses the same frozen prompt set, so diffs are comparable.
- Add an Apify Schedule (e.g. weekly). From then on you only hear about changes.
Connect via MCP
Run Brand Watch from Claude, Cursor or any MCP client — your agent starts watches and reads results directly. Endpoint: https://mcp.apify.com/?tools=desvela/brand-watch, authenticated with your Apify API token (runs bill to your Apify account, pay per check). Also listed in the official MCP registry as dev.desvela/brand-watch.
Claude Code
claude mcp add brand-watch \ --transport http https://mcp.apify.com/?tools=desvela/brand-watch \ --header "Authorization: Bearer YOUR_APIFY_TOKEN"
Cursor (.cursor/mcp.json)
{
"mcpServers": {
"brand-watch": {
"url": "https://mcp.apify.com/?tools=desvela/brand-watch",
"headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }
}
}
}
Claude Desktop (claude_desktop_config.json — via mcp-remote)
{
"mcpServers": {
"brand-watch": {
"command": "npx",
"args": ["-y", "mcp-remote",
"https://mcp.apify.com/?tools=desvela/brand-watch",
"--header", "Authorization: Bearer YOUR_APIFY_TOKEN"]
}
}
}
Then ask your agent: "run a brand watch for <brand> and summarize its mention rate across engines" — or, fully manual, "with these 10 category questions on openai+perplexity, 3 samples each, and summarize the mention rate".
Input schema
Only brandName is required. Omit queries to enter zero-config mode: the actor auto-discovers a category prompt set (and reuses a frozen one from a previous run when present). Supply queries to go custom — the historical behaviour is unchanged.
| Field | Type | Notes |
|---|---|---|
brandName | string · required | The brand to track. |
domain | string | Your own top-level domain (e.g. desvela.ai). Enables owned vs earned citation rates and matches the domain as a mention. |
brandAliases | string[] | Domain, alternate spellings — anything the engines may call you. |
competitors | string[] | Tracked alongside your brand; new entrants trigger alerts. |
competitorDomains | object | Competitor domains keyed by name (e.g. {"Coda": "coda.io"}). Measures competitorCitationRate; null without it, never 0. |
queries | string[] · max 30 | Optional. Without it the actor runs zero-config (auto-discovery of a prompt set) or reuses the frozen set from a previous run. With it, used verbatim (custom mode). |
autoGenerateQueries | boolean · default false | Only with given queries: discovered queries are added and combined (dedupe, max 30). Ignored in zero-config mode. |
engines | subset of openai, perplexity, anthropic, gemini | Default: openai, perplexity. |
samplesPerQuery | integer 1–5 · default 3 | Mention rate is computed across samples. |
webhookUrl | string | POST endpoint for signed change events. |
notifyOnlyOnChange | boolean · default true | If false, the webhook also fires on no-change runs. |
includeRecommendations | boolean · default false | Generate 3–5 concrete GEO actions from this run's data. Charged as one recommendation event, only when delivered. |
includeSentiment | boolean · default false | Opt-in paid add-on: a positive/neutral/negative label per measured cell. Descriptor only — never participates in diff or alerts. Charged per delivered cell as sentiment. |
promptSetAction | keep · renew · default keep | keep reuses the frozen prompt set (the WATCH mode). renew consciously regenerates it — that run sets a new baseline and emits no change events. |
Output & metrics
Every measured cell (query × engine) in the dataset carries schemaVersion: 2 and these fields:
| Metric | Meaning |
|---|---|
mentionRate | Fraction of samples (0–1) whose answer mentioned the brand. The canonical signal — shareOfVoice remains as a legacy alias with the same value. |
competitiveShareOfVoice | Brand mentions over samples where the brand or a watched competitor appears. Null when no competitor appears in any sample — never 0. |
avgPosition / recommendationRank | Textual order of first appearance vs explicit rank in a numbered recommendation list. prominenceScore (0–1) derives from the rank; null means "not measurable", not "low prominence". |
ownedCitationRate / earnedCitationRate | Fraction of samples citing your domain vs citing any non-own source (requires domain). Null — never 0 — when citations weren't available. |
competitorCitationRate + competitorRates | Fraction of samples citing each watched competitor's domain (requires competitorDomains). |
stableSources | Sources present in ≥50% of samples — the stable base for diffing, so a new source never fires from LLM jitter alone. |
sentiment | Dominant positive/neutral/negative label per cell, opt-in. Descriptor only — never alerts. |
evidence | Verbatim excerpt from the engine's answer per distinct sample — the part you can check yourself. |
Audit vs Watch
Each run has a runRole. The first run of a prompt set is an audit: it establishes the baseline silently — no alerts, by design. Every later run reuses the same frozen prompt set and is a watch: it diffs against that baseline and fires change events. A prompt set is only replaced by an explicit promptSetAction: renew, which is itself an audit run. Comparability is the point: if the questions changed between runs, the diff would mix "my visibility changed" with "what I ask changed".
Pricing events
| Event | Price | Charged when |
|---|---|---|
actor-start | $0.02 | Once per run. |
brand-query-checked | $0.08 | Per query × engine, all samples included. Never charged if all samples for that pair failed. |
recommendation | $0.04 | Opt-in via includeRecommendations. Charged only when the recommendations are actually generated and delivered. |
prompt-discovery | $0.04 | Zero-config mode only, when a prompt set is generated by the auto-discovery. |
sentiment | $0.04 | Opt-in via includeSentiment, per measured cell actually delivered. |
change-alert | free | Per change event detected. |
Example: 20 queries × 2 engines, weekly ≈ $14/month.
Webhook payload
One POST per run. The body is a self-contained, versioned envelope (schema v2): it carries the run, the frozen question set it was measured under and the verbatim evidence, so a consumer can understand a change without reopening the dataset. The event type is a mention-rate change — appeared · disappeared · sov_shift (≥25 points) · new_source · new_competitor. When a change fires, the payload also carries the context that explains it: reason, mention_rate (with delta), new_cited_source (a source the engine now cites in every sample), competitors_now_mentioned, evidence, samples and stableSources. Set-membership jitter never triggers an alert on its own. share_of_voice is kept as a legacy alias of mention_rate — consumers of the v1 contract keep working unchanged.
{
"schemaVersion": 2,
"runId": "wAT3x...",
"brand": "Acme CRM",
"promptSetId": "ps_a1b2",
"promptSetVersion": 4,
"generatedAt": "2026-08-08T10:05:00.000Z",
"events": [{
"event": "brand_visibility_changed",
"schemaVersion": 2,
"brand": "Acme CRM",
"engine": "perplexity",
"query": "best CRM for small business in Spain",
"promptSetId": "ps_a1b2",
"promptSetVersion": 4,
"runId": "wAT3x...",
"change": {
"type": "disappeared",
"reason": "mentionRate 0.67 → 0 (disappeared)",
"mention_rate": { "before": 0.67, "after": 0, "delta": -0.67 },
"share_of_voice": { "before": 0.67, "after": 0 },
"new_cited_source": "reddit.com/r/smallbusiness/..."
},
"evidence": ["Acme is absent from the engine's answer for this query"],
"samples": 3,
"stableSources": ["hubspot.com/...", "zoho.com/..."],
"checked_at": "2026-08-08T09:00:00Z",
"previous_checked_at": "2026-08-01T09:00:00Z"
}]
}
Headers: X-Desvela-Signature: hmac-sha256=<hex> (computed over the entire envelope body) and User-Agent: Desvela-BrandWatch/0.1 (+https://desvela.dev/bot).
Verify the signature
The secret is generated on your first run and stored with your watch state in the brand-watch-state key-value store in your Apify account (key STATE-*). Each run's SUMMARY record shows a hint of it.
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody, header, secret) {
const expected = 'hmac-sha256=' +
createHmac('sha256', secret).update(rawBody).digest('hex');
return timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}
Delivery & retries
- 3 attempts with exponential backoff (2s, 4s, 6s); 10s timeout per attempt.
- Redirects are refused — point us at the final URL.
- Any 2xx counts as delivered. Undelivered events remain in the run dataset — nothing is lost.
- No public endpoint? Skip
webhookUrland read the dataset on your agent's next run (pull mode).
FAQ
Why a mention rate instead of a single answer? AI answers vary between runs; single snapshots are noise. We sample N times and report the rate — the % of answers that mention you. Competitive share of voice and citation rates ride along on the same samples.
Do I need to provide the questions? No. Just a brand name: Desvela discovers the category questions, competitors and your domain, and freezes them as a prompt set. Every scheduled run measures the same frozen questions, so the diff is meaningful. You can override with your own list at any time.
Is the API answer identical to the consumer product? No — correlated, not pixel-identical. We always measure the same way, so diffs stay valid. Full note on the product page.
MCP? Available today via Apify's MCP gateway: point your client at https://mcp.apify.com/?tools=desvela/brand-watch with your Apify API token as Authorization: Bearer. Your agent can start runs and read results over MCP — runs are billed to your Apify account as usual. A native server is live for discovery: POST https://registry.desvela.dev/mcp exposes the free preflight(domain) tool (no auth), backed by our index of the top-100K.
x402? Live, through Apify. Brand Watch is eligible for agentic payments, so an agent can run it paying USDC on Base with no Apify account, no billing and no API key. Call POST https://api.apify.com/v2/acts/desvela~brand-watch/runs without credentials and you get 402 Payment Required with the terms in the payment-required header. The prepaid token has a $1 minimum and expires in 14 days, so spend it rather than topping up per run.