Fixing Structured Data That AI Agents Rely On
When an AI agent is asked to buy something, book a table or check whether a shop is open on Sunday, it needs a handful of facts it can trust: price, currency, stock, delivery, opening hours, dates. Structured data puts those facts in a labelled, machine-readable form, so the agent does not have to guess them from your layout.
This guide is for shop owners, venues and local businesses whose markup is missing, incomplete, injected too late or out of step with the page. It shows which properties answer which questions, how to add them and how to keep them accurate.
- Why agents lean on structured data
- Match each customer question to a property
- How to see what an agent actually receives
- The fix, step by step
- Two examples you can adapt
- When markup and page disagree
- Where the markup comes from on common platforms
- Actions, llms.txt and other things people ask about
- Keeping it right after launch
- Questions and answers
Why agents lean on structured data
An agent working for a customer is not browsing for pleasure. It has a task, such as "find a pair of size 9 walking boots under £120 that can arrive by Friday", and it needs to decide quickly whether your page can help. It can try to read the answer from the visible page, but prices appear in several places (was, now, per unit, with or without delivery), stock messages are phrased differently on every site, and opening hours are often a picture or a free-text paragraph. Each guess is a chance to get it wrong.
Structured data in JSON-LD removes the guesswork. A property called price with a value
of 115.00 and a priceCurrency of GBP means one thing only. The
agent can read it in milliseconds, and so can the lighter tools that fetch the HTML without running scripts,
which cannot see anything that only appears after JavaScript runs.
Two points to keep in proportion:
- Markup supports the page, it does not replace it. Agents that drive a real browser still look at what is on screen, and an agent that finds one price in the markup and another on the page may stop, or report the wrong one to its user.
- There is no special agent vocabulary. Agents read the same schema.org types that search engines use. The difference is emphasis: search features reward some properties, while agents care most about the facts needed to finish a task, such as stock, delivery, returns and hours, which sites often leave out.
If you are also working on search appearance, the Product schema errors guide covers Google's required fields for product snippets and merchant listings. This guide takes the agent's point of view: which facts an agent needs and how to make sure it can find them.
Match each customer question to a property
Start from the questions a person would ask before handing over money or turning up at your door. Each has a home in schema.org.
| The question | Where the answer lives | Common gap |
|---|---|---|
| Can I buy it now? | Offer.availability with a schema.org value such as InStock,
OutOfStock, PreOrder or BackOrder | Always InStock
because the theme hard-codes it |
| How much, in what currency? | Offer.price and Offer.priceCurrency | Price written as "£19.99" in a text field, or the pre-sale price left in the markup |
| Which size or colour? | ProductGroup with hasVariant and variesBy | One Product for the whole range with a single price |
| What does delivery cost and how long does it take? | Offer.shippingDetails
with shippingRate and deliveryTime | Missing altogether, so the agent cannot compare total cost |
| Can I send it back? | hasMerchantReturnPolicy with the return window and fees | Policy only on a separate page in prose |
| Are you open on Sunday, or on a bank holiday? | openingHoursSpecification
and specialOpeningHoursSpecification | Hours only in the footer as text, holidays not mentioned anywhere |
| Where are you and how do I call? | address, geo, telephone
on a LocalBusiness subtype | Phone number only as an image or a click-to-reveal button |
| When is it, is it still on, where do I get tickets? | Event.startDate, eventStatus,
location, offers.url | Dates without a time zone, cancelled events still marked as scheduled |
| Do you cover my area and what does it cost? | Service with areaServed,
provider and an offers price or price range | Services described only in marketing copy |
You do not need every row. A plumber needs the LocalBusiness and Service rows; an online shop needs the Offer rows; a theatre needs the Event rows. Pick the ones that match what people come to your site to do.
How to see what an agent actually receives
The AI Agent Compatibility audit reports missing structured data as one of its findings. To see the detail for yourself, check three things.
1. Is the markup in the first HTML?
Open the page, then view the source (Ctrl+U or Cmd+Option+U), not the Elements panel in developer tools. The Elements panel shows the page after scripts have run, so markup added by Google Tag Manager or a client-side app appears there even though a fetcher never sees it. From a terminal, the same check is:
curl -s https://www.example.co.uk/boots/fell-walker | grep -c 'application/ld+json'
A count of zero means no JSON-LD reaches tools that do not run JavaScript.
2. Is it valid and complete?
Paste the address into the Schema Markup Validator at validator.schema.org, which checks against the full schema.org vocabulary, and into Google's Rich Results Test, which checks Google's own requirements. Neither tool knows what agents need, so use the table above as your checklist for completeness.
3. Does it agree with the page?
Put the markup and the visible page side by side and compare price, stock, hours and dates. Then change something on the page, such as picking a different size, and check whether the markup was built for the default variant only. Disagreements are the most damaging finding of all, because they make the agent doubt everything else.
The fix, step by step
Step 1
Write down the questions each page must answer
List your main page templates, such as product, category, location, service and event pages, and for each write the three or four questions a customer needs answered before acting: price, stock, delivery, returns, hours, dates or coverage. Mark which answers are already visible on the page. This list becomes the specification for your markup, and it stops you adding types that look impressive but answer nothing a person asks.
Step 2
Put the markup in the HTML the server sends
Generate the JSON-LD in the page template on the server so it arrives in the first
response, ideally in the head. Move any structured data that is currently injected by Google
Tag Manager, a chat widget or a client-side framework into the template. Check with view-source or curl
that exactly one block per thing appears, and remove duplicates added by a second plugin, an app or an
old hard-coded snippet.
Step 3
Complete every offer
Give
each Offer a plain number in price, a three-letter priceCurrency,
a full schema.org URL in availability and the page address in url. Add shippingDetails
with the delivery cost and transit time to your main destination, and hasMerchantReturnPolicy
with the return window and whether returns are free. Only add priceValidUntil when a sale
genuinely ends on a known date, and remove it once the sale is over.
Step 4
Group variants instead of repeating products
When one item comes in several sizes or colours with their own price or stock, wrap them
in a ProductGroup with a shared productGroupID, list the attributes that change
in variesBy, and add each variant as a Product in hasVariant with
its own sku, size or color and its own offer. That lets an agent
tell the customer that size 9 is in stock even when size 10 is not.
Step 5
Add regular and holiday opening hours
Use the most specific LocalBusiness subtype that fits, such as Bakery
or Plumber, with a full address and a telephone in international
format. Describe normal hours with openingHoursSpecification, one entry per group of days
with the same times. Add specialOpeningHoursSpecification entries with validFrom
and validThrough dates for bank holidays and closures, and update them a few weeks before
each holiday season so they are never stale.
Step 6
Give events a status, real times and a ticket link
Write startDate and endDate as full ISO 8601 values
with the UK offset, such as 2026-11-14T19:30:00+00:00 in winter and +01:00
in summer. Set eventStatus to scheduled, postponed, rescheduled or cancelled as things change,
name the venue in location with its address, and point offers.url at the page
where tickets can actually be bought, with the ticket price and current availability, including sold
out.
Step 7
Check the markup against the visible page
Run each template through the Schema Markup Validator and the Rich Results Test, then compare every price, stock status, date and set of hours in the markup with what the page shows. Test a sale item, an out-of-stock item and a variant other than the default. Run the AI Agent Compatibility audit again, and add a check to your release routine so template changes cannot silently break the markup.
Two examples you can adapt
A product with sizes, delivery and returns
This fictional shop sells one boot in two sizes. Size 9 is in stock and size 10 is on back order, which a single Product with a single offer could not express.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "ProductGroup",
"name": "Fell Walker leather boot",
"productGroupID": "FW-BOOT",
"brand": { "@type": "Brand", "name": "Ridgeline Outdoor" },
"image": "https://www.example.co.uk/img/fell-walker.jpg",
"variesBy": "https://schema.org/size",
"hasVariant": [
{
"@type": "Product",
"name": "Fell Walker leather boot, UK 9",
"sku": "FW-BOOT-09",
"size": "UK 9",
"offers": {
"@type": "Offer",
"url": "https://www.example.co.uk/boots/fell-walker?size=9",
"price": "115.00",
"priceCurrency": "GBP",
"availability": "https://schema.org/InStock",
"shippingDetails": {
"@type": "OfferShippingDetails",
"shippingRate": { "@type": "MonetaryAmount", "value": "0", "currency": "GBP" },
"shippingDestination": { "@type": "DefinedRegion", "addressCountry": "GB" },
"deliveryTime": {
"@type": "ShippingDeliveryTime",
"handlingTime": { "@type": "QuantitativeValue", "minValue": 0, "maxValue": 1, "unitCode": "DAY" },
"transitTime": { "@type": "QuantitativeValue", "minValue": 1, "maxValue": 3, "unitCode": "DAY" }
}
},
"hasMerchantReturnPolicy": {
"@type": "MerchantReturnPolicy",
"applicableCountry": "GB",
"returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
"merchantReturnDays": 30,
"returnMethod": "https://schema.org/ReturnByMail",
"returnFees": "https://schema.org/FreeReturn"
}
}
},
{
"@type": "Product",
"name": "Fell Walker leather boot, UK 10",
"sku": "FW-BOOT-10",
"size": "UK 10",
"offers": {
"@type": "Offer",
"url": "https://www.example.co.uk/boots/fell-walker?size=10",
"price": "115.00",
"priceCurrency": "GBP",
"availability": "https://schema.org/BackOrder"
}
}
]
}
</script>
Each variant has its own address with a query parameter, so an agent can send the customer straight to the right size. In a real shop, give every variant the same shipping and returns details, or define them once at organisation level where your platform supports it.
A local business with holiday hours
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Bakery",
"name": "Tollgate Bakehouse",
"url": "https://www.example.co.uk/",
"telephone": "+44 1632 960123",
"address": {
"@type": "PostalAddress",
"streetAddress": "4 Tollgate Row",
"addressLocality": "Exampleton",
"postalCode": "EX1 2AB",
"addressCountry": "GB"
},
"openingHoursSpecification": [
{ "@type": "OpeningHoursSpecification", "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"], "opens": "07:30", "closes": "16:00" },
{ "@type": "OpeningHoursSpecification", "dayOfWeek": "Saturday", "opens": "08:00", "closes": "14:00" }
],
"specialOpeningHoursSpecification": [
{ "@type": "OpeningHoursSpecification", "validFrom": "2026-12-25", "validThrough": "2026-12-26", "opens": "00:00", "closes": "00:00" }
]
}
</script>
Sunday is simply left out, which means closed. The Christmas entry sets opening and closing to the same time, the usual way to say "closed all day" for a date range. The phone number uses a range reserved for fiction, so replace it with your own.
When markup and page disagree
A missing property makes an agent look elsewhere. A wrong one makes it confidently wrong. These are the usual causes.
- Cached markup. The page price is updated by a script or a stock feed, but the JSON-LD was cached with yesterday's price. Build both from the same data at the same moment, and purge the cache when prices change.
- Sale prices. The page shows the sale price and the markup shows the full price,
or the reverse. The
priceshould be what the customer pays today. If you want to show the old price too, schema.org haspriceSpecificationwith aUnitPriceSpecificationand apriceTypesuch ashttps://schema.org/StrikethroughPrice, but keep the mainpriceas the current one. - Currency switchers. A script converts prices on screen into euros for a visitor from Ireland while the markup stays in pounds. The agent then sees two different amounts. Either keep conversion off for the structured data and show the real charging currency clearly, or generate localised pages with their own markup.
- VAT. Consumer prices in the UK are normally shown including VAT. If your trade customers see ex-VAT prices after logging in, make sure the public markup carries the same figure the public page shows.
- Hard-coded stock. Many themes print
InStockfor every product. Check an item you know is sold out. - Time zones. A date written as
2026-11-14T19:30without an offset is ambiguous. Always add the offset, and remember the UK moves between+00:00and+01:00.
Where the markup comes from on common platforms
Most sites do not write JSON-LD by hand. Knowing which part of your system produces it tells you where to fix it.
- WordPress and WooCommerce. WooCommerce outputs Product markup itself, and SEO plugins such as Yoast SEO and Rank Math add or merge their own. Two sources often means two Product blocks with different prices. Pick one source, and use the plugin's settings for business type, address and hours. The WordPress guide covers the plugin side of agent compatibility.
- Shopify. Themes add Product markup in Liquid, and review or SEO apps may add more.
Newer themes use the
structured_dataLiquid filter, which builds a block from the product object. Check whether a variant's price and stock are reflected, and see the Shopify guide. - Single-page apps. If a React or Vue app adds JSON-LD with a head manager in the browser, fetchers never see it. Render it on the server with the framework's head or metadata API.
- Custom and static sites. Generate JSON-LD from the same database record that fills the visible template, not from a separate hand-edited file that drifts.
Actions, llms.txt and other things people ask about
Schema.org includes potentialAction with types such as BuyAction, OrderAction
and ReserveAction, which can describe how to start a task, for example a booking address.
They are part of the vocabulary and harmless to add when they are accurate, but the main facts in this
guide matter far more, and we are not aware of agents that depend on actions to complete a task. Treat
them as optional.
An llms.txt file is a different thing: a plain-text guide to your site's important pages for AI tools. It helps readers of your content, while structured data helps agents check facts on a specific page. They complement each other, and the Agent Readiness Guide covers llms.txt.
FAQPage markup no longer earns rich results for most sites, because Google now limits them to well-known government and health sites. It does no harm where the questions and answers are visible on the page, but it will not tell an agent your price or opening hours, so do not treat it as a substitute.
Keeping it right after launch
- One source of truth. Price, stock, hours and dates should come from one place that feeds both the page and the markup.
- Holiday calendar. Put a reminder in your diary a month before each bank holiday to add the special hours, and remove last year's entries.
- Release checks. After a theme update, a new plugin or app, or a change of stock system, re-test one product, one out-of-stock product and one location page.
- Event housekeeping. Change
eventStatusthe day an event is cancelled or moved, and keep the page live with the new status rather than deleting it. - Watch for drift. Markup that was right at launch tends to decay. The schema drift guide explains how to catch changes early.
1. Do AI agents actually read JSON-LD structured data?
Many do, particularly tools that fetch a page without running a browser, and agents that do run a browser can read it too because it is part of the HTML. No provider publishes a full list of what it reads, so treat structured data as a clear, reliable second copy of your key facts rather than a guaranteed input. The visible page still matters, and the two must agree, because a mismatch can make an agent stop or report the wrong figure.
2. Is structured data for agents different from structured data for Google?
The vocabulary is the same: schema.org types such as Product, Offer, LocalBusiness and Event in JSON-LD. What differs is which properties matter most. Google publishes required and recommended fields for each rich result, while an agent simply needs the facts to finish a task, such as stock, delivery cost, return window and holiday hours. Sites that did the minimum for rich results often leave those out, which is why this guide focuses on them.
3. My markup is added by Google Tag Manager. Is that a problem?
It can be. Markup injected by Tag Manager only exists after scripts run in a browser. Tools that fetch the raw HTML never see it, and even browser-based agents may read the page before the tag fires. Google can process it after rendering, but that is not the same as every agent seeing it. Move the JSON-LD into your page template so it is part of the HTML your server sends, and remove the tag once the template version is live.
4. How do I show sizes and colours with different stock levels?
Use a ProductGroup for the item as a whole, with a productGroupID and a variesBy property naming the attributes that change, such as size or colour. Then add each variant as its own Product inside hasVariant, with its own sku, the attribute value and its own Offer carrying price and availability. Give each variant an address that selects it, often with a query parameter, so an agent can send the customer straight to the right option.
5. How do I mark up bank holiday opening hours?
Keep your normal week in openingHoursSpecification and add a separate specialOpeningHoursSpecification entry for each holiday or closure, with validFrom and validThrough dates. For a day when you are closed, set opens and closes to the same time, commonly 00:00. For shorter hours, give the actual times. Add the entries a few weeks before the date, check them against the notice in your window, and remove them afterwards, so the markup never promises hours you are not keeping.
6. Should the price in my markup include VAT and delivery?
The price in the markup should be the same amount the page shows the customer for the item, which for UK consumer sites normally includes VAT. Delivery belongs in shippingDetails rather than being added to the price, so an agent can see both the item price and the delivery cost and work out the total. This is not legal advice, so check your own pricing obligations with a qualified adviser if you are unsure.
7. Will adding more schema types make agents prefer my site?
Not on its own. Adding types that do not match what the page offers adds nothing and can look like spam. What helps is complete, accurate markup for the few things that matter to your customers: offers for a shop, hours and contact details for a local business, dates and ticket links for events. An agent that can confirm price, stock and delivery quickly has fewer reasons to move on to another site.
The AI Agent Compatibility audit tests your page for bot blocks, content hidden behind JavaScript, unlabelled forms and controls, navigation agents cannot follow and missing structured data, and links each finding to the guide that fixes it.
Open AI Agent Compatibility Fixes โ