Every GEO tool can draw a visibility chart. That is the easy half, and it is where most of them stop.
The hard half — the only half that changes a decision — is what happened next on your own site. Your brand got named in an AI answer. Fine. Did anyone arrive? Did anyone buy anything? A chart that goes up while nothing else moves is a chart, not a signal.
BrandTrace is my attempt at the second half.
What GEO actually is
Generative Engine Optimization is SEO's awkward younger sibling. People increasingly ask ChatGPT, Gemini, Claude, Perplexity or Grok a question instead of typing it into a search box, and the answer they get names some brands and not others. If you are one of the ones not named, you do not get a rank-11 consolation prize. You are simply absent.
That makes three questions worth money:
- How often does an AI answer name you, for the prompts your customers actually ask?
- Who gets named instead of you?
- Which sources do those answers pull from — and can your pages even be read by the crawlers behind them?
Closing the loop
BrandTrace holds four signals together for the same URL, over the same window: an AI answer cited a source, an AI bot crawled that page, a session arrived from an AI surface, a conversion happened.
I want to be precise about what that claim is, because this is where GEO tooling tends to oversell. Holding four signals side by side is not the same as proving one caused the next. Causal attribution of an outcome to an action is an open problem and I have not solved it. What I can do is stop making you open four tools and eyeball whether the lines look similar.
Two consequences fall out of that:
First-party signal beats inference. Bot crawl events, Search Console data and SDK sessions are ground truth. Anything modelled or estimated is labelled as such in the UI. When there is a choice between inferring and measuring, measure.
An insight has to terminate in an action. A gap score that does not lead to a specific page to fix is an unfinished feature, not a dashboard widget.
Crawl log intelligence
Connect a Cloudflare Worker, or upload server logs, and you see exactly which AI bots fetched which pages, how often, and what status code they got back. This is the part I find most immediately useful and the part nobody else seems to bother with.
It answers embarrassing questions early. Is a robots.txt rule quietly blocking GPTBot? Did a deploy start handing 404s to a crawler for a week? Is the page you keep optimising one the crawler has never actually fetched?
None of that is inferred. It is your own log line.
The conversion side
A small pixel on your site sends page and session events to a Go ingest service. Naming a goal is one call in your own code:
brandtrace.track("signed_up")
A standard catalog plus lenient canonicalisation turns that into hard and soft conversion reporting, tied back to the session, the landing page, and the AI answer that cited it.
Answer readiness, and one decision I would defend
Being named by an AI engine and being readable by one are different problems.
Every page the crawler fetches is scored on seven signals: crawler access, structured data, readable content, entity clarity, heading hierarchy, llms.txt, and citation readiness. It is deliberately the one surface with no prerequisites — no Search Console connection, no SDK install. A crawled site is enough.
The scoring runs from a plain, un-rendered fetch rather than from the headless render. That is a deliberate choice and it is the one I would defend hardest. The question a crawler actually asks is what the HTML says before any JavaScript runs. By the time Chromium has finished, an empty single-page-app shell and a server-rendered page look identical — and a crawler does not wait for Chromium.
Not acting on things that were never there
A source report that lists citations the model invented from training data, rather than retrieved from the web, is worse than no report: it sends you off to court a publication that was never consulted.
So every source report carries a grounding summary. When its status is ungrounded, the cited URLs were generated rather than observed, and the correct action is none. The report says so rather than quietly presenting both kinds of citation as the same thing.
What is underneath
The API is FastAPI sitting on a stack of domain packages that know nothing about a web framework. Ingest is a separate Go service, because a pixel collector and a reporting API want opposite things: one must know nothing and absorb a lot, the other must know everything and be asked rarely.
Between them: Postgres, NATS JetStream, Valkey, and a Playwright crawler. Every LLM call goes through an internal gateway to OpenRouter, typed, metered and routed by plan tier — which sounds like overhead until the first time you have to explain a month of model spend per client.
The frontend is React and Vite, shipped as an installable PWA. It runs on self-hosted k3s with SOPS-encrypted secrets, traces to SpanBarn, errors to BugBarn, product analytics to FunnelBarn. The same stack every other thing I run sits on.
Built for agencies
Agencies are the shape of the buyer, so they are a first-class case rather than an enterprise afterthought: one credit pool distributed across client projects and reallocated instantly when a client churns, per-project model selection so a client who only cares about ChatGPT is not billed for five engines, multi-tenant isolation with per-seat roles, and bulk chat export so you can hand a client the raw evidence as a CSV.
What it does not do
Two things I want on the record, because the earlier version of the README claimed one of them before the code existed.
There is no brief generator, no draft-article generator, no UGC angle generator and no CMS export. The generative half is limited to what is built: a suggested title, a meta description and one self-contained answer paragraph for a flagged page.
And the agency workspace features — pitch projects and white-label — are specified but not built. They are roadmap, not behaviour.
If a GEO tool tells you it does everything, check which half it can show you.