Make Your SaaS Price Machine-Readable (So AI Assistants Can Actually Quote It) A developer argues that SaaS pricing must be machine-readable in plain HTML for AI assistants to quote it, providing a self-audit command and a JSON-LD template. The post identifies common failures like missing currency codes or client-side rendering that hides prices from crawlers, and offers a server-rendering fix for Next.js. When someone asks an AI assistant "what is a cheap tool for X under $20 a month," the assistant can only put your product in that answer if it can read your price as plain, parseable text. Not as a number painted into a hero image. Not as a value that only exists after three React renders and an API call. Not behind a "Contact us" button. This is a narrower problem than it sounds, and it is very fixable. Below is the version I wish someone had handed me: the exact failure modes, a self-audit you can run in one line, and a complete, valid JSON-LD block you can paste and adapt. An extractor a crawler, an LLM fetching your page, an AI search bot sees roughly what curl sees, plus a best-effort attempt at running your JavaScript. If your price is not in the HTML it receives, and not in your structured data, then as far as that extractor is concerned, your price does not exist. It will reach for a competitor whose price it can read. So the whole job is: get a real, qualified price into text the machine receives on first load. Here is the difference that matters, side by side. First, an Offer an extractor can use: { "@type": "Offer", "price": "12.00", "priceCurrency": "USD" } And here are three Offer shapes that look fine to a human but break machine reading: // 1. Missing priceCurrency: "12" of what? Unusable. { "@type": "Offer", "price": "12.00" } // 2. priceRange on an Offer: invalid here, it belongs on LocalBusiness. { "@type": "Offer", "priceRange": "$12-$49", "priceCurrency": "USD" } // 3. No numeric price at all, just prose. { "@type": "Offer", "description": "Contact sales for pricing" } The valid one has two things every parser needs: a numeric price and an ISO priceCurrency . Everything else in this post is in service of producing that. Before changing anything, look at your page the way a crawler does. The fastest check is one line: curl -s https://yourdomain.com/pricing | grep -iE '\$ 0-9 |per month|priceCurrency' This fetches the raw HTML no JavaScript executed, which is the worst-case an extractor might face and greps for the three signals that matter: a dollar amount, a billing period phrase, and the structured-data currency field. What the output tells you: $ match but no priceCurrency :Run it against a couple of competitors too. It is a quick way to see who is legible to AI and who is not. If you want to be stricter, fetch with a bot-like user agent and inspect the body: curl -s -A 'Mozilla/5.0 compatible; ExtractorBot/1.0 ' \ https://yourdomain.com/pricing | grep -ic 'priceCurrency' A count of 0 means no machine-readable currency anywhere in the served HTML. The single highest-leverage fix. If your pricing page is a client component that fetches plans from an API and renders them after hydration, the first HTML payload contains no prices. Extractors that do not run your JS many do not, and even those that do may time out see an empty shell. You do not have to server-render the entire interactive pricing table with its toggles and tooltips. You need at least one real, representative price in the initial HTML. A common, pragmatic pattern: render a static default tier server-side, then let the client enhance it. In a Next.js App Router server component, this is the default behavior as long as you do not push the data fetch into a 'use client' boundary: js // app/pricing/page.tsx Server Component, no "use client" import { getPlans } from '@/lib/plans'; export default async function PricingPage { const plans = await getPlans ; const starter = plans.find p = p.id === 'starter' ; return