The Receipts Engine
Anyone can estimate. We count.
The Receipts Engine is the scanner and monitoring stack behind everything Answerable sells. It asks real AI engines real buyer questions, stores every answer word for word, and counts the mentions with a regular expression. No model is ever asked to produce a number. The formula that turns those counts into a score is published on this page, in full, including the parts that are not flattering.
Every number on this page can be checked in the product. Each claim below ends with the command, file or file path that proves it, and the 300-business dataset we published is a file you can download and recompute without asking us for anything.
The pipeline
How a score is made.
Seven stages, in the order the scanner runs them. Three of them involve a model, and this page says which three.
-
01
Classify
Work out what the business is and where it operates
The scanner fetches the homepage and puts it to a model in one structured call, which returns three things: a category, a geography, and the brand names the business actually trades under. This is the first of the pipeline's three model calls, and it is worth naming because its brand-name list is what becomes the regular expressions in stage 04. The model proposes the names; the matching that produces every count stays deterministic.
That pair of category and geography becomes a cohort key, normalised to lower case, which is how businesses selling the same thing in the same place end up sharing a question set.
// cohort.js cohortKey cohort_key = "plumber|melbourne"
src/lib/classify.js classifyBusiness, src/lib/cohort.js cohortKey -
02
Generate the questions
Write buyer questions that never contain the brand
The questions are written for the category and the geography, not for the business, because a question that names you cannot test whether an engine names you unprompted. Two paths generate questions and the rule is kept differently on each, so it is worth being exact about which.
On the per-domain path the rule is enforced twice: it is in the instruction sent to the model, and a mechanical filter afterwards drops any question containing the domain or its stem. On the cohort path there is no single brand to filter against, so the questions are brand-neutral by construction instead: the function is given a category and a geography and is never passed a domain at all. Either way the scrub happens before anything is asked, so a stored transcript quotes exactly what was sent.
// classify.js classifyBusiness, the per-domain filter .filter(q => q && !q.toLowerCase().includes(stem) && !q.toLowerCase().includes(domain.toLowerCase())) // the deterministic cohort fallback set, verbatim `What are the best ${category} in ${geo}?` `Who is the most trusted ${category} in ${geo}?` `Which ${category} in ${geo} do people recommend most?`
src/lib/classify.js classifyBusiness (the filter above), cohortBuyerQuestions and templateCohortQuestions (the cohort path) -
03
Ask the engines
Put the questions to live AI engines and keep the answers
ChatGPT is asked through the OpenAI Responses API with the web search tool enabled. Gemini is asked through the Generative Language API with Google Search grounding. Both are ordinary paid or free-tier API access, nothing privileged. The answer text and its citations are written to storage exactly as they came back.
If Gemini's grounding is refused or its daily quota is exhausted, the question is re-run ungrounded. It is never quietly upgraded to a paid engine to hide the gap.
Being exact about where that fallback is recorded: today it is counted in the scan API's own response, as a grounded and ungrounded tally in the run metadata, and it is not yet written onto the report page. The report's methodology note currently describes the direct verdict as grounded whether or not the fallback fired, which means on a quota-exhausted run the report is more confident than the run was. That is a defect in our reporting, not a nuance, and the fix is to carry the flag through to each stored answer. It is written here rather than left for someone else to find.
src/providers/gemini.js ask, src/lib/cohort.js gemini_grounding, src/lib/scan.js assembleReport -
04
Match
Decide whether the business was actually named
Matching is a regular expression over the stored text, and it is deliberately fussy about short names. A brand of six characters or fewer with no space only matches when it is capitalised or in all capitals, because short brand names are usually ordinary English words too. Longer or multi-word names match case-insensitively.
The rule exists for a real business: the brand GLOW is also a common noun. Without the rule, the phrase "a healthy glow" would score as a brand mention.
// mentions.js matcherFor name.length <= 6 && !name.includes(" ") → /\b(GLOW|Glow)\b/ case-sensitive otherwise → /\bAustralian Glow\b/i case-insensitive analyze("she had a healthy glow") → mentioned: false analyze("Try GLOW for reviews") → mentioned: true
src/lib/mentions.js matcherFor, analyze -
05
Count
Increment two integers, and nothing else
For each question the scanner increments
asked, and incrementsmentionedif the matcher hit. A citation to the business's own domain counts as a mention for that question even when the name is not written in the prose, because an engine that links you has named you. It is also recorded as its owncitedflag, so citations can be reported separately from prose mentions. Because every question is its own provider call, a citation can only ever count for the question it was returned against.These two integers are the 70 per cent term of every engine score. No model touches them.
// mentions.js countMentions countMentions("no name here", ["https://glow.com.au/x"], matchers) → { mentioned: true, cited: true }
src/lib/mentions.js countMentions, citedIn; src/providers/index.js runOne -
06
Site checks
Three HTTP requests, four checks, fixed points
Site readiness has no model in it at all. The scanner makes three plain GET requests, to the homepage, to robots.txt and to llms.txt, and awards fixed points for four checks. The crawlers it looks for in robots.txt are GPTBot, ClaudeBot, Claude-Web, PerplexityBot, Google-Extended and Bytespider. Anyone can reproduce a site readiness score by hand.
src/lib/sitechecks.js AI_BOTS, runSiteChecksCheck What passes Points AI crawlers allowed Site reachable and robots.txt blocks none of the six 40 Structured data At least one JSON-LD block on the homepage 25 Meta basics Homepage has both a title and a meta description 20 llms.txt A non-empty llms.txt that is not an HTML error page 15 -
07
Assemble
Apply the formula, and write down what actually ran
The counts and the site score go through the published formula. Two model-graded inputs join them here: a recognised flag and a sentiment value, both read from the stored direct-answer transcript. Together they can move an engine score by at most 30 of 100 points.
The report then records its own methodology in a field, not just in the marketing: which engines were asked a fresh direct question, which were scored from a shared category sweep, and which were not configured at all.
// report.methodology { note, tier, weights, providers_scored, direct_platforms } // per engine platforms[].direct_asked → true | false
src/lib/scan.js assembleReport, src/lib/judge.js judgeEvidence
Published in full
The formula.
A black box is a business decision, not a technical necessity. Here is ours, in the arithmetic the code actually runs.
Per-engine visibility, out of 100
rate = asked > 0 ? mentioned / asked : 0
sent01 = clamp(0, 1, (sentiment + 1) / 2)
platform_visibility = round(100 * (
0.70 * rate
+ 0.20 * (recognized ? 1 : 0)
+ 0.10 * sent01
))
70 per cent is a count. The share of asked questions in which the engine named the business, where asked only counts calls that actually succeeded. 20 per cent is recognition, a true or false flag for whether the engine knew the business when asked about it directly. 10 per cent is sentiment, a value from minus one to one, rescaled to sit between zero and one.
The recognised flag and the sentiment value are the only model-produced inputs in the whole pipeline. They are graded from the stored transcript, and between them they are worth at most 30 of the 100 points. We say that plainly rather than letting the number read as fully mechanical, because the honesty here is asymmetric: the counted 70 per cent can be reproduced by a third party from the stored answers, and the other 30 cannot.
Overall score, out of 100
// engines that are configured AND answered
scored = platforms.filter(p => p.configured && p.ok)
if (scored.length === 0)
overall = round(site_readiness * 0.25)
else
overall = round(
0.75 * mean(platform_visibility over scored)
+ 0.25 * site_readiness
)
Three quarters of the score is what the engines said. One quarter is whether the site is technically readable. That weighting is a judgement call and it is stated openly so it can be argued with.
Status bands
score >= 70 → "Visible" score >= 35 → "Partly visible" score > 0 or recognised → "Weak" otherwise → "Not found"
Where each line lives
| Piece | File |
|---|---|
| Engine score | score.js platformScore |
| Overall score | score.js overallScore |
| Weights | score.js WEIGHTS |
| Bands | score.js statusFor |
| Site points | sitechecks.js checks |
Take the two engine scores and the site readiness score from any row of our published dataset and run the arithmetic above. It reproduces the published overall score. We ran it across every row: 300 of 300 match exactly, with no mismatches.
Four decisions
Four things that make it different.
Each one is a design decision with a cost attached, and each one ends in something a sceptic can run.
The cohort engine
One live sweep, shared by a whole category
Businesses selling the same thing in the same place get asked the same brand-neutral buyer questions, and the live answers to those questions are shared between them. Every business is then scored by re-counting that same shared transcript against its own matchers. The cohort questions are generated from a category and a geography only and never see a domain, so sharing them changes nothing about what is being measured.
The call count is emitted by the code rather than estimated, and the conditions belong with the number. Measured locally with MOCK=1 and all five adapters enabled, six businesses in one cohort came to 31 provider calls, against the 180 the same six would cost as separate full scans. On the deployed two-engine configuration the same comparison is 16 against 72. Cohort transcripts expire after 14 days, at which point the sweep is re-run live.
// emitted locally, MOCK=1, 6 domains, one cohort, // all five adapters reporting configured provider_calls = { buyer_sweep: 25, direct: 6, total: 31 } // deployed today: 2 engines configured // 5 questions x 2 + 6 directs = 16, vs 6 x 12 = 72
Run the scanner locally with MOCK=1 and post six same-cohort domains to /api/scan-batch. The response carries meta.provider_calls and cohorts[0].created. Post three more in the same cohort inside the 14-day window and the sweep count comes back zero.
Pinned question sets
Month to month only compares if the question does not move
A scanner that re-derives its buyer questions from a model every month is comparing two different measurements and calling the difference progress. Our monitoring product pins the question set on the first scan, stores each question under a hash of its own text, and re-verifies after every scan that the scanner asked that exact set. If it did not, the question set version is bumped and the comparability gate fires.
The scanner accepts a pinned set from an authenticated caller and stores it under a content-hashed cohort key, so a pinned run never collides with the shared cohort. An invalid question set is rejected with a 400 rather than silently falling back to generated questions.
Locally, posting a pinned set returns a report whose questions are byte-identical to what was sent, under a cohort key such as plumber|melbourne#q87454eae. This path is built and verified in local runs. The monitoring product has not been deployed, so we do not present monthly pinned questions as a live production guarantee.
The comparability guard
A broken scan can never be reported as an improvement
There is a specific failure that flatters an agency. The mention rate is mentions divided by questions asked, and asked only counts calls that succeeded, so when one engine goes dark the denominator shrinks and the score goes up. Left alone, a client whose scan half broke would be emailed an improvement.
The engine classifies that move as an artefact and it never reaches the email. An engine going dark is not recorded as a visibility loss and an engine coming back is not recorded as a win: both are disclosed to the client as a reading problem. A scan that failed cannot carry a score at all, and that is enforced by a database constraint rather than by convention. If the evidence behind a scan cannot be stored in full, the scan is demoted to failed and its score is deleted.
Run the diff test suite in the monitoring codebase. The block named "the fake gain: a broken scan must never read as an improvement" asserts that the raw score really does rise 8 points when Gemini dies, that the move comes back classified as an artefact, and that no score-moved item is emitted. All 28 assertions in that suite pass.
Counted, not generated
No model is ever asked for a number
Three structured model calls exist in the scanner: one classifies the business and proposes its brand names, one generates the cohort's buyer questions, and one grades the stored transcript for the recognised flag, the sentiment value and the report's prose. None of the three emits asked, mentioned or visible. Every number in a report traces back to a counter incremented in the provider loop.
Every answer is stored word for word. The report page shows three in full and the rest as locked rows, never blurred; the free scan on this site shows the direct answer about you and tells you how many are withheld. The full set is what the audit unlocks. In the monitoring product each stored answer also carries a SHA-256 of its own text, so it can be shown that nothing was edited after the fact. An answer that comes back byte-for-byte identical to last month is excluded from wins and losses before classification, because identical text is evidence of a cache, not evidence of stability.
On any report page, read the three featured answers against the questions they were counted from; the locked rows show you the question and withhold only the answer. Then grep the scanner for a model call that returns a score. A grep returns exactly three JSON-schema calls, at classify.js classifyBusiness, classify.js cohortBuyerQuestions and judge.js judgeEvidence, and not one of them produces a count.
The boundaries
What we do not claim.
This is not fine print. A measurement is only worth something if its limits are stated by the people who built it, so these are ours, in the same size type as everything else.
-
Two engines, not five
The deployed scanner queries ChatGPT and Gemini. Adapters for Claude, Perplexity and Grok exist in the code but have no API key configured, so a report marks them Not configured rather than scoring them. The scanner publishes a live configuration endpoint at
ai-visibility.jackson-89e.workers.dev/api/config, so which engines are switched on is a fact anyone can re-check at any time rather than a claim on a marketing page. That is the scanner's own host, not this one: this site is static pages and the endpoint lives with the code that answers it. -
No partnership with any AI company
We are a paying API customer of two AI providers. We have no partnership, integration, affiliation or authorised access with OpenAI, Google, Anthropic, Perplexity or xAI, and we do not display their logos as endorsements.
-
Scheduled, not realtime
Monitoring re-scans each client once a month, on their own scan day, picked up by a daily job that runs whoever is due. Between scans nothing is measured. There is no always-on, continuous or realtime tracking, and the monitoring product is built rather than operating: it has not been deployed yet.
-
We do not see real user sessions
We have no access to anyone's actual ChatGPT or Gemini conversations, no query volume data and no share of voice measurement. We ask a fixed set of questions ourselves, through public APIs, and count what comes back. Anyone claiming to observe what real buyers are shown by these tools should be asked how.
-
A scan is a snapshot
AI answers are non-deterministic and personalised, so the same question can return a different answer an hour later. In the published study each question was asked exactly once, with no repetition runs, and that limit is stated on the study page and anywhere its numbers are quoted. Two scans differing is often the model being inconsistent, which is precisely why the monitoring product carries a noise band and treats volatile questions separately.
-
In the cold-outreach scan, one call is about you
In the thin tier used for cold outreach, which is not the free scan you run yourself on this site, the only fresh per-business call is a single Gemini question about that business. The ChatGPT result comes from the shared category sweep, which is identical for every business in the cohort. A thin-tier report says exactly that in its own methodology field. The free scan on this site takes the other path: it is a full per-business run, with its own fresh questions and a fresh direct question to every configured engine.
-
The score is not a business outcome
Nothing in either codebase measures leads, revenue, traffic or rankings, so we do not claim the score predicts or causes any of them, we do not promise a score will improve, and we attach no timeframe or number to any improvement. We also do not sell llms.txt as a ranking lever: it is 15 of 100 points in our own readiness check, and Google has stated it does not use the file.
-
We do not claim to know why a number moved
The monitoring engine attributes a score move to the components it tracks, which are rate, recognition, sentiment and the site checks. When none of those flipped, it refuses to name a cause rather than inventing one, and it never attributes a change to an AI algorithm update.
-
Report links are private, not a public register
Individual report pages are unlisted, unguessable and set to noindex by design. They are proof for the business that owns them, not a directory a third party can look a competitor up in.
Proof at scale
The engine, run in public.
The strongest thing we can say about the measurement is that we ran it on 300 businesses that are not our clients and published the raw file.
The State of AI Visibility in Australia 2026 scanned 300 businesses on 30 July 2026, 50 in each of six industries, and published the complete dataset as CSV and JSON under a CC BY 4.0 licence with Answerable named as publisher. It is the same engine described on this page, pointed at businesses with no relationship to us.
The study states its own limits on its own page. Each question was asked once, with no repetition runs. All 300 businesses have a ChatGPT record but only 137 have complete records from both engines, and that figure is countable in the published file. The sample frame was top organic search results plus recognised national anchor brands, which is a visible-end sample rather than a random or representative one.
It also publishes a correlation that works against the obvious sales pitch. Across the 137 complete records, site readiness against mean per-engine visibility comes out at a Pearson r of 0.116. Readiness explains almost nothing about who gets recommended, and the study says so in those terms rather than burying it.
The reason to lead with this file is simple. A sceptic, a journalist or a regulator can recompute every headline number in the study from the raw data with four lines of arithmetic and no access to us at all.
// recompute the overall score from published columns const v = ['chatgpt', 'gemini'] .filter(p => r[p + '_questions_asked'] != null && r[p + '_questions_asked'] !== '') .map(p => r[p + '_visibility_score']); Math.round(0.75 * (v.reduce((a, b) => a + b) / v.length) + 0.25 * r.site_readiness_score) === r.ai_visibility_score // true for all 300 rows
Licensed CC BY 4.0. Use it, quote it, check our arithmetic with it. Attribution to Answerable is all we ask. The headline figures are also broken out in citable form in the AI visibility statistics summary.
Questions
The questions people ask about the measurement.
How is an AI visibility score calculated?
Which AI engines does Answerable query?
Is a model deciding my score?
Do you see what real people are asked in ChatGPT or Gemini?
Can I check your numbers myself?
Does a higher score mean more leads?
See what the engine says about you.
The free scan runs the same pipeline described on this page and gives you the direct answer it counted, word for word. If you want the full read on where the gaps are, the audit is the next step.