Research

What AI tools actually read on a web page: a server-log test

I planted six made-up prices on a web page and asked five AI tools about it. Only the two in the page text came back, and the server logs show why.

What AI tools actually read on a web page: a server-log test

Key findings

Bottom line: four of five AI tools reported the prices in the page text and nothing else. None of them looked at llms.txt or the Markdown copy, even though three of them read llms.txt when I asked them to open it directly. Perplexity never fetched the page at all.

In this AI crawler test, I planted six made-up prices in six different places on one web page and asked five AI tools what the page charged, three times each, on November 2, 2026. On November 5, I ran a positive control: I asked each tool to open llms.txt and the Markdown copy directly.

  • Only prices in the page text came back. In 15 runs across five AI tools, no tool reported a price that appeared only in JSON-LD schema, llms.txt, a Markdown copy of the page, or text added by JavaScript (0 of 15 for each).
  • A closed accordion didn't hide anything. ChatGPT, Google AI Mode, Claude and Gemini each reported both page-text prices in all three runs, including the one in a closed accordion (12 of 12 runs).
  • The tools could read llms.txt. They just didn't look. No AI agent requested llms.txt or the Markdown copy during the test, even though llms.txt sat at the standard /llms.txt location and the Markdown copy was linked from the page. When I asked directly, ChatGPT, Google AI Mode and Claude each fetched llms.txt and reported its price.
  • Perplexity never fetched the page. Perplexity couldn't load the page in any of its three runs, and the server logs show it never sent a request for it, even while its crawler fetched other, linked pages on the same site. In the control, it couldn't open either file.
  • Repeat runs didn't fetch again. ChatGPT and Claude each fetched the page once, on the first run, and made no new fetch on the two repeat runs. In the control, a fresh Claude chat with memory off answered correctly without sending a new request.

Results from the November 2 test, plus a positive control on November 5. A dated update will be added here after a week of daily log checks.

The moment that surprised me most: Perplexity told me it had opened a URL directly, and my server logs show it never sent a request for it.

What each AI tool read

Grid of results for six planted prices across five AI tools: ChatGPT, Google AI Mode, Claude and Gemini each reported the Pelican (plain HTML) and Osprey (closed accordion) prices in 3 of 3 runs, no tool reported Heron (JavaScript), Egret (JSON-LD schema), Cormorant (llms.txt) or Kittiwake (Markdown copy), and Perplexity couldn't load the page
Which of the six prices each AI tool reported, three runs per tool on November 2, 2026. Only the two prices in the page text came back.
Plan (where it lived)ChatGPTGoogle AI ModeClaudeGeminiPerplexity
Pelican (HTML)3/33/33/33/30/3
Osprey (closed accordion)3/33/33/33/30/3
Heron (JavaScript)0/30/30/30/30/3
Egret (JSON-LD schema)0/30/30/30/30/3
Cormorant (llms.txt)0/30/30/30/30/3
Kittiwake (Markdown copy)0/30/30/30/30/3
Said the page couldn't be loaded0/30/30/30/33/3

The two prices in the page text came back every time. ChatGPT, Google AI Mode, Claude and Gemini reported Pelican and Osprey in all 12 of their runs, so hiding a price in a closed accordion made no difference here, as long as the text was in the HTML.

Nothing outside the page text came back, including the schema price, which sat in the HTML but only inside a script tag. Heron, Egret, Cormorant and Kittiwake were each reported in 0 of 15 runs. Claude said outright that it had fetched the "server-rendered HTML."

Every tool that reached the page repeated its note that the prices weren't real, and none of them made up a price for the missing plans.

Claude's answer listing the Pelican plan at $47.13 and the Osprey plan at $38.57, repeating the page's note that the prices aren't real, and saying it fetched the page as server-rendered HTML
Claude's answer on November 2, 2026 (run 3). It reported the two HTML prices and said it fetched the page as server-rendered HTML.
Google AI Mode answer listing the Osprey and Pelican plans and noting the prices aren't real, with the test page listed three times in its sources panel
Google AI Mode's answer on November 2, 2026 (run 3), with the test page in its sources panel while the page wasn't in Google's index.
ChatGPT answer listing the Pelican and Osprey plans, with an ad for a GEO platform shown underneath
ChatGPT's answer, logged out, on November 2, 2026 (run 3), with an ad for a GEO tool underneath.

What the server logs showed

The answers only tell you what a tool said. The access log shows what it actually asked the server for. These are the requests for the test page and its files during the test, not counting my own browser.

Time (PT, Nov 2)User agentRequestedIP check
10:06:12 pmChatGPT-UserTest page, once (run 1 only)Not verified
10:07:58, 10:13:31, 10:17:48 pmGoogle-LensTest pageIn Google's published user-triggered fetchers range
10:08:40 pmClaude-UserTest page, once (run 1 only)Not verified
10:10:44 pm"Google"Test pageGoogle-owned block, but not in Google's published lists, so unverified
During the testAny AI agentThe JavaScript-loaded file, llms.txt or the Markdown copyNo requests

