AI Search Citation & Brand Mention Tracker avatar

AI Search Citation & Brand Mention Tracker

Pricing

from $20.00 / 1,000 observations

Go to Apify Store
AI Search Citation & Brand Mention Tracker

AI Search Citation & Brand Mention Tracker

Collect reproducible AI-search answers and citations for a fixed prompt set. Bring your own OpenAI, Perplexity, Gemini, or Anthropic key. Reports mention and citation evidence, share of voice, and change versus the previous run.

Pricing

from $20.00 / 1,000 observations

Rating

0.0

(0)

Developer

Olalekan Johnson

Olalekan Johnson

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

6 days ago

Last modified

Categories

Share

Collect the same prompts, on the engines you choose, and keep the evidence. Each row records the prompt version, the model that actually answered, the market you requested, the answer, the cited URLs, and whether your brand or a competitor was mentioned or cited.

This is for SEO and content agencies who already report AI-search visibility and need a repeatable sample they can drop into their own dashboard. It is not a rank tracker. API answers can differ from logged-in consumer apps. Counts describe the sample you collected. This Actor does not produce a ranking.

Unofficial, and not affiliated with OpenAI, Perplexity, Google, or Anthropic. You bring your own API keys and pay those providers yourself.

Contact: plusonecares@gmail.com

What you put in

A run is one fixed prompt set, one brand, optional competitors, one or more markets, and a repetition count.

InputRole
Brand, aliases, domainsText match for a mention. A domain match on a citation counts as cited.
CompetitorsSame rules. Used for share of voice.
PromptsThe exact wording. The set is hashed. Edit a prompt and later runs are a new version, not a quiet change in score.
Prompts CSV or https fileUsed only when the prompts list is empty. Private and local URLs are rejected.
MarketsCountry, city, region, timezone, language, and optional coordinates.
Repetitions1 to 5. Repeats are kept on purpose. One answer is not a share.
EnginesOpenAI, Perplexity, Gemini, Anthropic. A live engine runs only when its key is present.
Monitor idLeave blank to derive one, or set one so a schedule always compares against the same history.

With no API keys, the default input runs demo mode. It replays a bundled sample for the fictional brand Northstar Packs and the fictional competitors Trailbird and Kestrel Outdoor. Every row has demo: true. Demo mode does not charge the observation event.

Set demoMode to off when a missing key should fail the run instead of replaying the sample. on always replays the sample, even if keys are saved on the task.

What you get

The dataset is one flat row per prompt, engine, market, and repetition. The key-value record OUTPUT is the run summary: valid observations over attempts, share of voice, top cited domains, and the change versus the previous baseline.

A row is a valid observation when the engine returned a usable answer. These are reported separately and are not valid:

  • Failed collection — timeout, auth error, rate limit, safety block, or an unsupported model/tool.
  • No answer — the call succeeded and the answer was empty.

A valid answer can still be:

  • Answer without citations — text came back, no cited URL.
  • Answer without a brand mention — text and possibly citations, brand not in the text.
  • Both at once (answer_without_brand_mention_and_citations).

Those flags can overlap. The outcome field is one label so the outcome counts add up to the number of attempts. runValidLabel on every row is the run denominator, such as 6/8. Do not recompute a rate from a single exported row.

Share of voice is brand-mention observations divided by brand-mention observations plus competitor-mention observations, among valid answers only. If nobody you track was mentioned, share of voice is empty rather than 0%. Mention detection is a case-insensitive word match. It does not understand negation: “does not mention Northstar” still counts as a mention.

Sample observation

The default demo run returns 8 rows. This one is the first OpenAI replay. Domains ending in .example are fictional.

{
"demo": true,
"engine": "openai",
"model": "gpt-4.1-2025-04-14",
"modelRequested": "gpt-4.1",
"collectionMethod": "bundled_sample",
"tool": "web_search",
"promptSetVersion": "bc8b74db0c78",
"promptText": "Which weekend hiking backpack brands are most often recommended?",
"requestedLocation": "San Francisco, California, US",
"actualLocation": null,
"locationApplied": false,
"outcome": "brand_mentioned_and_cited",
"coverageStatus": "complete",
"valid": true,
"brandMentioned": true,
"brandCited": true,
"brandDomainsMatched": "northstarpacks.example",
"citationCount": 2,
"citedDomains": "fieldnotes.example; northstarpacks.example",
"competitorsMentioned": "Trailbird",
"charged": false,
"previousOutcome": "answer_without_brand_mention",
"mentionChanged": true,
"runValidLabel": "6/8",
"runBrandMentionRate": 0.6667,
"runShareOfVoice": 0.4444
}

