Reference
The product data an AI agent needs before it can buy
An agent does not read your product copy. It reads the JSON-LD block, and it drops anything it cannot price, confirm as in stock, or match against the same item elsewhere. Those are three separate tests, and they fail for different reasons.
The offer: can this be priced at all
Three fields decide whether a product is even a candidate. If any one of them is missing, the item is not under-ranked — it is excluded, because there is nothing an agent could responsibly say about it.
| Field | Weight | Why |
|---|---|---|
price | Blocking | A bare decimal number, no symbol and no thousands separator. Without it there is nothing to compare and nothing to put in a basket. |
priceCurrency | Blocking | An ISO 4217 code such as EUR. A number with no currency is not a price, and an agent will not assume one from your domain. |
availability | Blocking | The full schema.org URL, for example https://schema.org/InStock. An agent that cannot confirm stock will not propose the item, because proposing something unpurchasable is its worst outcome. |
url | Strongly advised | The canonical product URL. On a page with several variants it is what disambiguates which offer belongs to which. |
itemCondition | Advised | Cheap to add and removes an ambiguity that matters in any category where refurbished goods exist. |
priceValidUntil | Advised | Tells a consumer when to stop trusting a cached price. Omitting it is safe; setting it to a date in the past is worse than omitting it. |
shippingDetails and hasMerchantReturnPolicy sit one step further out. They are not needed to price an item, but they turn a price into a total, which is the number a shopper actually decides on. If your platform can emit them, do.
Identifiers: can this be matched
Pricing gets a product considered. Matching gets it compared. An agent asked to find the best price on a specific item has to decide that your listing and four others are the same thing, and identifiers are how that decision is made cheaply and correctly.
| Field | What it is | Why it earns its place |
|---|---|---|
gtin13 | and gtin8 / gtin12 / gtin14 | The strongest match: the same barcode on your page and on five other sites is how a comparison gets built. Only publish one you actually have. |
sku | your own code | Identifies the exact variant within your catalogue. The one field every store can fill in truthfully today. |
mpn | manufacturer part number | The fallback match for goods that carry no barcode, which is most furniture, most industrial supply and much apparel. |
brand | as a Brand object | A string works but a Brand node with a name is what normalises cleanly. “Own brand” is a legitimate answer; leaving it out is not. |
A complete block
One product, one offer, everything above populated. This is what the scanner looks for on a product page.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Merino Base Layer, Long Sleeve",
"description": "200gsm merino wool base layer.",
"image": ["https://yourstore.com/img/base-layer-1200.jpg"],
"sku": "BL-MER-200-M-NAVY",
"mpn": "BLM200MN",
"gtin13": "5012345678900",
"brand": { "@type": "Brand", "name": "Northfell" },
"offers": {
"@type": "Offer",
"url": "https://yourstore.com/products/merino-base-layer",
"price": "89.00",
"priceCurrency": "GBP",
"availability": "https://schema.org/InStock",
"itemCondition": "https://schema.org/NewCondition",
"priceValidUntil": "2027-01-31",
"shippingDetails": {
"@type": "OfferShippingDetails",
"shippingRate": {
"@type": "MonetaryAmount",
"value": "4.95",
"currency": "GBP"
},
"shippingDestination": {
"@type": "DefinedRegion",
"addressCountry": "GB"
}
},
"hasMerchantReturnPolicy": {
"@type": "MerchantReturnPolicy",
"returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
"merchantReturnDays": 30,
"returnMethod": "https://schema.org/ReturnByMail",
"applicableCountry": "GB"
}
}
}
</script>The mistakes that void otherwise good markup
Nearly every store we scan that fails this check has markup. It is the values that are wrong, and nothing on the page looks broken.
"price": "£89.00" // wrong: the symbol belongs in priceCurrency
"price": "89,00" // wrong: a comma decimal separator is not a number
"price": "From 89.00" // wrong: not parseable
"availability": "in stock" // wrong: must be a schema.org URL
"availability": "InStock" // wrong: still not a URL
"price": "89.00"
"priceCurrency": "GBP"
"availability": "https://schema.org/InStock" // right- The price is in the HTML but not in the markup. A theme that renders the price client-side and emits JSON-LD server-side often leaves the offer empty. The page looks right to you and is unpriceable to a parser.
- Variants share one offer. Five sizes at three prices, described by a single
Offer. UseProductGroupwithhasVariant, or anAggregateOfferwithlowPriceandhighPrice, so the range is explicit rather than implied. - The markup is on the category page only. Listing pages with
ItemListare useful, but the offer has to be on the page the product lives at, because that is the URL an agent lands on. - An invented
aggregateRating. A rating block with no reviews behind it is the one error here that is not a mistake, and it is the one worth naming: it is a false statement in a format designed to be trusted without being read. Leave it out until you have reviews.
And one block on the homepage
Product markup says what is for sale. Organizationmarkup, or its more specific OnlineStore, says who is selling it, which is what lets an agent attribute a product to an identifiable merchant rather than to a URL.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "OnlineStore",
"@id": "https://yourstore.com/#organization",
"name": "Northfell",
"url": "https://yourstore.com",
"logo": "https://yourstore.com/logo.png",
"email": "help@yourstore.com",
"sameAs": ["https://www.instagram.com/northfell"]
}
</script>This is the finding our own site failed on when we first scanned it, which is written up in full on our own report card.
Check it on your own store
The scan reads your homepage and up to three product pages, parses every JSON-LD block it finds, and reports which offer fields and identifiers are present. If it cannot identify a product URL from your sitemap it says the check was not possible rather than telling you the markup is missing.
Run the free scanNo account, no email. It reports what it measured and says so when it could not measure something.