ChatGPT and Claude each fetched the page once, on the first run. On runs two and three they answered without a new request. The logs can't show where those answers came from. The likeliest explanation is a copy cached from the first fetch, but I can't confirm that. The November 5 control points the same way for Claude: a fresh incognito chat, with no memory, answered correctly with no new request, about 16 minutes after an earlier fetch of the same file.

Google fetched the page live while it wasn't in Google's index. The three Google-Lens requests came from IPs inside Google's published list of user-triggered fetchers, and Google AI Mode's sources panel pointed to the page even though a site: search returned nothing. None of those fetches picked up the JavaScript price.

Nobody but me ever requested the file the JavaScript loaded the Heron price from. No AI agent requested llms.txt or the Markdown copy either. From October 31 until the control on November 5, llms.txt was requested only by SEO tools (Semrush's site audit bot, SEOJuice, Piperic and Keywords Everywhere), a web survey bot, a fetch-cache bot and once by an ordinary Firefox browser on Android.

A user-agent name on its own doesn't prove anything. The morning after the test, a scanner at 35.203.82.17 sent 250 requests in a few minutes, rotating through the ChatGPT-User, Perplexity-User, PerplexityBot and Claude-User names while looking for configuration files and keys. Its IP isn't in Perplexity's published lists. If you count AI bot visits from user agents alone, scanners like this end up in your numbers.

Could they have read llms.txt?

A zero only means something if the tool could have found the price. So on November 5, I asked each tool to open llms.txt and the Markdown copy directly, and checked the access log for each request.

Toolllms.txt (Cormorant)Markdown copy (Kittiwake)Requests logged
ChatGPTReported $53.94Said the file had an unsupported content typeBoth files
Google AI ModeReported $53.94Said the URL was inaccessible, then reported $29.67Both files
ClaudeReported $53.94Reported $29.67Both files
PerplexityFetch failedFetch failedNone
GeminiErrorErrorNone

Three of the four tools that read the test page also read llms.txt when I asked. They just didn't check it when I asked about the page. The file was at the standard location and linked to the test page, and no tool went looking.

The Markdown copy was messier. ChatGPT downloaded the whole file, then said it couldn't use it because of its content type. I served it as text/markdown, which is the official file type for Markdown, and ChatGPT's tool turned it down. A site that serves .md files as plain text might get a different answer. Google AI Mode fetched the file three times and reported the right price while saying the URL was inaccessible, so its own account of what it did was wrong.

Gemini failed on both files, and on the HTML test page it had read correctly on November 2, with no request reaching the server. That looked like a problem on Gemini's side that morning, so I count its control as inconclusive, not as a no.

Why Perplexity couldn't see the page

Perplexity only answered from pages its own crawler had already visited. When I gave it a URL it hadn't crawled, it said the page wouldn't load, and it never sent a request to fetch it.

The site wasn't blocking it. Genuine PerplexityBot, from IPs in Perplexity's published range, visited other pages on the site during the test: at 10:07 pm (the services page and robots.txt), around 10:17 pm, and from 10:38 to 10:39 pm. It never asked for the test page, because nothing linked to it.

Perplexity documents a separate agent, Perplexity-User, that visits pages when a user asks about them. That's the one that should have shown up, and there were no requests from its published IPs all day. To rule out my own site, I also set up a request-logging URL on a separate host (webhook.site), which records every request it gets. Then I asked Perplexity five more ways, each in a fresh thread.

How I askedURLWhat Perplexity saidRequest logged
The bare URL, with no questionTest pageThe direct fetch failedNone
"What plans are listed at [URL]?"Test page with a unique query stringBoth versions of the URL failed to loadNone
"What does [URL] say about pricing?"The site's SEO services page, with a unique query stringAnswered with that page's pricesNone
"Fetch this page live right now and quote its exact text"Test page with a unique query stringQuoted its tool's error: "Failed to fetch content"None
"What does this page say?"A unique webhook.site URLCouldn't retrieve the contentsNone, on either host

None of them produced a request. As a control, I gave ChatGPT the same webhook.site URL, and its request arrived within seconds.

The services page is the telling one. Perplexity's crawler had already visited it, and when I asked Perplexity to open it directly, it said it had opened the URL and answered with the page's content. It did that twice, once with a unique query string added, and no request for that page reached the server either time. It was reading its stored copy.

In these runs, pasting a link didn't make Perplexity read a new page. A page has to be crawled first, which means linked from somewhere PerplexityBot visits. And asking an AI tool whether it read your page isn't a test. Only the logs can tell you that.

Perplexity answer saying it couldn't verify the plans or prices because the URL failed to load and a search found no indexed copy
Perplexity's answer on November 2, 2026 (run 1). It said the URL failed to load, and the server logs show it never requested the page.

Limitations

This is one page on one small site, tested on one evening with a short control three days later, with one account per tool, from Vancouver. ChatGPT was logged out and Perplexity used its default Search mode. The control added AI requests for llms.txt and the Markdown copy to the logs, so the finding that no AI agent asked for them covers October 31 to the morning of November 5. It tests what a tool could read on this page, not how often AI tools cite pages or how common any of this is across the web. The larger studies cover that side, and the results here line up with them: Vercel and MERJ found no JavaScript rendering by the major AI crawlers across their network, Ahrefs found that 97% of the llms.txt files on the 137,000 sites it tracked got no requests in a month, Ahrefs also found no AI citation lift from adding schema, and HasData's AI Crawler Block Index measured how firewalls and robots.txt affect AI crawlers. What this test adds is the request-by-request view from the server.

How to run this test yourself

  1. Make an unlinked test page with a visible note that it's a test, and plant one made-up fact in each place you want to check (plain HTML, JavaScript, schema, llms.txt, anything else).
  2. Check the page source and the rendered page, and confirm that robots.txt and your firewall allow the AI user agents.
  3. Ask each tool the same question in a fresh chat, three times, and save the exact answers and screenshots with timestamps.
  4. Ask each tool to open each file directly, so you know a zero means it didn't look rather than couldn't read it.
  5. Pull your raw access logs for the test window and match each request to a run by time.
  6. Check bot IPs against each company's published range files before you trust a user-agent name.

Methodology

Conrad Studio original research. The main test answers were collected on November 2, 2026, between 10:06 and 10:35 pm Pacific. The positive control ran on November 5, 2026, between 10:33 and 10:56 am Pacific.

Test page

I built a test page on an agency site. The page described a made-up product with six pricing plans, each named after a seabird, and each price lived in a different place.

PlanPriceWhere the price lived
Pelican$47.13A plain paragraph in the HTML (the control)
Heron$62.81Added to the page by JavaScript from a separate text file, so it wasn't in the page source
Osprey$38.57Inside a closed accordion ("More plans"), in the HTML but hidden until clicked
Egret71.29 USDJSON-LD Product schema only
Cormorant$53.94The site's llms.txt file only
Kittiwake$29.67A Markdown copy of the page only, linked from the page as an alternate version

The llms.txt and Markdown prices were where a tool following those conventions would look. llms.txt was the site's existing file at the standard /llms.txt location, with a new section that linked to the test page and gave the Cormorant price. The Markdown copy sat at the page's address with .md on the end, was linked from the page's head with a link rel="alternate" type="text/markdown" tag, and was served as text/markdown. The Egret price was in the HTML too, but only inside a JSON-LD script tag, not in the page text.

The page carried a visible note saying the prices weren't real and the page was part of an AI crawler experiment. Before running anything, I checked the page source and the rendered page, confirmed that the main AI user agents got a 200 response, and confirmed that robots.txt allowed them. The page wasn't in the sitemap or linked from anywhere else on the site, and it wasn't in Google's index at the time of the test. Not being linked matters for the Perplexity result. Not being indexed matters for Google AI Mode, which fetched the page live anyway.

Question and tools

Each tool got the same prompt in a fresh chat: "What plans does [URL] offer, and how much does each one cost?" I ran it three times per tool, from Vancouver.

  • ChatGPT, logged out
  • Google AI Mode, signed in
  • Claude (claude.ai, Opus 5.5, with web fetch)
  • Gemini Pro
  • Perplexity Pro, default Search mode

Positive control

On November 5, I gave each tool two prompts, each in a fresh chat: "Open [site]/llms.txt and tell me what plans and prices it lists," and the same for the Markdown copy. I used the same accounts and settings as the main test and matched each answer to the access log by time.

I then re-ran both Claude prompts in incognito chats, which don't use memory or past chats, to rule out answers coming from my account. Gemini returned errors on both files, so I asked it to open the HTML test page it had read on November 2. That failed too.

Answers and logs

I saved each answer's exact text with a checksum, took screenshots at the time, and pulled the raw access logs for each session. Bot IP addresses were checked against each company's published IP range files, where the company publishes one and I could load it.

For the nerds: logs are the raw SSL access log from cPanel. Google-Lens requests came from 66.249.84.138 to .140, inside 66.249.84.128/27 in user-triggered-fetchers-google.json. PerplexityBot requests came from 18.97.9.96 to .103, each listed in perplexitybot.json. ChatGPT-User (9.129.50.35 on November 2; 9.129.50.36 and .46 in the control) and Claude-User (160.79.106.x) weren't checked against published lists, so they're reported as unverified. All checks are against published range files, not reverse DNS.

let’s Work together