BA AI Engineer & Full-Stack TypeScript Developer

Case study · AEO / GEO / SEO

Built to be quoted, not just ranked

More and more buying research now happens inside an assistant instead of a results page, and the assistant either quotes you or it doesn't. That turns out to be an engineering problem with a specific shape: render the facts, mirror them as typed data a machine can resolve, publish endpoints worth fetching, and attach a date and an author to every claim. This is that build for a client's rebuilt site — plus the part nearly everyone skips, which is finding out whether it worked.

7 of 7 page types reading the full metadata contract — it was 1 of 7
163 URLs in a generated sitemap, each with typed structured data
16 AI crawler user-agents named individually, on top of the wildcard
15 × 5 prompt-by-assistant citation grid, frozen so months stay comparable

Three acronyms, one job. SEO is ranking in a results page — optimised for a crawler and a ranker. AEO, and GEO which is the newer name for the same intent, is being cited inside an answer — optimised for a model at the moment it replies. They overlap by maybe eighty percent, so this isn't twice the work. It's the same work, sequenced properly, for twice the surface.

Before anything else

Why there's no logo on this page either

Same client as the other two write-ups, same rule — and on this page it costs more than usual, because the natural evidence would be screenshots of an assistant citing them by name.

The citation results are described by page type, never by URL. A cited URL is the client's domain in plain sight, and the URLs are literally the evidence field of the source material. So you'll read that a pricing guide and an FAQ answer were the pages that surfaced, which is the part that carries the lesson, and not which ones.

Everything else here is deliberately un-anonymised, because genericising it would destroy the point: the crawler user-agents, the Schema.org types, llms.txt, IndexNow. Those are conventions and vendor names, not client information, and a page about machine readability that won't say GPTBot is not worth reading.

What each substitute stands for

On this page Means Detail
the client A US travel company that sells complex multi-stop itineraries Same client as the other two write-ups. In business since the late 1980s, which matters here — an old domain with real editorial history is a different problem from a new one.
the main site Their public marketing site, rebuilt on Next.js and a headless CMS Around 163 published URLs across itineraries, supplier profiles, FAQ answers, guides, team profiles and audience hubs.
the partner site A sister editorial brand with a large archive on a separate domain Relevant because assistants cite it too — so 'were we cited' has to count both properties or it undercounts itself.
the self-serve builder Their trip-building product on a separate subdomain The identity boundary between it and the main site is why every funnel step here has to stay same-site.

The shape of the problem

“Add llms.txt” is about a tenth of it

The public conversation about this has collapsed into a single file, which is a real convention and nowhere near sufficient. What actually determines whether a model can quote you is four layers that depend on each other — and each one fails in its own specific way when it's missing.

Four dependent layers, and how the system fails without each one A stack of four bands read top to bottom as a dependency chain. First, rendered content: assistants read the page a browser would render, at the moment they answer; without it there is nothing to quote, and markup describing invisible content is a liability. Second, typed nodes mirroring the same facts, so a machine can tell what the page is and which fields carry the answer; without them the page is readable but not citable. Third, machine-readable endpoints — llms.txt, JSON catalogues, the sitemap and feeds — giving a crawler something cheap and complete to fetch; without them the content is never discovered. Fourth, provenance: dates, authors and sources on every claim; without it a claim is a rumour that a model declines to repeat. A footnote records that llms.txt, which is the whole of most public discussion about this topic, is a single item inside the third layer. WHAT A MACHINE NEEDS, IN DEPENDENCY ORDER SKIP IT AND 1 Rendered content clean, visible HTML Assistants read the page a browser would render, at the moment they answer. Nothing to quote. Markup describing content that isn't visible is a liability, not a signal. 2 Typed nodes the same facts, mirrored Structured data that says what the page is and which fields carry the answer. Readable but not citable — a machine can't tell who is claiming what, or attribute it. 3 Machine-readable endpoints llms.txt · JSON · sitemap · feeds Something cheap and complete to fetch, instead of scraping every page. Never discovered. A catalogue nothing points at may as well not exist. 4 Provenance dates, authors, sources Every claim carries when it was true, who stands behind it, and where it came from. A rumour. Undated anonymous assertions are the ones a model declines to repeat. Almost every public discussion of this topic is about llms.txt. It is one item inside layer 3.
The order is the argument. These are not four tasks you can pick from — each one is worth roughly nothing without the ones above it, which is why the build sequence matters more than any individual item in it.

