See which sources Google AI Overview, ChatGPT, Perplexity and Gemini cite in your category, and where competitors appear and you do not. The default engine needs no API key and has no daily cap (English). Other languages run on your own Gemini key. Stability scoring, week-over-week diff.
All notable changes to the AI Search Visibility Tracker Apify Actor.
0.2.14 (2026-08-13)
Fixed
A rejected API key no longer fails the run. When every engine call died on an
auth rejection, the run ended with Actor.fail() — but a bad key is a setup
mistake, not a broken Actor, and this was the likeliest way a real user turned
into a "failed run" on the public success rate (75% in August; the platform blocks
empty inputs before a run starts, so a bad key was the setup error that actually
reached the code). Measured against the live APIs on 2026-08-13: OpenAI, Perplexity
and Anthropic reject a bad key with HTTP 401/403, Gemini rejects it with HTTP 400
and "API key not valid" — both shapes are now recognized. Such runs exit cleanly
with a status message and an explanatory record naming the fix (and the keyless
google-aio engine). A mixed or 5xx failure pattern still fails the run loudly.
Empty required inputs no longer fail the run. Submitting the form with no
queries or no brandDomains called Actor.fail(), so every blank-form attempt
counted as a failed run in the public success rate — part of why the Store quality
profile (70/100, 2026-08-13) flagged the Actor as "failing often" (75% success rate
in August). A setup mistake is not a broken Actor: these paths now behave exactly
like the no-key path — loud status message, explanatory dataset record, clean exit.
Genuine delivery failures (keys supplied, every call dead) still fail the run.
The no-key message now mentions the keyless engine. It suggested only
geminiApiKey, although google-aio runs with no key at all.
Added
Dataset schema with two views (citations, sources): run results now render
as organized tables in the Console instead of raw JSON. Also a prerequisite for
publishing a pre-configured task on the Store page.
Actor output schema (.actor/output_schema.json): the Output tab links the
citation records as JSON and CSV.
Gap warning: builds 0.2.0–0.2.3 shipped without changelog entries. The build
tagged latest before this round was 0.2.3; the entries below resume from
there. Treat anything between 0.1.3 and 0.2.3 as undocumented.
Second gap, same mistake: 0.2.6–0.2.9 also shipped without entries. The
entries below were reconstructed on 2026-08-05 from the Apify build API
(build numbers and finish times) plus local file modification times. Build
numbers, timestamps and the current state of the code are verified facts. The
mapping of which change went into which build is inferred, not recorded —
treat it as a best reconstruction, not a log.
[Unreleased] — audit fixes, 2026-08-05
Found by a full two-repo audit (77 confirmed findings). Nothing here is live yet — it all
needs one apify push. Backup of the pre-edit files: session scratchpad backup-fixes-0447/.
Fixed — user-visible false claims
.actor/input_schema.json, Gemini key field: promised "up to 250 queries/day" free
tier. Measured 2026-08-02 against the live API: 20 requests/day per model
(GenerateRequestsPerDayPerProjectPerModel-FreeTier, quotaValue 20). The README was
corrected at the time, the schema was missed, so the live 0.2.9 build still serves the
12.5× overstatement to every user who opens the input form.
.actor/input_schema.json, engines field: described the $0 entry point as Gemini's
free tier. The $0 entry point is the keyless google-aio engine, which is the schema
default and prefill. Rewritten to say so.
README.md:107 — comparison table "Entry price $0/mo (free Gemini tier)" →
"(keyless English runs)". Same misattribution.
README.md:128 — engine table still carried the old quota (250–1000/day) and priced
Gemini at $0.005/query. The adapter calls with google_search grounding
(src/engines/gemini.js:67), which is billed per request, not per token: ~$0.035.
Both corrected.
README.md:133, 331, 332 — the same $0.035 grounding cost was missing from every
per-query total. All-four-engines figure moved from ~$0.25–0.28 to ~$0.275–0.30.
README.md:306 — agency revenue math assumed one Gemini key covers 5 clients at 300
prompts/month each. That is 50 requests/day against a 20/day cap. Rewritten around the
keyless default, which has no per-key cap.
README.md:328-332 — the pricing section listed Gemini free / Gemini paid / all four
engines, and omitted the default engine entirely. Anyone pressing Start on the Store
runs google-aio, and its price appeared nowhere. Added as the first row.
Removed — claims the code disproves
README.md, FAQ "Why no Google AI Overviews in v0.1?" and the limitations bullet
"Google AI Overviews are not yet covered in v0.1", both promising AIO "in v0.2 via
apify/google-search-scraper". src/engines/google-aio.js has shipped, is wired into
src/main.js:21/153/186, and is the default engine. Replaced with what is actually
true, including the English-only limit and why (bot detection on the SERP proxy blocks the
language JS bundle; seven fixes attempted, all failed).
[0.2.9] — 2026-08-05 (reconstructed)
Tagged latest at 12:49 local (10:49 UTC), two minutes after 0.2.8 took the
beta tag. That gap matches the documented release order: push to beta, run
against it, then move latest.
Added — keyless English path is now the default
src/engines/google-aio.js (written 2026-08-04 22:06) reads Google's own result
page through Apify's GOOGLE_SERP proxy with Playwright and extracts the AI
Overview text plus its cited sources. No API key is required from anyone —
not the user, not the author.
Wired into src/main.js:
line 21 — import { queryGoogleAio } from './engines/google-aio.js'
line 153 — registered in the engine map as 'google-aio'
line 186 — if (e !== 'google-aio' && !engineKeys[e]) exempts it from the
API-key precondition that gates every other engine
proxy handling is conditional, so a run without google-aio reserves no proxy
.actor/input_schema.json sets bothdefault and prefill to
["google-aio"], so a new user who opens the Store listing and presses Start
runs with zero configuration.
Measured on 2026-08-04, English only: 8.9 s per query, 1247 characters,
4–5 sources, ~$0.005/query. At the $0.05 price taking effect 2026-08-10, after
Apify's 20% margin, that is roughly 87% gross margin.
⛔ English only, and this is a hard limit — do not reopen it. Seven fixes were
tried against German/Hungarian and all seven failed. The decisive measurement:
Playwright loads example.com through the residential proxy in 1.4 s but does
not load google.com in 35 s, while curl on the same proxy gets a 302 in
1.3 s. Google declines to serve a browser from a proxy IP. The language JS
bundle (/xjs/_/js/k=xjs.s.de…) returns ERR_ABORTED through the SERP proxy, so
the renderer never runs. This is bot detection, not a language bug. Details and
the full list of attempted fixes are in the project memory.
ℹ️ Two facts worth keeping: the SERP proxy serves HTTP only (HTTPS tunnelling
fails with ERR_TUNNEL_CONNECTION_FAILED), and the AI Overview header is
identified by jsname="cUzNTd", which is language-independent. Never match
on the visible caption — it differs per language.
Changed
.actor/input_schema.json and .actor/actor.json updated at 12:45 local,
four minutes before this build finished, so both are in it.
README.md updated at 12:46 local — including the corrected Gemini free-tier
figure (20 requests/day per model, measured 2026-08-02, down from the 250
and ~1000 previously published).
Known defect shipped in this build — fixed after the fact
.actor/input_schema.json still described the Gemini free tier as
"up to 250 queries/day" while the README already carried the corrected
20/day figure. Every user opening the input form saw the wrong number. Corrected
on 2026-08-05 after this build; the fix is not in 0.2.9 and needs a push.
[0.2.8] — 2026-08-05 (reconstructed)
Tagged beta at 12:47 local (10:47 UTC). Same content as 0.2.9 as far as the
file timestamps show; this was the test build that 0.2.9 promoted two minutes
later.
[0.2.7] — 2026-08-04 (reconstructed)
Built at 15:59 local (13:59 UTC), five minutes after 0.2.6. The only source
file touched in that window is src/engines/http.js (modified 15:44 local).
A five-minute gap between two builds is the signature of a follow-up correction
to the change in 0.2.6, but what specifically was corrected is not recorded.
[0.2.6] — 2026-08-04 (reconstructed)
Built at 15:54 local (13:54 UTC). Carries the src/engines/http.js changes made
at 15:44 local — the shared retry/backoff layer introduced in 0.2.4 that all four
API adapters route through. The specific change is not recorded.
⚠️ src/main.js (21:25) and src/engines/google-aio.js (22:06) were both written
after this build, so neither is in 0.2.6 or 0.2.7. They first shipped in 0.2.8.
[0.2.5] — 2026-08-02
Fixed — a missing API key failed the run, which flags the Store listing
Regression introduced by 0.2.4, caught the same day. Apify runs every Store Actor
automatically against the prefilled input schema and expects it to succeed
within 5 minutes. That prefill has queries and brandDomains but — necessarily,
for a bring-your-own-key Actor — no API key. So the automated run lands on the
"no engines runnable" branch.
Before 0.2.4 that branch called Actor.exit(1), which the SDK silently treats as
exit code 0, so the run reported Succeeded and the check passed. Fixing the
exit-code bug in 0.2.4 turned that into a genuine FAILED run — verified against
the live platform with the exact prefill input (run udMBpN5Wgq8PSAlkf,
exitCode: 1). In other words the Actor had been passing quality assurance only
by accident, and 0.2.4 removed the accident without supplying the substance.
The distinction the code now draws:
No API key supplied → a configuration gap, not a broken Actor. Exits 0 with
a terminal status message the Console surfaces on the run, plus an error
record in the dataset so the run is not silently empty.
Keys supplied, every call failed → the Actor failed to deliver. Still
Actor.fail(), as introduced in 0.2.4.
This is not a retreat to the old silent-success behaviour: that was a green run
after real work failed, this is an honest report that the run never had the
credentials to start.
⚠️ Context worth keeping: the listing was already flagged "Under maintenance" by
Apify before this release, so the underlying problem predates 0.2.4 — an Actor
that cannot do anything without a user-supplied key cannot pass an automated
check that has no key to give. This release stops the bleeding. The durable fix
is the zero-setup path (shared Gemini key), which is a cost decision, not a code
one — and the newly measured 20-requests/day free tier makes a free shared key
a non-starter.
[0.2.4] — 2026-08-02 — superseded by 0.2.5 the same day
Fixed — engine calls had no retry, and a single transient error lost the query
The defect: src/engines/*.js each did one fetch, threw on any non-2xx, and
main.js logged the failure and moved on. A provider hiccup therefore cost the
user a query permanently. On a 25-call local run on 2026-07-28, 23 calls died
with Gemini 503 UNAVAILABLE ("The model is overloaded"). Nothing was charged
(Actor.charge fires only after a successful call), but the user received a
near-empty dataset — paying nothing and getting nothing is still a reason to
leave, and this is a plausible contributor to the "30 days: 5 users → 7 days: 0"
trend.
New src/engines/http.js — one retry layer shared by all four adapters.
Retries 408/429/500/502/503/504, network errors and timeouts; never retries
other 4xx (a bad key fails identically on attempt 4, so retrying only delays the
error message). 3 retries, full-jitter exponential backoff, 180 s per-attempt
timeout, 75 s total sleep budget per call.
All four adapters (gemini, openai, anthropic, perplexity) now route
through it and accept optional onRetry / retries.
main.js logs every retry, counts them in stats.retries, and records
attempts on each error row in the dataset.
Fixed — a run where every call failed reported success
An all-failed run wrote a summary and exited 0, so the Apify run list showed
green next to an empty dataset. It now exits non-zero with an explanatory log
line. This will lower the Store-visible success rate when providers are
down — deliberately: the number should reflect what users actually received.
Nothing is charged on such a run.
(See 0.2.5: applying this to the no key supplied branch as well was a mistake,
and broke Apify's automated quality check within hours.)
Measured, 2026-08-02 (gemini-2.5-flash, 12 calls at concurrency 8)
ok
failed
before (single attempt)
1
11
after (3 retries)
5
7
Honest caveat: the two runs are not a clean A/B. The measurement itself burned
the key's daily quota (see below), so the "after" run started from a worse state
than the "before" run — the comparison understates the fix rather than
flattering it. The recovery path is covered deterministically instead by
scripts/test-retry.js (39 assertions against a scripted local server).
Two things the live API taught us that docs did not
Google sends no Retry-After header. It puts the wait in the JSON body,
as a google.rpc.RetryInfo detail ("retryDelay": "18s") and in prose
("Please retry in 18.852153328s."). The first version of this fix ignored
both and used computed backoff — it spent all three attempts inside 7 s while
the limiter wanted 19 s, i.e. the retry was pure added latency on a
guaranteed failure. Now parsed, header first, body second.
A server hint is a floor, not a ceiling. Under sustained rate limiting
Gemini alternates real waits ("38s") with "retryDelay": "0s". Obeying the
0 literally burned attempts 2 and 3 in the same millisecond. The wait is now
max(hint, computed backoff). Regression-tested.
Fixed — the free Gemini tier is 20 requests/day, not 250
Measured directly against the API on 2026-08-02, on both gemini-2.5-flash and
gemini-2.5-flash-lite:
Verified by sending one grounded and one plain call: the cap applies to both,
so it is not a grounding surcharge. The README promised 250/day and ~1000/day;
those numbers were true when written and are not any more. README and the
gemini.js header now carry the measured figure, dated, with a pointer to
re-measure rather than trust it.
Consequence for the run loop: Google attaches a shortretryDelay (33 s) even
to a spent daily quota, which made every worker sleep a minute for nothing.
The layer now reads the QuotaFailure violation instead of the delay, and fails
a per-day exhaustion immediately with an actionable message. Wall time on an
exhausted key went from ~60 s of sleeping per call to 0.4 s for the whole run.
⚠️ This is a live product concern, not just a doc fix: a free-key user gets 20
citation records per day, which is thin ground for a "$0 to start" pitch. Worth
revisiting alongside the shared-key decision.
Tooling
scripts/test-retry.js (npm run test:retry) — 39 assertions against a
scripted localhost server: real 503 recovery, no-retry-on-401, Retry-After,
Google's body hints, the "0s" regression, per-day fast-fail, per-attempt
timeout, connection refused, sleep budget. No network, no API keys.
scripts/bench-retry.js (npm run bench:retry) — hits the live API N times at
the Actor's real concurrency and reports the success rate. --no-retry
reproduces the pre-fix adapter for A/B. Keep N small: 20/day/model.
Stability score visualisation (screenshot ) — 5-sample bar chart from real Phase 3 data showing custify.com (1.00), encharge.io (0.80) down to memberspace.com (0.20). Documents the recommended stability >= 0.5 filter.
Delta-mode visualisation (screenshot ) — gained / lost domains per query, week-over-week.
Engine comparison visualisation (screenshot ) — same query on Gemini / ChatGPT / Perplexity / Claude side-by-side, with FREE TIER vs BYOK badges and per-engine cost.
Five use-case personas: agency SEO lead, in-house SEO at B2B SaaS, PR/comms team, indie SEO consultant, Hungarian/German/French/Polish B2B brand. Each with concrete numeric ROI.
FAQ section — 14 questions covering: engine selection, API key safety, manual ChatGPT comparison, scraping vs API, Google AIO absence, multilingual support, CSV export, scheduling, market analysis, model upgrades, paid Gemini tiers, free pricing window.
Pricing breakdown table — three pricing scenarios with concrete dollar figures.
Table of contents at top.
Tooling
scripts/make-extra-screenshots.py — renders the 3 new screenshots (stability bars, delta gained/lost, engine comparison).
scripts/upload-screenshots.js — extended to upload all 6 PNGs (icon + 5 screenshots) idempotently.
Notes
README grew from 184 lines / 1,500 words to 435 lines / 3,651 words. All new content is Apify-Store-SEO-targeted long-tail keyword coverage. No code changes — actor logic identical to v0.1.2.
[0.1.1] — 2026-04-29
Added — Store listing visuals (README inline images)
Hero image at the top of the README showing the real cloud-run output of the same "best CRM for small business" intent in EN / DE / FR / HU. The German prompt surfaces 5 of 7 sources from .de/.at/.ch TLDs, the Hungarian one 3 of 7 from .hu, the English one 0 of 11 — making the multilingual edge legible at a glance.
Output-detail image in the Output section showing a single citation record from the Apify cloud run sIXsrVM5qKzAHY8HP — query, engine, citation count, top sources, and the brand-match table with all three tracked brands cited.
Branded actor icon (512×512 PNG) — indigo→cyan gradient with a centered crosshair / target ring, mint "AI" wordmark, and a "GEO" chip below. Replaces the generic Store gradient; signals the tracking + AI-search category at a glance.
All three assets are stored in a named, public Apify key-value store (ai-search-visibility-tracker-marketing) so they don't expire and the README CDN-proxies them via images.apifyusercontent.com.
Tooling — repeatable visual generation
scripts/make-icon.py — generates marketing/icon-512.png and marketing/icon-128.png with the branded design. Re-run on each redesign.
scripts/make-screenshots.py — renders both README screenshots from real captured run data (Phase 2 multilingual run + cloud run sIXsrVM5qKzAHY8HP). Glyph-independent: drawn check marks (line segments) replace ✓ to avoid font-fallback rendering issues.
scripts/upload-screenshots.js — idempotent uploader that finds (or creates once) the named public KVS and PUTs each PNG.
Notes
No code changes in this release — purely Store-listing polish. The actor itself is bit-for-bit identical to v0.1.0; the new build only ships an updated README.
[0.1.0] — 2026-04-29
Initial scaffold — $0 build
First runnable version. Validates the core thesis: a multilingual GEO tracker can run on the Apify platform without the author paying for any third-party API.
Engines (all share the same return shape)
Gemini 2.5 Flash with google_search grounding — works on Google AI Studio's free tier (10 RPM, 250 RPD). Citations are returned as structured groundingChunks (URI + title + snippet).
OpenAI Responses API with web_search_preview tool (gpt-4o-mini-search-preview) — BYOK, ~$0.025/query.
registrableDomain — heuristic eTLD+1, handles .co.uk, .com.br, .co.jp, etc. without a Public Suffix List dependency.
extractUrlsFromText + normaliseCitations — merges engine-supplied structured citations with URLs mined from the answer text, dedup by canonical host+path.
detectBrandCitation — flags brand mentions via citation match OR whole-word brand-token match in the answer text.
citationShare — share-of-voice across tracked domains.
stabilityAcrossSamples — fraction of N samples in which each domain was cited (filters one-shot hallucinations when samplesPerQuery > 1).
diffRuns — gained/lost domains per query vs a prior dataset (delta mode).
pMap — bounded-concurrency async map, no external dep.
26 offline assertions (npm test) cover all of the above. Pass on first run.
Orchestrator (src/main.js)
Validates input, drops engines with missing keys (warns instead of failing).
Builds a work plan of (query × engine × sample) tasks.
Runs them with maxConcurrency (default 4 — stays under Gemini free-tier 10 RPM).
Writes per-call citation records, plus optional stability (when samplesPerQuery > 1), delta (when previousRunDatasetId set), and a final summary record.
Mirrors the summary into the run's KV store under key SUMMARY.
Input schema
queries, brandDomains are required; competitorDomains, language, engines and BYOK keys are optional.
24 language enum (en, de, fr, es, it, pt, nl, hu, pl, cs, sk, ro, bg, hr, tr, ja, ko, zh, ru, uk, sv, no, da, fi).
samplesPerQuery (1–5) for stability scoring.
previousRunDatasetId for delta mode.
Per-engine model overrides (geminiModel, openaiModel, perplexityModel, anthropicModel).
Tooling
scripts/test-utils.js — offline assertions for utils.js. No network, no keys.
scripts/smoke.js — live-network smoke against whichever engines have a key set in env.
scripts/test-comprehensive.js — 6-phase live integration runner with billing-aware error handling and retry on 503.
scripts/cloud-run.js — REST-API trigger + status poll, used to verify the actor on Apify cloud.
scripts/update-actor-metadata.js — applies Store metadata (categories, seoTitle/description, exampleRunInput) idempotently.
Why "BYOK + free Gemini tier"
Research showed that the cheapest viable competitor (Otterly Lite at $29/mo) gates users at 15 prompts/month. Profound has no free trial. Peec AI starts at €89/mo. The whole category locks out indie SEOs and small agencies. By passing the API-key cost transparently to the user, we charge only Apify pay-per-event for the orchestration layer, and the entry point is genuinely $0/month with Gemini's free tier.