Research · measured 31 July and 2 August 2026

Thirty-six of forty UK service websites publish their prices. None publishes one a machine can read.

We read 40 UK service business websites — 10 dental practices, 10 physiotherapy clinics, 10 veterinary practices, 10 driving schools, one city each — the way an AI assistant reads them, and recorded what a machine can actually get out of each one. This page is the full result: the question, the method exactly as run, the numbers per vertical, the eight ways a published price defeats an extractor, what consolidation does to one of the four verticals, and all 40 rows.

This page began as 20 sites in two verticals and now carries 40 in four. The first two verticals were measured on 31 July 2026 and nothing about them has been restated; the veterinary and driving-school rows were measured on 2 August 2026 and are marked as such. Where the four-vertical number differs from the two-vertical one, both are shown.

The question

When a customer asks an AI assistant "what does this cost, and can you book me in?", the assistant has to do two things: read a price off the business's website, and find somewhere to send the request. We measured whether either is possible, per site: is a price published; is any price published in structured data a machine reads reliably; is there any endpoint an assistant can send a request to; and is a real llms.txt served.

The four verticals were chosen because they are the ones that habitually name a service and state a price. That is the precondition for everything else here — a business that will not quote in public cannot be misread by a machine, because there is nothing to read.

Method, exactly as run

  • 40 sites: 10 dental practices, 10 physiotherapy clinics, 10 veterinary practices, 10 driving schools. One UK city per site, ten cities per vertical, no city reused inside a vertical.
  • The search query per city was the vertical and the city only — "dental practice Leeds", "physiotherapy clinic Norwich", "veterinary practice Cardiff", "driving school Exeter". No price words, so the sample is not biased toward sites that publish prices. That makes the 36 of 40 a property of the verticals, not of the query.
  • The first qualifying result on the business's own site was taken. Directories, Facebook pages, NHS trusts and university clinic pages were skipped, and each skip was recorded.
  • Substitutions happened only when a site was unreachable after two attempts, and each is recorded: Manchester (timeout, re-searched), Sheffield (401 bot wall at position 1, 403 at position 2, took position 3), Nottingham (timeout at position 3, took position 6), Liverpool (403 at position 1, directory at position 2, took position 3), Exeter (position 1 skipped because that chain was already sampled in Plymouth).
  • Each site's pages were fetched and read as text and as JSON-LD. The served HTML was also searched for prices held only inside JavaScript payloads, which is how three sites turned out to publish figures no plain reader sees (trap 7 below).
  • llms.txt was checked by fetching it and inspecting the content, not just the status code — which mattered (trap 5 below). Where a site declared an agent endpoint, exactly one read-only JSON-RPC tools/list call was made to enumerate what it offers. No tool was ever called.
  • The dental and physiotherapy rows were measured 31 July 2026; the veterinary and driving-school rows 2 August 2026. Websites change; every claim on this page is scoped to those dates.

Headline numbers

Publish a price for at least one service36 of 40
Publish a full fee schedule, not just examples21 of 40
Publish any price in structured data a machine reads reliably0 of 40
Have anywhere an assistant can send a request0 of 40
Serve a real llms.txt5 of 40
Declare an agent endpoint a machine can call2 of 40
Measure Dental Physio Vet Driving All 40
Publish a price10/108/109/109/1036/40
— a full fee schedule9/103/106/103/1021/40
Price in structured data0/100/100/100/100/40
Price only in a JavaScript payload0/100/101/102/103/40
Emit any JSON-LD at all9/107/107/106/1029/40
Serve a real llms.txt3/101/100/101/105/40
Answer /llms.txt 200 with HTML0/101/100/101/102/40
Declare an agent endpoint1/100/100/101/102/40
Can receive a request0/100/100/100/100/40
Owned by a chain or group2/102/107/104/1015/40

Every figure here was recomputed from the row data rather than carried forward from the earlier two-vertical write-up. The one number worth stating twice: the 36 of 40 that publish a price are 33 whose price is plain text in the HTML a fetch receives, plus 3 whose price exists in the served page only as a JavaScript payload. A human sees all 36; a plain reader sees 33.

29 of the 40 sites emit JSON-LD, and not one of them puts a usable price in it. One dental site marks up its whole treatment catalogue in schema.org Offer and OfferCatalog terms and leaves every price field empty; two physio and two veterinary sites carry markup whose only pricing is a priceRange of "$$" or "£". The longest fee schedule in the sample runs to 103 priced lines, and one veterinary group publishes 92 — all of them invisible to a machine.

The zero that matters least is the structured-data one, because it is a formatting problem and a business can fix it in an afternoon. The zero that matters is the last row: across 40 sites in four verticals, on two measurement dates, there is nowhere an assistant can send a customer. Two sites do expose something an agent can call, both put there by their website platform rather than by their owner, and neither offers a tool that submits anything — that finding has its own page.

