The Product record
One bounded record of a product feeds its landing, emails, share image, directory entry and agent prompt, so it says one thing everywhere.
A Product is what a Customer gets from a Startup, with a promise the Customer can rely on.Product startupbuilder.dev
An Offer is a Product, under a Brand, at a price, on terms, made by a Startup.Offer startupbuilder.dev
The Product record is one bounded record of a product: its name, Theme, addresses, plans and first calls. Every rendering of the product reads it, so the landing, the emails and the agent prompt never disagree. It projects startupbuilder.dev's Product, its Offers and their Prices, and defines no second Product.
What mdxui renders
Today each Page record carries the product's facts itself. Harbor's plans below are written into pages/harbor/pricing.json by hand, and its home page repeats the name and the first call.
Loading the record…How it maps
| Reads it | Takes | Status |
|---|---|---|
the landing | heropricingapi-callThe name, the Theme, the plans and the first call, written into each record by hand | Alpha: Renders today from the published packages |
the directory entry | product-listproduct-headThe category, the tags, the summary and the date it listed, written by hand | Alpha: Renders today from the published packages |
the agent prompt | The quickstart calls in agent order: the CLI, the MCP server, the SDKs, then curl | Spec: Written ahead of the package |
the share image | The name, the summary and the Theme; see Share image | Spec: Written ahead of the package |
the emails | The name, the Theme and the dashboard address; see Email | Spec: Written ahead of the package |
Status
The record and every rendering that reads it are Spec. The landing and the directory entry render today from Page records that repeat the same facts. A price changed in one record and not another is a disagreement no parse catches.
Intended Noun
noun: Product record
projects: startupbuilder.dev Product, its Offers and Prices; defines nothing new
fields:
name: { type: string, bound: open, required: true }
theme: { type: Theme, required: true }
mark: { type: icon name, required: false }
apiBase: { type: URL, required: true }
docsUrl: { type: URL, required: true }
dashboardUrl: { type: URL, required: false }
contactUrl: { type: URL, required: false }
statusUrl: { type: URL, required: false }
keyPrefix: { type: string, bound: open, required: false }
quickstart: { type: list of call, count: open, required: true, order: [cli, mcp, sdk, curl] }
plans: { type: list of Offer, count: open, required: true, each: [price or contact, limit, retention, seats, feature lines, action] }
questions: { type: list of onboarding question, count: open }
directory: { category: string, tags: list of string, summary: string, since: date }Every bound is open until the record ships. Each string gets one, measured from real products, as every Section type's strings have.