The counter-intuitive one is the first layer. Rich structured data on a page whose visible content doesn't say the same thing is not neutral — it's a liability. Assistants read the rendered DOM at the moment they answer, and treat markup as a supporting signal rather than a source. So the rule that falls out of it is: emit every important fact twice — once for the reader, once as a typed node — and never let the second one exceed the first.

What got built

Ten layers, in dependency order

Sequenced so each layer stands on the one before it — structured data referencing an organisation that doesn't exist yet is a broken graph, not a head start. 8 of the 10 shipped and verified; the rest carry their real status below, because the difference between "built" and "producing output" is the whole difference between a portfolio and a receipt.

Layer What it is Why a machine cares Status
01 One origin, one entity The canonical origin as a single environment variable, everything else derived from it, and a site-wide Organization plus WebSite graph with stable ids emitted as the first structured-data block on every page. Entity consistency is how a machine decides two pages belong to the same organisation. Stable ids let every other node reference that entity instead of redescribing it — and one origin constant means a domain cutover can't leave the site quietly asserting two competing identities. Shipped + verified
02 Per-page metadata, everywhere One shared builder returning the full metadata shape — canonical, robots, OpenGraph, Twitter — wired into all seven detail types rather than the one somebody got right first. Canonical and noindex are how you stop an assistant learning the wrong URL for a fact. Half-wired, they silently do nothing, and the CMS fields behind them look functional while consuming editorial trust. Shipped + verified
03 A typed node per content type One structured-data generator per document type, each emitting a single script with a graph rather than several competing ones. The itinerary pages — the crown jewel — had none at all before this. A site-wide blob tells a consumer that a page exists. A typed node tells it what the page is and which fields hold the answer. That's the difference between 'a page about X' and an entity with extractable properties. Shipped + verified
04 Index pages as lists CollectionPage plus ItemList on every index, capped to the top entries with the true count declared and an explicit ordering. Without it an index is unstructured cards. With it, an assistant answering "which X does this company recommend" can reproduce a ranked list and attribute it. Ambiguous ordering makes a reproduced list wrong in a way nobody catches. Shipped + verified
05 Sitemap and a named crawler policy A CMS-generated sitemap with per-entry last-modified dates, and a robots file naming sixteen AI crawlers explicitly alongside the wildcard. Sitemaps are how crawlers find pages without waiting to follow links, and the last-modified dates feed the freshness signal assistants weight. The named allowlist is a statement of intent to operators who increasingly read silence as ambiguous. Shipped + verified
06 llms.txt A plain-text root file, around 12KB, describing what the site is, where every machine-readable endpoint lives, and one summary block per significant page — capped so it stays ingestible in a single fetch. Fetched directly by several assistants when they meet a domain. It's the one artefact that says what a site is FOR before a model starts inferring it from page titles. Shipped + verified
07 Endpoints that publish judgement Three JSON endpoints under a versioned envelope with a generated timestamp, permissive CORS and a long shared cache — carrying the parts a competitor can't regenerate: a four-level supplier trust tier with strengths and weaknesses, and per-stop transfer-risk disclosure on itineraries. A model can already assemble a list. It cannot synthesise dated, opinionated judgement from ten other sources. CORS matters more than it looks — without it, browser-based LLM tooling and MCP servers simply cannot read these at all. Shipped + verified
08 Freshness as a field Per-answer last-updated dates mapped onto the structured data, and dated claims that render an "awaiting refresh" badge once they pass ninety days. Recency is one of the few properties a retrieval layer can check cheaply, and an undated claim is hard to cite safely. Surfacing staleness in the UI is what makes the refresh actually happen. Shipped + verified
09 Rich-result surfaces on guides FAQ and step-guide emitters, with the step emitter gated on a minimum heading count in code because the format is rejected below three. Both are rich-result eligible and both are what an assistant reaches for on a direct question. The gate exists so the threshold is enforced by the build rather than by somebody remembering it. Live, never fired
10 Launch hooks and crawler verification Search-console verification, sitemap submission, an IndexNow ping on publish, a structured-data validator in CI, and log analysis confirming AI crawlers actually fetch the endpoints. The half that tells you whether the other nine worked. IndexNow pushes new URLs to Bing, Yandex and Perplexity in minutes rather than waiting to be crawled — and the log check is the only thing that can prove a crawler ever arrived. Specified, not built