Eight ways a published price defeats a machine

"Publishes a price" hides a lot. These are the concrete failure modes, each measured on a site in the sample; rows are identified by city and vertical below. The first six came out of the dental and physiotherapy sample; traps 7 and 8 are new, and neither appeared in the first twenty sites.

  1. The header-only pound sign

    One dental fee table (Southampton) lists "New patient examination 65" under a column headed "Fees From (£)". The pound sign exists once, in the header; every number in the table is bare. A text extractor that reads rows without the header reads unitless numbers — and misses that every figure is also a From-price.

  2. From-ranges that read as fixed prices

    Six of the eighteen priced sites quote their headline service as a From-price — "From £69.00", "from £39.50", "starting from £60", "start from: £45". A From-price is not an error; it is how these businesses actually price. But flattened to text it reads as a fixed number, and an assistant that repeats it as one has quoted a floor as a total.

  3. The price that only exists inside the booking flow

    One physio clinic's initial-assessment price (Plymouth) appears only inside its online booking application, not on the clinic page an assistant would read. The page a machine fetches has no price on it at all; the number is real but lives behind a click into a web app.

  4. Structured markup with the value missing

    The one dental site that models its services in schema.org Offer terms (Sheffield) shipped every price field empty on the day of measurement. Two physio sites (York, Aberdeen) publish only a priceRange of "$$" or "£". The schema is there; the number is not. A parser that trusts the markup gets a catalogue of services that all cost nothing in particular.

  5. The fake llms.txt

    One physio clinic's /llms.txt (Aberdeen) returns 200 and serves an ordinary HTML page. Any presence check that trusts the status code overcounts. We counted 4 real llms.txt files in 20 — after reading them.

  6. Bot walls, and who runs them

    Three sites hit during sampling refused a plain non-browser fetch twice and had to be substituted out: two chain-operated dental sites in the Sheffield search (a 401 bot-verification wall and a 403) and one national physio chain in the Liverpool search (403). All three are large operators. The bigger the operator, the more likely its site is unreadable to exactly the agents this measurement is about.

  7. The price list that exists only as a JavaScript payload

    Three sites publish prices that no reader of the served HTML can see. A veterinary group's fee page (Glasgow) lists 24 treatments with prices, and in the HTML those exist only inside a hydration payload — objects with a treatment name and a standard price field; the static markup contains not one pound sign. Two national driving-school brands (Edinburgh and Exeter, the same platform and the same owner) do the same at scale: 407 KB and 95 KB of HTML respectively with zero pound signs anywhere in them, while an embedded instructor payload carries per-instructor hourly rates for three named services — 278 such price objects on the Edinburgh page alone.

    This is the sharpest version of the whole finding, because these prices are not sloppily published. They are strictly typed numbers with a service name attached, which is more machine-readable than anything else in the sample. They are just in a private shape belonging to that platform's own front end, addressed to that platform's own widget, and readable by nothing else. A fourth site (Coventry, driving) does it too, but also renders the same 16 priced products as visible text, so it fails only half of this trap. Structure is not the same thing as an agreed structure.

  8. The pricing page that is mostly POA

    One driving school (Aberdeen) publishes an eleven-row pricing table in which ten cells read "POA" and one reads £90. The page is titled and linked as a pricing page and it satisfies any check for "does this business publish prices" — including ours, which counted it as publishing a price, because it does. A customer arriving with a question about a ten-hour course leaves with a phone number, and so does an assistant. It is a reminder that price-published is a coarse measure, and that the honest reading of 36 of 40 has to allow for the difference between a fee schedule and a single number in a table of blanks.

Why dental is 10 of 10: it is the rulebook, not the market

UK dental practices do not publish fees as a courtesy. The General Dental Council's Standards for the Dental Team, standard 2.4, requires that patients get clear information about costs, and its guidance 2.4.2 is explicit that prices belong "in your practice literature and on your websites — patients should not have to ask for this information". The same guidance (2.4.1) sanctions from–to price ranges for treatments that vary, which is exactly the From-price pattern in trap 2. We verified this against the published standard on 2 August 2026.

That is what makes the dental column of this survey interesting: price display is regulation-driven, so compliance is universal — and machine-readability is still zero. Every practice in the sample has done the hard part, publishing real fees to the public. None of them gets credit for it from the assistants their patients are starting to ask, because the regulation was written for human readers and the pages were built for them too.

The veterinary column is not ten businesses. It is about five.

Seven of the ten veterinary practices are owned by corporate groups: three by one group, and one each by four others. That is by far the highest concentration in the survey — dental and physiotherapy are 2 of 10 each, driving schools 4 of 10 and all four of those are national franchise brands rather than owners.