Sample summary

6/8 means six usable answers out of eight attempts. One attempt had no answer. One collection failed. The headline mention rate is 4 of 6 valid answers. The paired comparison against the previous sample is a separate number, because a failed recollection is not treated as the brand disappearing.

{
"validLabel": "6/8",
"coverageStatus": "partial",
"brandMentionRate": 0.6667,
"shareOfVoice": 0.4444,
"shareOfVoiceCounts": {"brand": 4, "competitors": 5, "validObservations": 6},
"chargedUnits": 0,
"materialChange": true,
"baselineUpdated": true,
"domainsRemoved": ["oldreview.example"],
"pairedBrandMentionRate": {"previous": 0.8, "current": 0.6, "delta": -0.2}
}

Coverage for the run is complete, partial, empty, blocked, or unsupported. A partial run can still contain valid answers. confidence describes whether the citation list parsed cleanly. It is not a ranking and it is not a probability that a consumer saw the brand.

Engines

EngineCallLocation
OpenAIResponses API with the web search toolCountry, city, region, and timezone are sent when you supply them. If you omit them, OpenAI may default the search to the US. The response does not confirm the location used.
PerplexitySonar (POST /v1/sonar)Country, city, and region. Coordinates are sent only together with a country.
GeminigenerateContent with Google Search groundingCoordinates only. A country without latitude and longitude is requested but not applied. Grounding URLs are often redirect wrappers. A bare domain in the title is kept as the destination. Turn on Resolve public citation redirects to follow public https hops.
AnthropicMessages API with the web search tool, called directlyCountry, city, region, and timezone when you supply them.

actualLocation stays empty unless a provider explicitly reports the location it used. These APIs do not. The requested market is never copied into that field.

The model id stored on the row is the id the provider returned when it returned one, plus the id you requested. A snapshot model such as gpt-4.1-2025-04-14 is evidence. It is not a promise that a later call uses the same snapshot.

Retrieved search results are not the same thing as citations. Perplexity’s citations array and Anthropic’s citation objects count as cited. Extra search hits that the model did not cite are counted in retrievedCount only.

Change versus the previous run

Successful runs save a baseline in the named key-value store ai-search-citation-tracker-state. Demo history and live history are separate, so a demo run cannot overwrite a live baseline.

  • The same monitor, prompt-set hash, engine, market, and repetition is one slot.
  • Mention-rate and share-of-voice deltas use only slots that had a valid answer in both runs.
  • A failed recollection does not count as the brand disappearing, and it does not erase the last valid answer for that slot.
  • A run that crashes, collects zero valid answers, stops because the charge limit was reached, or is clipped by maxObservations does not replace the baseline.
  • A prompt-set edit is reported as not comparable.

alertOnlyOnChange writes only the changed slots to the dataset. The summary still covers every attempt. Live calls are still charged, because the engine was queried. A slot that keeps failing stays in the change list until a later run collects it again.

Pricing

This Actor uses pay-per-event pricing. You pay only for these events:

EventWhen it is chargedPrice
observationOne completed prompt-engine observation: a usable answer, an answer with no citations, an answer that omits the brand, or a completed empty answer$0.02
apify-actor-startOnce per run$0.005

Not charged: failed collection, auth and rate-limit blocks, unsupported model or tool, demo replays, and a retry of a slot already charged in the same run. There is no separate per-result charge.

Provider token and search fees are billed by OpenAI, Perplexity, Google, or Anthropic to the key you supply. They are not included in the prices above. providerReportedCostUsd is copied onto the row only when that provider returns a cost. It is not an Actor charge.

The platform also bills its own compute. This Actor asks for 512 MB.

Worked examples

These use the $0.02 observation price. Add one $0.005 start event per run. They are not a forecast of how many answers will mention a brand.

RunAttemptsCompletedObservation charge
Trial: 5 prompts, 1 engine, 1 market, 1 repetition, 2 calls fail533 × $0.02 = $0.06
Weekly agency export: 20 prompts, 2 engines, 1 market, 2 repetitions, 75 of 80 complete807575 × $0.02 = $1.50 per run, about $6.00 across four weekly runs
Daily watch: 40 prompts, 3 engines, 1 market, 1 repetition, all complete120120120 × $0.02 = $2.40 per run, $72.00 across 30 daily runs

