Track your AI search visibility (GEO/AEO): do AI models name your brand when buyers ask? Mention rate, rank, competitors and share of voice per model, with change since the last run. Asks the API models behind ChatGPT, Claude, Gemini and Perplexity (not the apps); $15 per 1,000 checks plus tokens.
Versions follow MAJOR.MINOR.PATCH (src/version.py); Apify shows MAJOR.MINOR from .actor/actor.json.
Every run logs its version and records it in the RUN_STATS key-value record.
About future changes in output: models answer differently between runs, and a provider can change a model
behind the same id. That's what the samples and the change since the last run are for.
0.1.5 (2026-10-16)
A run that returns nothing (every input bad, private, blocked or unreachable, or an empty input) now ends
SUCCEEDED with an empty dataset and the reason as its status message and in the log, instead of FAILED. Only an
unexpected error still fails the run. RUN_STATS keeps the reason as before.
No change to output fields or price.
0.1.4 (unreleased)
Fixed: one flagged question no longer stops the run. When the model provider's moderation flags a question or
answer, OpenRouter answers with HTTP 403, the same status as a refused token, and the run stopped there and
failed. That check now gets a row with the reason ("the model provider's moderation flagged this question or
answer (...)"), uncharged, and the other checks carry on. A refused token or account still stops the run at once.
A model that declines to answer now says so, with its reason ("the model declined to answer: ..."), instead of
"the model returned an empty answer". Still a row with valid: false, uncharged.
0.1.3 (2026-10-03)
The history store is opened through the shared helper below; its name is back to ai-visibility-history, with
humble-echidna-ai-visibility-history (0.1.2's name) as the fallback, so runs since 0.1.2 keep their history.
A run no longer fails when the store it remembers earlier runs in can't be opened (403: the account already has
a store of that name that this actor didn't create). It falls back to humble-echidna-<name>, and if that is
refused too it runs without memory for that run. Users' existing memory is untouched: the usual name is tried first.
0.1.2 (2026-10-03)
Fixed: runs by other users could fail at once with "Insufficient permissions for the key-value store" (Apify's
daily test did, so the actor was flagged "under maintenance"). The history store now has a name of its own,
humble-echidna-ai-visibility-history, and a store that still can't be opened no longer fails the run: it runs
without the change since the last run and says so in the log and RUN_STATS.historyUnavailable. History from
earlier runs starts over once.
0.1.1 (2026-10-02, private)
A model left out of one run keeps its history: the next run that asks it again compares with its last known
rate (found on Apify: a run on another model in between left before empty).
Web search tested through Apify's OpenRouter proxy, 2026-10-02: neither OpenRouter's web plugin nor the
:online model suffix searched (no citations, no search results in the token counts), so webSearch stays out
of the input. perplexity/sonar does search on its own, and its citations come through as citedUrls.
0.1.0 (2026-10-02, private)
First release: whether AI models name your brand when buyers ask them questions.
Input:brand and competitors (other names after |, whole-word, case-insensitive; a domain among them
marks links to it), prompts (up to 100), models (up to 5 OpenRouter ids, default openai/gpt-4.1-mini),
samples (1-5), onlyChanges, maxChecks. A prompt that names the brand itself is flagged in the log and
RUN_STATS.promptsNamingBrand.
Checks: every prompt × model × sample, sample round first (a capped run covers every prompt and model once
before repeating). Each check is two calls through Apify's OpenRouter proxy with the run's own token: the question
as a bare user message (no system prompt), the answer capped at 1,000 tokens; then a strict structured-output call
on openai/gpt-4.1-mini that lists every brand the answer names, in its order, validated against the schema here.
Rows:brandMentioned, brandRank (among every brand named; a tracked brand the list missed is placed where
the text first names it), competitorsMentioned, competitorRanks, brandsNamed, citedUrls (citations plus
links written in the answer), brandCited, answer, answerTruncated, changed, valid, errors, tokens.
Summary row (free): mention rate overall and per model, each sample round's rate and their spread, average
rank, share of voice among the tracked brands, the 20 brands named most, and changes against the last run of
the same brand + prompt set (named store ai-visibility-history in the user's account, one record per tracker;
adding a competitor or a model keeps the history). onlyChanges leaves unchanged checks out of the dataset (still
summarised and charged).
Charging: rows are free; answer-checked is charged once per valid check, after its row is in the dataset
(or when it's left out as unchanged). Failed calls, empty answers and unreadable brand lists are rows with the
reason, uncharged. maxChecks and the maximum cost per run are honoured before a check is asked: a slot is
claimed per check and given back when it isn't valid. A proxy that refuses the run (401/402/403) stops it at once.
Web search: the code can ask for OpenRouter's web plugin (webSearch), but it isn't offered in the input
until it's confirmed to pass through Apify's proxy.
Sources and terms (ACTOR-CHECKLIST section 0): nothing is scraped. The model endpoint is Apify's own,
documented for actors (https://docs.apify.com/sdk/python/docs/guides/ai-agents , read 2026-09-29 for ai-extract:
"The token usage is billed against the Apify account running the Actor, so no provider API key is required"). No
credential or identity of ours is used. No Google AI Overviews and no consumer-app scraping (legal hold). Brands,
not people: said in the README and the brand input description (it can't be enforced).