Why AI Agents Say You're Out of Stock When You're Not

Jump to section›
Short version: A US furniture store had 30,855 products sitting in stock and a website that clearly said so. Four AI shopping agents still told buyers it was sold out. The store wasn't unlucky, and the agents weren't broken. The availability the machines read and the availability the humans saw were two different numbers living on the same page. Availability isn't a ranking problem — it's a structured-data problem, and it fails silently.
#Humans and agents read two different versions of "in stock"
Open one of your product pages. You see a green "In stock" badge, a price, an Add to Cart button. That's the DOM — the page after your JavaScript has run in a real browser.
An AI shopping agent almost never sees that. It reads the page as it arrives over the wire: the server-rendered HTML and any JSON-LD Product/Offer block embedded in it — the version that exists before your JavaScript paints anything in. If your "In stock" badge is drawn in by client-side JS, but the raw HTML or the JSON-LD still carries a stale value like BackOrder, the agent trusts what it read first. Then it repeats that to the shopper: "It looks like that's backordered — here are some alternatives."
The agent read the page correctly. The page was telling two different stories to two different readers.
This assumes the agent reached your page at all. If it can't resolve your URL, none of this markup is ever read — that's the redirect-map layer underneath, the prerequisite for everything here. This post is about the next question: once the agent arrives, does it read the truth?
#Where availability actually lives
In structured data, stock status isn't prose — it's a specific field with a controlled value. On a schema.org/Offer, it's availability, and it takes a URL value from a fixed set:
https://schema.org/InStockhttps://schema.org/OutOfStockhttps://schema.org/BackOrderhttps://schema.org/PreOrderhttps://schema.org/LimitedAvailability
That value is what an agent quotes when a buyer asks "can I get this?" And agents read in layers, in order of trust: JSON-LD Product/Offer first, then microdata, then the visible text in the HTML. When those layers agree, everything is fine. When they disagree — JSON-LD says one thing, the on-page text says another — the agent has to guess which one is authoritative, and it often guesses wrong.
This is why "it looks right on screen" doesn't save you. The badge a human sees is the last thing an agent trusts, and usually the thing it never sees at all. If your JSON-LD ships a stale availability, a perfectly correct JavaScript badge is invisible to the machine reading you.
#Three bugs that make availability read wrong
These are mechanism-level patterns — the recurring shapes we see reading public storefronts, not measurements of any one store.
#1. Stock painted by JavaScript, HTML and JSON-LD left stale
The most common one. The server ships HTML (and often JSON-LD) with a default or last-known availability value, and the real status is fetched and painted in by client-side JavaScript after load. Humans get the corrected badge because their browser runs the JS. The agent reads the served bytes and leaves before the JS ever runs. Whatever stale value was in the HTML is the answer it takes away. This is exactly what happened to Home Elegance below.
#2. Duplicate or conflicting JSON-LD
Two Product nodes on one page — one from the theme, one from an SEO app — each with its own Offer. The agent has to resolve which one is real, and there's no guarantee it picks the one with the correct availability. The fix is deduplication: one Product/Offer graph per page, with a stable @id so the node is unambiguous. (This is the same class of markup hygiene that governs brand and AggregateRating resolution, too.)
#3. An unconditional boilerplate sentence in the template
A template that prints "Ships in 4–6 weeks" or "This item is backordered" into the HTML for every product, regardless of real stock, because the condition that should have gated it was never wired up. A human skims past it. An agent reads it as the product's official status and quotes it back — even when your inventory system says the item is sitting on the shelf.
All three are fixable at the markup/render layer. None of them require touching checkout, the theme's design, or your ERP.
#A real case: Home Elegance USA
Home Elegance USA is a US Shopify furniture retailer with 30,855 products, all showing in stock on the site. We tested availability the boring, repeatable way: five buyer questions across four agents — ChatGPT, Gemini, Perplexity, Claude — 20 cells, scored and dated.
- Baseline: 4 of 4 agents told shoppers that in-stock furniture was backordered or unavailable. 0 of 4 confirmed "In stock."
- The cause: bug #1 above — a backorder sentence rendered unconditionally in the HTML, with the true stock status painted in later by JavaScript.
- The fixes, over about ten days: made the backorder sentence conditional and server-rendered the real stock status; deduplicated JSON-LD and re-attached
brandvia a stable@id; wired liveAggregateRating; added FAQ schema. All correctness work, all at the markup layer. - After: 0 of 4 agents said backordered; 3 of 4 explicitly confirmed "In stock."
And the part we say out loud on purpose: category ranking stayed flat (roughly 4/12 → 3–4/12). These were correctness fixes, not ranking fixes. Making an agent describe you accurately is a different mechanism from making it pick you in a competitive answer — the second runs on reviews, entity authority, and third-party corroboration over a longer arc. Anyone claiming a ten-day ranking win from a template fix is selling something.
The full case narrative — symptom, cause, five fixes, dated before/after — is here: Four AI Agents Told Shoppers This Furniture Store Was Out of Stock, and the client-approved case page is at luma-e.com/work/home-elegance-usa-ai-agent-commerce-readiness-2026.
#How to know your fix actually worked
Measure it the way you'd measure anything you want to trust: a fixed set of buyer questions, asked across the agents that matter, scored and dated, before and after. Boring, repeatable, dated. If it isn't written down with a date next to it, it isn't a result — it's a vibe.
You can run the smallest version of this at home right now. Pick your best-seller. Ask ChatGPT whether it's in stock, phrased the way a real buyer would ask — "is the [product] available to order?" Then compare the answer to what your inventory system actually says. If they disagree, you've found the gap, and it's almost certainly one of the three bugs above.
#The fix, without a rebuild
- Server-render the real stock status into the HTML. Don't let client-side JavaScript be the only source of truth for availability. The value in the bytes is the value the agent takes.
- Deduplicate JSON-LD. One correct
Product/Offernode per page, with a stable@idso there's nothing for the agent to resolve wrong. - Map
Offer.availabilityto the real state.InStock/OutOfStock/BackOrder/PreOrderwhere each actually applies — and delete the unconditional backorder sentence. - Keep the layers in sync. Price, currency,
AggregateRating, and availability should say the same thing in the HTML and in the JSON-LD. Disagreement between layers is what makes an agent guess.
Availability isn't a badge — it's a value the machines read before your customer ever sees your site. Make the version the agent reads match the version that's true. Everything else in "AI visibility" comes after that. If you've never checked, the fastest test is free: ask ChatGPT whether your best-seller is in stock, the way a real buyer would — and see which of your two answers it gives back.
Send one store URL and we'll run a free async AI-correctness read: we ask live AI agents about your products, run the same 20-cell availability test, and send back a named punch-list of exactly which agents are reading your stock wrong and where the markup disagrees with the truth: luma-e.com/ai-readiness. No call required.