Set the platform’s maximum total charge and this Actor’s maxObservations before a large schedule. A run that hits the charge limit stops and leaves the previous baseline in place. The grid is prompts × markets × engines × repetitions. The defaults are 2 × 1 × 4 × 1 = 8 demo observations.

Limits

  • 40 prompts, 8 markets, 5 repetitions, and 500 observations per run. maxObservations defaults to 200. Crossing it clips the grid and does not update the baseline.
  • No login to ChatGPT, Perplexity, Gemini, or Claude. The result is the API response for the key and tool you configured.
  • No claim that a brand is “ranked”, recommended more often than the sample shows, or visible in a consumer app in a city.
  • Gemini without coordinates does not receive a location. OpenAI without a location may search as the United States.
  • Redirect resolution follows at most three public https hops and at most five wrapper URLs per answer. Private IPs, localhost, link-local addresses, and credentialed URLs are rejected.
  • Answer text is clipped at 12,000 characters. The hash is of the full text.
  • Mention matching does not understand negation, translation, or logos.
  • A citation of a brand domain counts only when you supplied that domain, or when the alias appears as its own word in the title or URL.
  • Demo mode replays only the bundled Northstar Packs prompt set. Other prompts in demo mode are returned as unsupported and are not charged.

Data and provider terms

Live answers and citations are collected with your key under that provider’s API terms. They are stored in your Apify dataset and the baseline store for this Actor. This project does not ship a scrape of those products.

The demo dataset is fictional and written for this Actor. .example domains are not real sites. Do not treat the demo mention rate as a measurement of any company.

If you display an answer to an end user, keep the cited URLs visible. OpenAI, Google Search grounding, and Anthropic each have their own citation and search-suggestion display rules. This Actor does not store Gemini’s search-suggestion HTML.

Integrations

The rows are flat so a spreadsheet or a webhook can use them without unpacking nested documents. citedDomains, citedUrls, and competitorsMentioned are semicolon-separated.

  • Schedule. Save a task with a stable monitorId, the same prompts, and the keys as secrets. Run it weekly or daily. Read OUTPUT.change.materialChange before paging someone.
  • Webhook. Put an https catch hook in webhookUrl, or use an Apify webhook on run succeeded. The body is the summary, including validLabel, shareOfVoice, topCitedDomains, and change. A rejected or failed hook does not fail the run.
  • n8n or Make. Trigger on the Apify run webhook. Branch on demo so a sample run cannot enter a client report. Map brandMentioned, citedDomains, outcome, and runValidLabel.
  • Google Sheets. Use the Apify integration to import the default dataset. The columns above are the ones to keep. Filter valid is true before you chart a rate, and use runValidLabel as the denominator rather than the number of rows in the sheet. In alert-only mode the sheet receives changed slots only, while runValidLabel still describes the full run.

Run it locally

Python 3.12.

pip install -r requirements.txt
python -m src

With no input, the Actor writes the demo dataset under storage/datasets/default and the summary at storage/key_value_stores/default/OUTPUT. Put a live input at storage/key_value_stores/default/INPUT.json or an INPUT.json file in the working directory. Tests:

pip install pytest
pytest

FAQ

Why doesn’t this match the answer I just saw in the consumer app? The consumer app can use a different model, memory, location, and interface. This Actor stores the API call you configured. It does not claim to reproduce a logged-in session.

Is 4/6 a ranking? No. It means the brand was mentioned in 4 of 6 valid answers in that run. Failed calls are not in the denominator. One answer is not a share of voice.

What if an engine times out? That slot is failed_collection and is not charged. The last valid answer for that slot stays in the baseline. The summary shows the gap. A run where every slot fails does not replace the baseline.

Will a bad run wipe last week? No. The baseline moves only after a finished run with at least one valid answer, and not when the charge limit or the observation cap stopped the grid early.

Do you store my API key? The key is a secret input for that run. It is sent only to the provider you selected. It is not written to the dataset. Error text is redacted.

Which engines run if I add one key? Only the engines that have a key, intersected with the engines you selected. The others are listed as skipped. They are not filled with demo answers in the same run as live answers.