TL;DR

Only 4% of B2B software listings on G2 include an explicit numeric price, even though pricing is the single most helpful piece of information for buyers. When ChatGPT couldn't parse JavaScript-rendered pricing on two SaaS pages, its internal reasoning traces showed it silently fell back to third-party review sites instead of the vendor's own numbers.

Companies that hide pricing behind a "contact us" gate generate 64% more form submissions but convert those leads to pipeline at 1.7x lower rates than transparent pages. The fix is structural: embed Google's Product structured data (Offer, priceCurrency, priceValidUntil, and UnitPriceSpecification) directly in server-rendered HTML—not client-side JavaScript—so AI crawlers can extract plan names, prices, and billing periods without guessing or borrowing stale data from elsewhere.

A pricing page built for AI search is one where every plan name, price, billing period, and usage limit can be extracted correctly by a machine reader without inference, disambiguation, or a fallback to a third-party source. That's the definition. Everything below is about how to get there and what happens when you don't.

This matters now because AI answer engines and shopping assistants have started answering "how much does X cost" directly, without sending the person to your site at all. If the structure underneath your pricing table is ambiguous, the AI doesn't ask for clarification — it guesses, and it states the guess with the same confidence as a fact.

Why AI systems get pricing wrong

The clearest documented case comes from a piece of independent research published in Search Engine Journal in 2026. The author, Suganthan Mohanadasan, read ChatGPT's own saved reasoning traces — not its output, its internal network activity — while it looked up pricing for two SaaS products, Profound and Peec. In both cases the model reasoned that "the pricing isn't showing up directly in the search result, possibly because it's loaded with JavaScript," gave up on the vendor's own page, and explicitly decided to "use citations from G2 where appropriate" instead. The company's own numbers didn't parse, so the model borrowed someone else's version of them — see "How ChatGPT Actually Picks Sources (I Read the Network Traffic, Not the Outputs)".

That single mechanism — client-side rendered pricing tables that a crawler can't read, followed by a silent fallback to a review site or a stale cache — is the most concrete, traceable failure mode currently documented for AI-misstated SaaS pricing. It also explains why "the price is on the page, so we're fine" isn't a safe assumption: the price has to be on the page in the response body a bot actually receives, in text a parser can extract, matched to a specific plan name and billing period.

The business incentive to fix this is not abstract. HockeyStack Labs analyzed 31 million website visits across 80 B2B SaaS companies and found that pages hiding pricing behind a "contact us" gate generated 64% more form submissions but converted those submissions to pipeline at 1.7x lower rates than pages with transparent, published pricing — see the HockeyStack Labs pricing/demo study. Separately, G2's own audit of listings on its marketplace found that only 4% of product profiles list an explicit numeric price, even though pricing information ranks as the single most helpful piece of information B2B buyers look for when evaluating software — see "Is B2B Software Pricing Transparency Broken?". eMarketer, citing TrustRadius buyer research, reports pricing transparency as the number one change B2B technology buyers want from vendors, named by 45% of buyers worldwide — see eMarketer, "B2B tech buyers crave pricing transparency". Gating price behind a sales conversation was already a documented conversion problem for human buyers before AI search existed; it's a bigger problem for a system that can't have that conversation at all and has to either guess or skip your product.

What "machine-readable" pricing actually requires

Google's structured data documentation is the most concrete, stable spec available for describing prices to a machine, and it's what most AI systems' crawlers and ingestion pipelines are built to expect even outside classic Search. Google's Product structured data guide requires an Offer object nested inside Product markup, and per Google Merchant Center's supported attributes, the fields Google treats as required for automated processing are price, priceCurrency, availability, and condition. Critically, Google's own guidance states that "structured data markup must be present in the HTML returned from the web server and can't be generated with JavaScript after the page has loaded" — the exact failure mode the Search Engine Journal traces documented.