Two of those statuses are uncomfortable, which is why they're in the table rather than in a footnote. The FAQ and step-guide emitters are correct, deployed, and have never produced a single line of output, because no document has been tagged in the CMS to trigger them. And the entire launch-hook layer is written up and undeployed. A checklist would happily record both as done — that is precisely what checklists are bad at.

The type mapping — the part that transfers to any content site

Content type Emits Why that one
The crown jewel — a priced, multi-stop itinerary TravelAction + Article + ItemList Every stop is a Place carrying its IATA code, so a question naming two specific cities is answerable from structure instead of by parsing prose. This page type had zero structured data before the audit.
A dated factual claim inside any document Claim The assertion, the date it was true, and the source — rendered visibly AND emitted as a node. The highest-value emission on the site: dated and sourced beats confident and anonymous.
FAQ answer FAQPage (QAPage when it's a single question) Rich-result eligible, and the shape an assistant reaches for on a direct question. Each answer carries its own last-updated date.
Long-form guide Article, plus HowTo at three or more steps The threshold is enforced in code — the format is rejected below three steps, so emission is gated on the heading count.
Any index page CollectionPage + ItemList With an explicit order. Ambiguity here produces a confidently mis-ranked list.
Supplier profile The most specific Organization subtype available Specificity is free and it disambiguates. A generic Organization competes with every other organisation on the internet.
Author Person with knowsAbout[] Anonymous content is close to invisible to citation graphs. The expertise array is the machine-readable form of why this person is worth quoting.
Every detail page BreadcrumbList Tells a machine where a fact sits in the site's structure. The trailing leaf drops its item — Schema.org says don't self-link.
Screenshot pending

Structured-data validation, one page of each type

The pre-launch pass: one representative URL per content type through the rich-results tester and the schema validator, confirming each expected type is detected with zero errors. Warnings on optional fields are fine; errors are not.

The mechanism

What actually has to happen for a URL to end up in an answer

Worth drawing, because “AI SEO” is usually sold as a vibe. A question arrives, the assistant retrieves, it needs specific properties present before it will repeat a claim, and only then does a link appear — and one step in that chain has never been verified.

How a published page turns into a cited URL, and the step nobody has verified A left-to-right path. Someone asks an assistant a question in plain language. The assistant runs a retrieval step and fetches three things from the site: llms.txt, which says what the site is for; the rendered page, which is what it actually says; and the JSON catalogue, which is the same content typed. That fetch arrow is drawn dashed and amber and labelled unverified, because no server-log analysis has been run to confirm that any AI crawler has ever made these requests. Next, before the assistant will repeat a claim it needs four properties present: a date the claim was true, a named source, an author with stated expertise, and a stable URL to point at. Only then does a URL appear in the answer as a citation. The diagram ends on that citation, with a note that the measurement panel is what turns the final step from an assumption into a number. QUESTION → RETRIEVAL → REQUIREMENTS → CITATION someone asks in plain language, of an assistant, not a search box it retrieves — and fetches llms.txt what this site is for the rendered page what it actually says /data/*.json the catalogue, typed unverified — no server-log analysis has been run before it repeats a claim a date it was true a named source an author with stated expertise a stable URL to point at missing any one and it paraphrases instead a URL appears in the answer, attributed A deep page beats the homepage — the work landed on the content, not the brand. the 15-prompt panel — the only step that produces a number
The mechanism, and where the evidence stops. Everything left of the amber arrow was built and can be inspected. The amber arrow itself is an assumption until somebody reads the server logs — which is why it is drawn as one.

Crawler access, done deliberately

  • Sixteen AI crawlers named individually — GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, PerplexityBot, Google-Extended, CCBot and the rest — on top of a wildcard that already allows them.
  • The redundancy is the point. Operators increasingly read silence as ambiguous, and it makes the site's posture reviewable in one file rather than inferred from an absence.
  • It's a maintenance commitment, not a one-off. The operator list moves constantly, so the re-check interval is written down next to it.
  • Disallow is not noindex. Disallow blocks crawling; a page with inbound links can still be indexed, now without the crawler ever having read the content that would have made the summary accurate. Two levers, two jobs.

Publish the judgement, not just the catalogue

The endpoints could have shipped a safe catalogue — names, prices, destinations. The fields that actually went out are the opinionated ones: a four-level supplier trust tier with strengths and weaknesses, and a per-stop transfer-risk disclosure saying where a connection is likely to go wrong.

That's decades of consultant judgement, and the instinct is to keep it behind a conversation.

The instinct is wrong. A model can already assemble names and prices from ten sources — publishing yours adds nothing and gets you averaged into a paragraph. What it can't synthesise is dated, opinionated judgement attributable to someone. The risk of being copied is smaller than the risk of being irrelevant to the answer.

Screenshot pending

The llms.txt endpoint

Plain text, around 12KB, capped so an assistant can ingest it in one fetch: what the site is, where every machine-readable endpoint lives, and a one-block summary per significant page.

The half nobody does

An AEO claim you can't falsify isn't a claim

Most of this industry sells work whose success can't be checked. The fix isn't clever — it's a frozen set of questions a well-informed assistant ought to answer using your site, run under controlled conditions, scored the same way every month.

13.3% of buyer-intent prompts returned a link to the client's own pages
1–6 AI-assistant referrals per day, flat across the relaunch
4 distinct owned URLs surfaced across the whole baseline run
Where the assistant cited the site, grouped by what the question was trying to do Five rows, one per intent cluster, each containing three prompts drawn as three cells; a filled cell means at least one link to the client's own pages appeared in the answer. Route and itinerary design scored one of three. Cost and pricing scored one of three. Ticket rules and alliances scored zero. The cluster asking who a traveller should book a complex trip with — the only cluster written to carry buying intent — scored zero of three. Audience-specific planning scored zero. In total 2 of 15 prompts, 13.3 percent, on one assistant with browsing enabled, one run per prompt. The pattern is that both hits are informational questions and the commercial question returned nothing. ONE ASSISTANT · BROWSING ON · ONE RUN PER PROMPT 2 / 15 prompts returning an owned link A Route and itinerary design 1 of 3 B Cost and pricing 1 of 3 C Ticket rules and alliances 0 of 3 D “Who should I book this with?” the only cluster with buying intent in it 0 of 3 E Audience-specific planning 0 of 3 three prompts per cluster · filled = at least one owned link in the answer Both hits are informational. The question that would actually send someone a customer returned nothing. Useful, and not the same thing as a marketing win — which is exactly why the prompt set separates the two.
One assistant, one run per prompt, a few weeks after launch. Engines are non-deterministic and vary by region, so a single cell is not signal; the frozen set exists so the trend can be. Low citation this early is the expected reading rather than a failure — the panel was built to measure a climb, not to pass or fail on the day.

The two hits are both informational. The cluster written specifically to test whether an assistant will name a specialist — the only one with buying intent in it — returned nothing at all. That's a genuinely useful thing to learn in month one: the markup is doing informational work, and the commercial question — the one that would actually send someone a customer — is a different and harder problem than structured data. You only find that out if the prompt set was written to separate them before you started.

The protocol — six rules, and each exists because breaking it silently ruins the number

Rule What it's protecting against
Browsing on, always With retrieval off you're testing the model's training memory, which predates the work. You'd measure near-zero no matter how good the implementation is, and the result would be meaningless.
Memory, personalisation and custom instructions off Otherwise you're measuring your own browsing history. Temporary-chat modes exist for exactly this, and using one is the difference between a test and a flattering anecdote.
One prompt, one fresh session Ask the fifteen as follow-ups in a single thread and each answer contaminates the next. This is the mistake that makes most informal AEO testing worthless.
Verbatim, and never hint the brand The whole test is whether the site surfaces unprompted. Naming it in the question converts a measurement into a lookup.
Stamp date, engine, mode and rough location Several assistants ground differently by region and change without notice. An unlabelled result can't be compared to next month's.
Count distinct owned URLs, and keep the URLs The count is the score; the URLs are the evidence. A deep page is a much stronger signal than the homepage. A brand mention with no link is recorded separately, because it's a different outcome.

Fifteen prompts across five assistants is sixty to seventy-five runs, roughly ninety minutes in one sitting. That cost is what makes the automation question interesting rather than obvious — see hard part four.

Screenshot pending

A citation test in progress

One prompt, one fresh session, browsing on and memory off, sources panel expanded. The evidence is the URL list — the score in the grid is only a count of it.

Why a click-through funnel can't answer this on its own. Assistant referrals to this site run at one to six a day and were flat across the relaunch. Detection is split across two incompatible mechanisms — some assistants tag the link they hand out, others send nothing but a referrer — so the funnel is structurally a floor rather than a count. A metric that small and that lossy cannot detect a change in citation behaviour, which is exactly why the prompt panel exists as a separate instrument instead of another dashboard tile.

The hard parts

Five decisions where the obvious answer was available and wrong

Anyone can list Schema.org types. What's worth reading is the point where the cheap option was right there and I can tell you exactly what it would have cost.

01

AEO is not a file you add at the root

What was wrong

The entire public conversation reduces to "add llms.txt". It's a real convention and it's roughly a tenth of the work. A site that adds it and nothing else has published a table of contents for a book with no pages.

What I did

Build it as four dependent layers. Content rendered as clean HTML, because assistants read the visible page at inference time. The same facts mirrored as typed nodes, so a machine can resolve who is claiming what. Machine-readable endpoints — the JSON catalogue, the sitemap, the feeds, llms.txt — so there's something efficient to fetch. And freshness, authorship and provenance attached at every level, so a claim has a date and a name behind it.

Why the obvious answer was worse

Skip a layer and the system rots in a specific, predictable way. Structured data with no visible content doesn't get cited, because assistants read the rendered DOM and treat buried markup as a supporting signal rather than a source. A JSON catalogue nothing points at never gets discovered. A dated claim with no author is a rumour. It isn't a checklist, it's a dependency chain — which is why the build order matters more than the individual items.

02

The most important page type had no structured data at all

What was wrong

The audit found correct markup on secondary pages and none whatsoever on the crown jewel — the priced multi-stop itinerary, the most differentiated thing the client publishes and the closest to revenue. An assistant asked for a route through two specific regions had to read English prose and guess.

What I did

Build the crown jewel's graph first, not last: the itinerary as a bookable action, an Article wrapper carrying author and date and price, and a list of stops where each is a place with its airport code. Then work outward to the secondary types.

Why the obvious answer was worse

It isn't a one-off oversight, it's a pattern with a cause: the most important content type is the one most likely to be missed, because everyone assumes someone senior already handled it. The defence is to survey what every page type actually emits before writing a line of new markup, and to start from the page closest to revenue rather than the one easiest to mark up.

03

Publishing the judgement instead of protecting it

What was wrong

The endpoints could have published a safe catalogue — names, prices, destinations. The genuinely valuable fields are the opinionated ones: a four-level trust tier per supplier with strengths and weaknesses, and a per-stop transfer-risk disclosure that tells a traveller where a connection is likely to go wrong. That's consultant judgement built over decades, and the instinct is to keep it behind the conversation.

What I did

Publish it, in typed fields, in public — as the deliberate centrepiece of the machine-readable layer rather than an afterthought.

Why the obvious answer was worse

A model can already assemble a list of suppliers and prices from ten sources; publishing yours adds nothing and gets you averaged into a paragraph. What it cannot synthesise is dated, opinionated judgement attributable to someone. Withholding the judgement protects an asset that stops being an asset the moment the answer arrives without you in it — the risk of being copied is smaller than the risk of being irrelevant to the answer.

04

Automating the citation test would have measured a different thing

What was wrong

Sixty to seventy-five manual runs a month is exactly the toil an engineer wants to automate, and the automation is easy — a cron, SDKs already in the codebase, a table to write to. It was specified and then deliberately not built.

What I did

Write down why, and defer it as a second metric rather than a replacement. The consumer products do live retrieval against their own search indexes; the plain APIs mostly don't browse and hit different infrastructure. An automated sweep would produce a real, trendable number that doesn't reproduce the manual one and can't be compared to it.

Why the obvious answer was worse

The failure would have been invisible and permanent: you automate, the numbers move, and you spend a year interpreting a trend in a metric that isn't the one you thought you were tracking. Two incomparable series both labelled "citation rate" is worse than one series that costs ninety minutes a month.

05

A framework convention silently overrode the editor's choice

What was wrong

Generated social cards shipped for every page. So did a CMS field letting an editor upload a custom card per document. Both worked. The framework resolves file-based metadata ahead of programmatic metadata, so the generated card always won — the editorial upload survived only in the structured data, invisible to every social platform. Nothing errored, and the field looked functional in the CMS.

What I did

Name it as a known issue with its real impact, write out the three options with their trade-offs, and recommend one — have each generated card check for an editorial image and defer to it — rather than quietly accepting the framework default because it had already shipped.

Why the obvious answer was worse

The tempting move is to say nothing: it renders, it looks good, and the only person who notices is the one editor who uploads an image and can't work out why it never appeared. That's how a CMS field becomes decorative, and a decorative field is worse than a missing one because it spends trust every time someone uses it.

What this isn't

The limits, said out loud — starting with the biggest

This field is full of unfalsifiable claims, so a page about it had better be precise about where its own evidence stops.

Nothing here proves a crawler ever arrived

The biggest hole, so it goes first. The plan says to confirm from server logs that the AI crawlers actually fetch llms.txt and the JSON endpoints, with a thirty-day tripwire if they never do. That log analysis has not been run. Every claim on this page is about what was built and published — none of it is evidence that a machine came and read it.

The citation rate hasn't gone up — there's one reading

The baseline was captured on one assistant a few weeks post-launch, which is early by design and not yet a trend. The honest claim is a method plus a starting number, not a lift. And it was meant to be captured BEFORE launch, which is the one piece of measurement design that got away from us: it slipped, so the "before" is really a slightly-after.

Two emitters are live and have never fired

The FAQ and step-guide emitters are correct and deployed, and no published document has yet been tagged in the CMS to trigger them. Code shipped, output zero. That's a content-operations gap rather than an engineering one — and exactly the kind of thing a checklist would happily record as done.

The launch hooks are specified, not built

Search-console verification, sitemap submission, the IndexNow ping and the CI validator are written up and none are deployed. They're on the page as a pending layer because leaving them out would misrepresent how complete the system is.

Nobody can promise you a citation

There's no ranking API, no guaranteed placement, and the engines are non-deterministic and change without notice. What's buildable is the substrate — clean rendered content, a coherent entity graph, machine-readable endpoints, dated sourced claims — plus a measurement that tells you honestly whether it's working.

This decays without content ownership

Freshness is weighted heavily, and stale dates don't merely stop helping — they cost citation weight. If nobody owns refreshing dated claims on a cadence, the whole investment rots. That's flagged as a decision to make before writing code, not a nice-to-have afterwards.

Where every number on this page came from

Figure Kind Source, window and denominator
7 of 7 page types reading the full metadata contract — it was 1 of 7 shipped An audit found one detail route wired to the CMS metadata field group and six returning title and description only. Canonical URL, share image and the index flag were fields an editor could set and nothing consumed. All seven now go through one shared builder.
163 URLs in a generated sitemap, each with typed structured data counted Counted at build across itineraries, supplier profiles, FAQ answers, guides, team profiles, audience hubs and the static surfaces. Generated from the CMS with a static skeleton fallback, so an unreachable CMS cannot 500 the sitemap mid-launch.
16 AI crawler user-agents named individually, on top of the wildcard counted GPTBot, ChatGPT-User, OAI-SearchBot, ClaudeBot, Claude-Web, PerplexityBot, Perplexity-User, Google-Extended, CCBot, cohere-ai, Applebot-Extended, Bytespider, DuckAssistBot, Diffbot, meta-externalagent, Amazonbot. Redundant against the wildcard by construction — a legible policy for crawler operators, not a functional unblock.
15 × 5 prompt-by-assistant citation grid, frozen so months stay comparable counted Fifteen prompts in five intent clusters, run verbatim, never naming the brand, in a clean session with browsing forced on. Specified across five assistants. The grid is the instrument, not the result — one column is complete and there is no trend in it yet.
13.3% of buyer-intent prompts returned a link to the client's own pages measured 2 of 15 frozen prompts, one assistant with browsing on, one run per prompt, evidence captured July 2026. One engine of the five in the panel — the others are unrun or partial. Cells count distinct owned URLs, not brand mentions. A floor to climb from, not a result of the markup work.
1–6 AI-assistant referrals per day, flat across the relaunch measured Two matched Monday-to-Thursday windows either side of the June 2026 cutover, using per-assistant source detection. This is the honest size of the prize as measured today — and the reason the citation panel exists at all, because a click-through metric this small cannot detect anything.
4 distinct owned URLs surfaced across the whole baseline run measured One from a route-planning prompt, three from a pricing prompt. All were deep pages — an FAQ answer and a cost guide — rather than the homepage, which is the signal the method says matters: it means the work landed on specific content, not on brand recognition.

FAQ

What people ask about AEO

Starting with whether it is a real discipline and ending with whether it worked here.

Is AEO just SEO with a new name?

They overlap heavily, and pretending otherwise would be selling you the same work twice. The difference is the target: SEO ranks a link in a list of results, AEO tries to make your page the thing a model quotes when nobody sees a results page at all.

In practice it is the same work sequenced properly, for twice the surface — structured data that agrees with the visible text, endpoints worth fetching, and a date and an author on every claim.

Can you guarantee an assistant will cite us?

No, and anyone who does is guessing. There is no ranking API, no guaranteed placement, and the engines are non-deterministic and change without notice — the same prompt can return different sources on two consecutive runs.

What can be guaranteed is that the surface is built correctly and that you will know whether it is working, which is more than most people in this market can offer.

Is publishing an llms.txt file enough?

It is roughly a tenth of it. The file is a real convention and worth having, but a model that arrives at a page whose visible content does not say the same thing as its markup treats the markup as a liability rather than a source.

The layers that actually determine whether you can be quoted sit underneath: one canonical origin, an entity graph with stable identifiers, typed structured data per content type that matches the rendered page, and machine-readable endpoints carrying something a model could not have assembled on its own.

How would we know whether any of it worked?

A frozen set of questions a well-informed assistant ought to answer using your site, run verbatim in clean sessions under a written protocol, scored the same way every month. Never naming the brand in the prompt, counting distinct owned URLs rather than brand mentions, and keeping the URLs as the evidence.

A click-through funnel cannot answer this on its own. Assistant referrals to the site in this write-up run at one to six a day and detection is split across two incompatible mechanisms, so the funnel is structurally a floor rather than a count.

Did it work for the client in this write-up?

There is one reading, it is a floor, and it is not evidence the markup did anything — the baseline was captured slightly after launch rather than before, which is the one piece of measurement design that got away from us.

The largest hole is named on the page: nothing here proves a crawler ever arrived. Confirming from server logs that the assistants actually fetch the endpoints has not been run. Every claim on that page is about what was built and published, not about what a machine came and read.

Next step

If people are asking an assistant about your category

The build transfers to any content site with a headless CMS, and it's mostly a sequencing problem rather than a volume-of-work problem. The measurement transfers even more cleanly — a frozen prompt set costs an afternoon to design and tells you, monthly, something almost none of your competitors can find out about themselves.

An audit is the ten layers checked against your site, plus a prompt set built for your category and run once so you have a baseline — which is the thing you can't retrofit later.