The three practices belonging to the same group publish a byte-identical price template. Same page structure, same row labels in the same order, same two columns of standard fee and plan inclusion; only the numbers differ, and the row counts (89, 92 and 83 priced lines) differ because individual practices add or drop services. Three practices in three cities are, as far as a machine reading them is concerned, one website with three sets of numbers in it.

Two of the group-owned practices publish no treatment fees at all, only monthly health-plan subscription prices. One of those two has a pricing page whose standard-rates tab renders no numbers in the fetched HTML — the tab exists, the label is there, the figures are not.

The consequence is a commercial one and worth stating plainly, because it cuts against the obvious read of the numbers. A vertical where seven of ten locations sit behind five head offices is not a small-business market. Improving how those practices publish prices is one template edit at one group, made once and applied to hundreds of locations at once — which also means it is not a decision any individual practice can make. Whatever we or anyone else has to say to veterinary practices about being readable by assistants is an enterprise conversation with a group, not a self-serve one with a practice. The other three verticals are not shaped like that.

The same fact cuts the other way on the reading half. If a group can make hundreds of sites machine-readable with one edit, then the readable half of this problem is closer to solved than the per-site numbers suggest — for group-owned businesses. It does not touch the last row of the table. A shared template gives every practice in a group the same prices in the same shape; none of the three has anywhere an assistant can send a customer, and a template cannot give them one, because taking a request is a thing a practice does, not a thing a page says.

Driving schools are the opposite shape

Where veterinary practices concentrate, driving schools scatter. Four of the ten are national franchise brands. Of the remaining six, three are identifiably one instructor — a single mobile number, a first name in the contact line, "ADI registered", no company number and no limited-company suffix anywhere on the pages fetched. One more is a small limited company with its registration number printed in the footer. The last two could not be determined from their own sites: both trade under a school name, both recruit instructors, and neither states a legal form on any page we read, so we have left them unclassified rather than guess.

We record this because it is the sharpest test of a claim this whole survey rests on: that these are businesses without an IT department. A one-instructor driving school with a WordPress site and a mobile number is not going to implement a structured pricing schema, and the three in this sample that publish the clearest fee tables in the vertical did it by typing prices into a page. Meanwhile the two national brands, which do have engineering, ship 400 KB of HTML with no price in it at all and 278 price objects hidden in a widget payload. Neither end of the market has produced anything an assistant can use, for opposite reasons.

All 40 rows

Anonymised to city and vertical. The named per-site records — business, URL, price evidence quoted verbatim, JSON-LD types, ownership, how each site was found — are retained internally, and the full list is available on request to hello@ajarhq.com. Dental and physiotherapy rows measured 31 July 2026; veterinary and driving-school rows 2 August 2026.