Two schema.org details matter specifically for SaaS pricing pages, which rarely sell a single physical unit:

  • priceValidUntil: an Offer property (defined at schema.org/Offer) that states the date after which a listed price is no longer guaranteed. Leaving it blank or stale is one of the most common reasons a listed price gets treated as unreliable and dropped from rich results.
  • UnitPriceSpecification: schema.org's mechanism for stating a price's basis — for example, price per seat, per user, or per month — using a referenceQuantity and a unitText (schema.org has no reserved unit code for "seat" or "user," so a free-text unit label is the accepted pattern). Without this, a machine reader has no reliable way to tell "$49" per seat per month apart from "$49" as a flat, one-time, or per-workspace price. See schema.org/UnitPriceSpecification.

Commerce-focused AI systems are converging on adjacent but not identical specs. OpenAI's Product Feed Specification for ChatGPT shopping ingestion requires, at minimum, an ID, title, description, price, availability, URL, and image per item, delivered as a structured feed file rather than crawled — OpenAI's Shopping with ChatGPT Search help article confirms that ChatGPT weighs "structured metadata from first-party and third-party providers (e.g., price, product description)" when deciding what to surface and cite. SaaS plans aren't physical products moving through a shopping feed, but the underlying principle is identical: a machine consumer wants one canonical, structured, dated source of price, not a paragraph of marketing prose it has to parse.

Pricing-page patterns and how machines read them

PatternWhat a human seesWhat a machine reader getsRisk
Full "contact sales" gate, no numbers on pageTiers named, no prices, "Talk to us" CTANo price value to extract at allAI either says "pricing not public" (best case) or fabricates a plausible number from training data (common case)
Prices rendered client-side via JavaScriptCorrect prices, fully interactiveEmpty or partial HTML at crawl time — no price in the response bodyModel falls back to a third-party review site's possibly outdated number, as documented in the Search Engine Journal trace analysis
Prices in a marketing paragraph ("plans start around $99")Reads naturally, feels humanAmbiguous — "around," no currency code, no billing period, no explicit plan bindingPrice gets rounded, misattributed to the wrong tier, or ignored as unreliable
Static HTML table, price per tier, no billing-period or per-seat labelClear to a human who knows the productMachine sees a number with no unit — can't tell monthly vs. annual vs. per-seatAI states the number but attaches the wrong basis (e.g., quotes annual price as monthly)
Static HTML with Offer/Product markup, priceCurrency, priceValidUntil, and a stated per-unit basisSame clear table, unchanged for the userEvery value has an explicit type, currency, expiry, and unitLowest ambiguity; still not immune to staleness if the page isn't kept current
  1. Publish an actual number for every advertised tier. If a tier is genuinely custom and un-priceable (true enterprise/negotiated deals), say so explicitly in text — "custom pricing, contact sales" — rather than leaving a blank cell a model has to interpret.
  2. Render prices in the initial HTML response, not client-side JavaScript. Verify with a plain HTTP fetch (no browser execution) that the price text is present in the raw response. This is the single fix the Search Engine Journal trace analysis identified as decisive.
  3. Name tiers unambiguously and consistently everywhere. If the pricing page says "Growth" but the app's UI, invoices, and help docs say "Pro," a model reconciling multiple pages will average, guess, or pick whichever source it trusts more — usually not yours.
  4. State the billing basis next to every number, in text, not just visually. "$49/user/month, billed annually" as literal text beats a table layout that implies the same thing through column headers alone.
  5. Add Offer structured data with price, priceCurrency, and availability for every tier, following Google's Product structured data guide. Use one Offer per tier — not a single AggregateOffer price range — since Google's own guidance is explicit that ranges are for multi-seller listings, not your own tiered SKUs.
  6. Set and actively maintain priceValidUntil. A stale or missing value is one of the more common reasons Google's own systems stop trusting a listed price; don't assume "set it once" is enough.
  7. Encode the per-unit basis with UnitPriceSpecification and a referenceQuantity/unitText (e.g., unit text "seat," quantity 1) so a machine reader can distinguish per-seat from flat pricing without inferring it from prose.
  8. List concrete usage limits as numbers, not adjectives. "Up to 10,000 contacts" is extractable; "generous contact limits" is not — and a model asked to compare it against a competitor will either skip it or invent a figure.
  9. Re-crawl and spot-check what AI systems currently say about your pricing, on a recurring basis rather than once. Prices change, plans get renamed, and third-party listings (G2, Capterra, review aggregators) drift out of sync with your live page — each of those is a separate source a model might cite instead of you.

What this doesn't guarantee

Structuring a pricing page correctly reduces ambiguity; it does not force any AI system to use your numbers. Models trained on older snapshots of the web can still surface a stale price from their training data regardless of what your current page says, and there is no mechanism to force a retroactive correction of that training data. Retrieval-based assistants that browse live pages (Perplexity, Copilot, Google's AI features) tend to pick up a corrected price faster than assistants relying purely on trained-in knowledge, but "faster" still means a lag measured in days, not instantly. Clean markup also doesn't stop a model from citing a third-party listing it trusts more than your own domain — Search Engine Journal's traced example shows a model choosing to cite G2 even after finding (or failing to find) the vendor's own number. And structured data can't resolve genuine pricing complexity: if your real-world pricing depends on negotiated volume discounts, region, or contract term, no schema property makes that fully machine-representable — the honest move there is to publish the representative starting price and say plainly what varies.

Where nqzai fits

Direct answer: nqzai's AI-visibility tooling checks what answer engines currently say about a business's pricing against what's actually published on its site, flags mismatched tiers, missing per-unit basis, and stale numbers, and points to the specific structural fixes — like the ones above — needed to close the gap, without asking anyone to guess at what a crawler saw.

FAQ

Does hiding pricing behind "contact sales" always hurt AI visibility?

It removes the one thing a machine reader needs most: a number. Some AI systems will accurately say "pricing isn't public, contact the vendor" — but many will instead pull a number from a stale cache, a competitor comparison page, or a review site, and state it as fact. If your pricing is genuinely negotiated, the safer move is stating that explicitly in text rather than leaving the field blank.

Is AggregateOffer ever the right choice for a SaaS pricing page?

Rarely for tiers you set yourself. AggregateOffer with lowPrice/highPrice is meant for price ranges across multiple sellers of the same product — not a single vendor's own Basic/Pro/Enterprise ladder. Each of your tiers should generally be its own Offer with one specific price.

Do OpenAI and Perplexity require the same structured data as Google?

No single spec covers all of them, but they overlap heavily. Perplexity's shopping ingestion follows the existing Google Shopping product feed format rather than publishing its own, and OpenAI's product feed spec explicitly falls back to Google's product data field definitions when a feed is already Google-compatible. Clean Product/Offer markup and an accurate Merchant Center-style feed serve as a reasonable common baseline across all three.

How often does priceValidUntil actually need to change?

There's no universal rule, but leaving it blank or letting it expire is treated as a reliability signal by Google's own systems. A practical approach is setting it 30-90 days out and refreshing it on a schedule tied to your actual pricing review cadence, not a one-time setup task.

Can per-seat pricing be marked up if the page displays a single number like "$49/month"?

Only if the basis is stated somewhere machine-readable. Visual layout (a small "/user" under the price) isn't reliably parsed the same way text is. Pairing visible text ("$49 per user, per month") with UnitPriceSpecification markup covers both a human skimming the page and a system extracting it.

Will fixing structured data stop AI systems from ever misquoting our price?

No single fix eliminates the risk entirely. Training-data lag, third-party listings, and negotiated pricing complexity all sit outside what markup can control. What structural fixes reliably do is remove your own site as the source of the ambiguity — so when an error happens, it's traceable to a stale cache or a third-party page, not to your own pricing page failing to state a clear number.