City Vertical Published price Price in structured data Can receive a request Real llms.txt Notes
ManchesterDentalFull fee schedulenononoChain. Headline fees are From-prices; NHS band price also shown.
LeedsDentalFull fee schedulenonono103 priced lines, including a membership-plan discount column.
BristolDentalFull fee schedulenonono
SheffieldDentalFull fee schedulenonoyesOffer/OfferCatalog JSON-LD with every price field empty (trap 4). Took search position 3 after a 401 and a 403 (trap 6).
NottinghamDentalFull fee schedulenonoyesChain. Fees on the homepage itself, From-prices, NHS band included.
GlasgowDentalFull fee schedulenononoNo JSON-LD at all.
CardiffDentalFull fee schedulenonono
SouthamptonDentalFull fee schedulenononoThe header-only pound sign (trap 1); the whole table is a "Fees From (£)" column.
LeicesterDentalFull fee schedulenononoHeadline fee is a from-price.
NewcastleDentalA few examplesnonoyesPage states the fees shown are examples. Its llms.txt declares a live agent endpoint the practice did not configure; none of the nine tools it serves is transactional.
BirminghamPhysioFull fee schedulenonoyesThe one real physio llms.txt in the sample.
LiverpoolPhysioNonenononoNo fees page discoverable; both sitemaps 404. Took position 3 after a chain 403 and a directory (trap 6).
EdinburghPhysioFull fee schedulenonono21 prices, tiered by clinician seniority.
NorwichPhysioSingle pricenonono"Starting from" £-amount; no JSON-LD.
PlymouthPhysioSingle pricenononoChain. The price exists only inside the online booking flow (trap 3).
YorkPhysioFull fee schedulenononoChain. JSON-LD carries only priceRange "$$" (trap 4).
AberdeenPhysioA few examplesnononoJSON-LD priceRange "£" only (trap 4); /llms.txt answers 200 with an HTML page (trap 5).
BrightonPhysioNonenononoNo pricing page exists; no JSON-LD.
CoventryPhysioSingle pricenononoPrice stated plainly in a homepage FAQ; headline is "start from". No JSON-LD.
ExeterPhysioA few examplesnononoPosition 1 skipped: that chain was already sampled in Plymouth.
ManchesterVetFull fee schedulenononoIndependent. 57 priced treatment lines, the deepest independent schedule in the vertical.
LeedsVetFull fee schedulenononoCorporate group. 89 priced lines in the group's shared template: standard fee and plan-inclusion columns.
BristolVetA few examplesnononoCorporate group. Monthly health-plan prices plus one add-on; no treatment fee list on either page fetched.
SheffieldVetFull fee schedulenononoCorporate group, same group and same template as Leeds and Leicester; 92 priced lines. JSON-LD carries priceRange only (trap 4).
NottinghamVetNonenononoIndependent. No fees anywhere on the site.
GlasgowVetFull fee schedulenononoCorporate group. 24 priced treatments that exist in the served HTML only as a JavaScript payload; the static markup renders zero pound signs (trap 7).
CardiffVetFull fee schedulenononoIndependent. 19 rows where the pound sign sits in a separate element from the number — trap 1 in a different disguise.
SouthamptonVetA few examplesnononoCorporate group. Health-plan from-prices only; the same page's standard-rates tab renders no numbers at all in the fetched HTML.
LeicesterVetFull fee schedulenononoCorporate group, same template again; 83 priced lines. JSON-LD priceRange only (trap 4).
NewcastleVetA few examplesnononoCorporate group. Health-plan monthly prices by dog weight band, plus two benefit-line prices.
BirminghamDrivingA few examplesnononoNational franchise. Two lesson from-prices; a block-booking discount is mentioned without a figure.
LiverpoolDrivingFull fee schedulenononoTwo priced tables, manual and automatic, plus an evening and weekend surcharge. One course row reads "Email for price".
EdinburghDrivingFull fee schedulenononoNational franchise. Not one pound sign in 407 KB of HTML; 278 per-instructor price objects sit in the booking widget's payload (trap 7).
NorwichDrivingNonenononoNational franchise. No price anywhere on the pages fetched.
PlymouthDrivingFull fee schedulenononoSingle instructor. Three lesson lengths, a block booking and a test-day package, all priced in plain text.
YorkDrivingA few examplesnononoSingle instructor. Two prices for the same beginner package that disagree with each other by £16, on the same site.
AberdeenDrivingSingle pricenononoAn eleven-row pricing table in which ten cells read "POA" and one carries a number (trap 8).
BrightonDrivingA few examplesnononoSingle instructor. Two named services priced; intensive courses described but not priced.
CoventryDrivingFull fee schedulenonoyes16 priced products carrying a number, a currency and a formatted string in the shop payload, and rendered as visible text too — but in the platform's own shape, with no JSON-LD on the page (trap 7). Its llms.txt declares a live agent endpoint the school did not configure; none of the nine tools is transactional.
ExeterDrivingFull fee schedulenononoNational franchise, same platform and owner as Edinburgh. No pound sign in 95 KB of HTML; rates in the widget payload (trap 7). /llms.txt answers 200 with a "Not found" page (trap 5).

"Published price" is what a human sees on the page; "price in structured data" means a price in schema.org markup, which is what a machine reads reliably. The three sites whose prices live only in a JavaScript payload are recorded as publishing a price and as having none in structured data, because both are true and the payload is readable by nothing but its own front end. "Can receive a request" means any endpoint — MCP, API, structured form target — an assistant could send a booking or quote request to; on all 40 sites, reading is the whole story and the conversation ends at a phone number or a human web form.

What this looks like from the assistant's side

The rows above are properties of websites. What they mean for a customer's question is a separate measurement, so we ran one: five recorded assistant sessions against five of these sites, each asking "how much is a check-up, and can you book me in?". Two prices were cleanly readable, three had to be reconstructed or came back contradicted by the site's own channels, and none of the five could be booked. That write-up is at Can an AI assistant book you a dentist appointment?, including one practice whose visible page, structured data and llms.txt state three different prices for the same examination — and whose markup changed between this survey's measurement date and the session two days later.

The two sites in the table that expose something an agent can call are the subject of the third piece: Two of forty sites ship an agent endpoint, and neither owner knows. Both were put there by the website platform, both serve the same nine tools, and not one of those tools submits anything.

Disclosure, and what this survey does not say. Ajar sells the missing half of this table: a page that serves a business's prices in a shape a machine parses exactly, and an endpoint where an assistant's booking request waits for the owner's approval — see for dentists and for physios, or read the row this table does not contain on our own demonstration practice, which is fictional and says so. That is why we ran the survey, and you should read it knowing that. What the survey does not say: it does not say customers are currently being lost over any of this, and we will not pretend to know that. It is 40 sites, one per city per vertical, measured on two days; a lower bound on the state of four verticals, not a census of them. The veterinary column in particular is ten sites describing about five owners, which is a finding rather than a sample size we would defend.