Buildventory — Product Guide

Buildventory is deal- and inventory-management software for residential builders and developers. It runs the day-to-day of selling new construction: your projects, your unit inventory, prices and statuses, a live availability board your whole team works from, deals and deposits, contacts, documents, and buyer-facing project sites.

Two things make it different from what's on the market today:

  1. It onboards from your real files. Drop the price sheets and unit trackers you already keep, and Buildventory reads them, shows you what it found, asks about anything missing, and hands you an account already filled with your data — no manual setup.
  2. It's AI-native. Every action a person can take, an AI agent can take too, through the same audited, permission-checked path. Connect Claude (or any MCP client) and it can manage your inventory for you.

This guide walks the whole product, screen by screen — from the smallest dialog to the signature board — plus how to run it and what you'll set at deploy time. Every screenshot is a real capture of the running app in its current look.


Try it first — the read-only demo

The fastest way to understand Buildventory is to walk through a populated one. The marketing site has an "Explore the live demo" button that drops you straight into a fully-seeded example developer ("Bramblewick Hollow") — no sign-up — as a read-only guest. You can open every screen, but nothing can be changed (the demo user is a read-only member, and the server rejects every write). A clay banner marks it as a demo and points to sign-up.

The demo is off by default and enabled per environment with DEMO_ENABLED + NEXT_PUBLIC_DEMO_ENABLED, then seeded with pnpm --filter @builderdesk/core run seed:demo (see `DEPLOY.md`). It's a great first stop before reading the rest of this guide.


The landing page and the AI onboarding flow

The public site leads with the thing that sets Buildventory apart: you start by dropping your files, not by filling out forms.

[ Screenshot: Landing page ]

1 — Drop your files

The onboarding "intake" accepts CSV, Excel, PDF, and images — unit trackers, price sheets, contact exports. Up to 20 files, 25 MB each. You give an email (that's where your finished account lands) and click Analyze my files.

[ Screenshot: Onboarding — file intake ]

2 — Watch it recognize each file

Files are analyzed off to the side by a background worker. Each one gets a recognition card in real time — "Unit price sheet · 1 sheet · 40 units" — so you can see exactly what Buildventory understood before it commits to anything. Unsupported files say so plainly.

[ Screenshot: Onboarding — recognition ]

How the reading works, and why you can trust it. For spreadsheets, Buildventory parses the grid deterministically and asks the AI only to *map columns* ("which column is the price?") — then applies that map to every row in code. The AI never invents unit numbers or prices; every value comes from your file. For PDF price sheets, where there's no grid to parse, the AI reads the values directly, but those are flagged "AI-read · confirm" and capped in confidence so you always review them before they're trusted. Anything the AI couldn't map is surfaced, never silently dropped.

3 — Review, then answer a couple of plain-language questions

You see a consolidated review of everything found — projects, unit types, units — with low-confidence values flagged for a look.

[ Screenshot: Onboarding — review ]

If something's missing or ambiguous (no currency on a phase, an unknown region, a price the AI wasn't sure about), Buildventory asks a short, plain-language question instead of making you re-enter everything.

[ Screenshot: Onboarding — gap questions ]

4 — Verify your email and get a seeded account

You create your account, verify your email, and Buildventory provisions your organization seeded with your real data. It tells you exactly what it created:

[ Screenshot: Onboarding — account seeded ]

Every seeded row is written through the same audited commands a human would use, tagged as an AI action with a link back to the source file, sheet, and row it came from — so the provenance of your starting data is on the record. Then you click straight into your board.

Honesty by design. If AI extraction isn't available, Buildventory doesn't fake it — it falls back to an honest "leave your files and we'll analyze them and email you" path. There are no fabricated results anywhere in the flow.

The daily-driver app

Inside, Buildventory is a fast, keyboard-first professional tool — think Linear or Attio, not a form-heavy website. There's a command palette on ⌘K, and prices, unit numbers, and dates render in a monospace type so columns line up and numbers are scannable. The visual language is warm and daylit — a bright, clean workspace with a single clay accent carrying every action.

Dashboard

Your organization at a glance — profile, team members and their roles, and the start of your activity timeline. That timeline is a spine of the product: every change, by a person, an API key, or an AI agent, is recorded with who did it and what changed.

[ Screenshot: Dashboard ]

Projects and unit types

A project holds your unit types (floorplans — beds, baths, square footage, base price) and your units. The project overview summarizes counts and value; a project tab holds its unit types.

[ Screenshot: Project overview ]

[ Screenshot: Unit types ]

The units table

Your inventory lives in a dense table built for real scale — it stays fast at a thousand units. It supports filtering by status/type/floor and CSV export.

[ Screenshot: Units table ]

Add a numbered range in one step. "Add units" opens a dialog with a Single mode and a Batch mode; Batch creates a whole numbered range at once (e.g. 201–260 on floor 2) with a live preview of the numbers it will create.

[ Screenshot: Add units — single ]

[ Screenshot: Add units — batch ]

Edit in place, or in bulk. Click a cell to edit a unit's price, type, or floor inline; select a set of rows to change their status together.

[ Screenshot: Inline edit ]

[ Screenshot: Bulk status ]

Extras

Each project has an Extras tab for parking spots, lockers, and storage — priced inventory you can assign to units. It starts with a clear empty state and fills in as you add extras. Prices everywhere carry the project's currency (Canadian or US dollars), enforced end-to-end.

[ Screenshot: Extras — empty ]

[ Screenshot: Extras ]

The availability board — the signature screen

The board is where builders live. It's a stacking elevation: floors as rows (penthouse up top), units as chips, each chip showing its number, price, and status as a soft color.

[ Screenshot: Availability board ]

Click a chip to peek at the unit and move its status; shift-click or drag to select a range and change many at once. It's fully keyboard-operable — arrow keys move between chips, Enter opens the peek, Space selects.

[ Screenshot: Board — unit peek ]

[ Screenshot: Board — range select ]

Unit statuses move through a strict state machine — available → hold → sold → closed, plus "unavailable" for withheld stock — and only through explicit actions, so a unit's status history is always meaningful. Two people can't accidentally sell the same unit; the system serializes those changes with row locks.

Deals and deposits

When a unit sells, you open a deal on it. The deals list starts empty and grows as you write business.

[ Screenshot: Deals — empty ]

[ Screenshot: Deals list ]

A deal links the unit (and, optionally, the buyer contact) and moves through a strict pipeline — draft → reserved → firm → closed, with cancel available early. Each transition drives the unit itself: reserving puts the unit on hold, making the deal firm marks it sold, cancelling releases it. Two people can't open conflicting deals on the same unit.

[ Screenshot: New deal ]

[ Screenshot: Deal detail ]

Each deal tracks its deposits (amount, due date, and status: expected → received → refunded), which you record and reconcile by hand today.

[ Screenshot: Deposit ]

Online payment collection is the one deposit feature that waits on a Stripe connection — until then the deal shows an honest "Connect Stripe to collect payments online" panel, not a fake pay button.

Deposit reminders

When a deposit has a due date and the deal's buyer has an email, Buildventory schedules a small set of reminder emails automatically — a week before the date, on the day, and a few days after if it is still outstanding. A background job sends the due ones each day (and whenever the worker starts), then marks them sent; a deposit that is received or refunded before a reminder fires is quietly skipped rather than nagging a buyer who has already paid. Every deposit shows its reminder history — planned, sent, or skipped (with the reason) — beneath the deposits on the deal page, and you can Remind now to send one by hand at any time.

Reminders need an email connection (Amazon SES) in production. Without it they no-op gracefully — the schedule still records what it would have sent as "skipped", nothing breaks. Locally they simply run against the console mailbox.

Collections

Each project view carries a Collections card: how much is expected, received, and refunded across that project's deposits, an overdue breakdown aged into buckets (up to 7 days, 8–30 days, over 30 days past due), and the deposits due in the next 30 days. It is the project-level companion to the home dashboard's cross-project "overdue deposits" attention row, so you can see where the money stands at a glance.

Commissions

Every deal tracks its commissions — what each agent or house split earns, and where that payout stands. A deal can carry several commissions at once (a listing-agent share and a buyer's-agent split are just two rows), each with its own recipient, basis, and status.

You set the rate two ways. Commission rules in Settings are auto-include defaults: give a recipient kind (listing agent, co-broker, or internal) a percentage or a flat amount, and when a deal goes firm the matching commission is created automatically — firming is the earn event, exactly like the sales-this-month math. A project-scoped rule overrides the org-wide default for the same recipient kind. You can also add commissions to any deal by hand.

[ Screenshot: Deal detail — commissions ]

Each commission moves through pending → payable → paid, or is voided. It becomes payable only once the deal is firm or closed; marking it paid records the payment reference (cheque number, wire reference) for the paper trail. Cancelling a deal automatically voids every commission that hasn't been paid. The project overview rolls the whole picture up — pending, payable, and paid totals at a glance.

The commissions a deal creates or voids *on its own* — the ones auto-added when it goes firm, and the ones voided when it's cancelled — ride that deal's own activity-timeline entry (the firm / cancel event) rather than logging separate entries, so the audit trail reads as one action with its consequences. Commissions you add or move by hand each record their own timeline entry as usual.

Commissions are tracked, not paid through the platform: Buildventory records the amount, the status, and the reference for a payout you make outside it. Moving the money (ACH/Stripe payouts) is a deliberate non-goal for this release, the same discipline deposits follow.

Contacts

A lightweight CRM for the people around your deals — buyers, realtors, and vendors. Starts empty, then fills as you add people; each contact has details, a type, notes, and links to the deals they're part of.

[ Screenshot: Contacts — empty ]

[ Screenshot: New contact ]

[ Screenshot: Contacts list ]

[ Screenshot: Contact detail ]

Email campaigns and nurture sequences

The Marketing section turns your contact list into an outbound channel — one-off campaigns (a broadcast to a segment) and nurture sequences (a timed drip that fires automatically on a trigger). Both ride the same email transport as the rest of the app, so with no email provider configured they still compose and preview; sends are simply recorded skipped and, in dev, test sends land in the console/mailbox.

Composing a campaign. Give it a name, a subject, and an HTML body. Merge fields — {{firstName}}, {{lastName}}, {{fullName}}, {{company}}, {{email}} — are filled per recipient at send time and HTML-escaped, so a contact whose name contains markup can never distort another recipient's email. A live preview pane shows the body as it will render. Pick a segment (everyone, a contact type, or every buyer on a project) and the composer shows the exact recipient count — the same resolution the send uses, so the number you confirm is the number that go out. Send a test to yourself first, then send for real behind a confirmation that states the count.

What you can see afterward. Each campaign's detail page carries a per-recipient table — delivered, opened, clicked, unsubscribed, or skipped (with the reason) — so the outcome is honest, not a vanity number. Opens are tracked by a pixel, clicks by a per-link redirect, and every send carries an unsubscribe footer.

Sequences are a set of ordered steps, each with its own delay, subject, and body. A sequence has a trigger — a new contact, a new buyer configuration lead, or a reserved deal — and when that trigger fires, the matching contact is enrolled exactly once and receives the steps on schedule. Enrollments stop automatically if the person unsubscribes or the triggering deal is cancelled. The editor shows how many people are active, completed, unsubscribed, or exited.

Compliance is built in, not bolted on (CASL / CAN-SPAM). Canadian anti-spam law (CASL) and CAN-SPAM require a working unsubscribe and that you honour it. So every campaign and sequence email carries an unsubscribe link and a one-click List-Unsubscribe header unconditionally — there is no author toggle to remove it. Unsubscribing writes the address to a per-organization suppression list and exits any active sequence enrollments. At send time, every campaign and sequence send checks that list and skips suppressed addresses — visibly, as a suppressed row in the table. You can also add addresses to the suppression list by hand.

Marketing vs. transactional — a deliberate distinction. Suppression applies only to *marketing* email (campaigns and sequences), which a recipient has consented to and can opt out of. It does not apply to *transactional* email — deposit reminders, disclosure delivery, team invitations — which rests on a different basis (an existing business relationship about a specific transaction) and which a buyer cannot opt out of without losing information they need. Keeping the two consent bases separate is why the suppression check lives in the marketing send path only.

Documents

Upload your contracts, floorplans, and price sheets and keep them organized, with the option to attach a document to the project or deal it belongs to. Files are stored through the same storage layer the rest of the app uses (local disk in development, S3 in production), and every upload is scoped to your organization — another org's files are never reachable, even with a direct link.

[ Screenshot: Documents — upload ]

[ Screenshot: Documents list ]

[ Screenshot: Rename document ]

Sending a document out for e-signature waits on a DocuSign connection — the linked documents on a deal show an honest "connect DocuSign to send for signature" state, not a fake signing flow.

[ Screenshot: Document — DocuSign seam ]

Disclosures and amendment delivery

Pre-sale condo and new-construction sales run on disclosure documents — a disclosure statement, then amendments as things change — and on being able to prove each buyer received them. Buildventory models this as a compliance primitive: a versioned disclosure document, delivered per recipient, with tracked receipt evidence. It is not a jurisdiction rules engine — the regional wording lives in the PDF you upload; Buildventory tracks the delivery and the receipt.

Each project has a Disclosures tab. Register a disclosure from a document you have already uploaded, choosing statement or amendment and a title; Buildventory numbers them in order. Deliver to all sends every non-cancelled deal's buyer a tracked link — the document itself is never attached; the email carries only an unguessable link to a clean buyer page. The operation is idempotent: run it again after adding a deal and only the new buyer is sent to, the rest are reported as already delivered.

The buyer opens the link to a plain page — your org and project name, the disclosure, and the document to read — with an Acknowledge button. Opening the page records that they viewed it; pressing acknowledge records their receipt. Treat acknowledgement as the strong evidence: a "viewed" stamp can be triggered by an email client's link scanner or a browser prefetch opening the page automatically, whereas an acknowledgement is a deliberate button press by the recipient. Each disclosure's delivery table shows exactly where every recipient stands — pending, sent, viewed, acknowledged — with the timestamps, so the tab is a compliance report you can read at a glance. You can re-send a delivery (say, after email was connected), and copy a recipient's tracked link to hand out by other means.

A delivered disclosure is immutable: once anyone has been sent it, it cannot be edited or removed — a correction is a new amendment, which is the compliance-correct posture. The recipient's name and email are snapshotted at send time, so changing a buyer's contact details later never rewrites the evidence of what was delivered to whom.

Delivery emails need an email connection (Amazon SES) in production — the same dependency as deposit reminders. Without it, deliveries are still created and tracked as pending with a clear reason, and the tracked links still work, so staff can hand them out manually; nothing pretends to have sent. Locally they run against the console mailbox.

Contract generation and signing

Buildventory turns a deal's data into a finished, data-merged contract — no copy-pasting the buyer's name, the unit, the price, the options, and the deposit schedule into a Word template. Your organization authors the template, exactly like disclosures own their regional wording: Buildventory merges and generates, it does not embed a legal rules engine.

In Settings → Contract templates you write a template per slot — a purchase agreement, a deposit receipt, an amendment — as plain HTML with merge fields ({{buyerFullName}}, {{unitNumber}}, {{salePrice}}, and the rest) and two generated tables you drop in with {{optionsTable}} and {{depositTable}}. A live preview renders your draft against a sample deal in a sandboxed frame as you type, so you can see exactly how it reads. You can keep an org-wide template and override it for a specific project. Every save is a new revision with an active pointer, and the revision history is right there — because a generated contract pins the exact revision it used, editing a template never rewrites what a buyer already signed.

On a deal, the Contracts card generates the package: pick which documents to produce, and Buildventory merges each template with that deal's real data, renders a PDF, and stores it as an organization-scoped document you can download. From there the lifecycle is draft → generated → sent for signing → signed, or voided — regeneration is allowed only while a package is still a draft or freshly generated, never after it has gone out for signature.

Signing works two ways, and the manual path is first-class, not a fallback: download the generated PDF, have it signed offline, upload the signed copy, and press Mark as signed with a reference. When a DocuSign connection is configured, Send for signing creates the envelope and a signature-verified webhook flips the package to signed the moment the envelope completes. Without DocuSign, Send for signing says so plainly and points you at the manual path — it never fakes a signing flow.

Sending for e-signature needs a DocuSign connection (see DEPLOY.md). Without it, generation, download, and the manual signed path all work fully — only the automated envelope send waits on credentials.

Options and upgrades

New construction is rarely one price. Buyers add a finished basement, pick a countertop package, choose an elevation. Buildventory models all of that as a per-project options catalog with a real rules engine — so a configured price is always a legal, priced, auditable number, whether staff build it on a deal or a buyer builds it themselves.

The building blocks — groups and options. Options live in groups, and a group carries the selection semantics: a kind (structural or design), a selection mode (*pick-one*, like a single elevation, or *pick-many*, like a set of add-ons), whether it's required (at least one choice must be made), and an optional cutoff date after which its selections lock. Inside a group, each option is a named line with a price delta in the project's currency (zero is allowed, for a no-charge standard), the unit types it's available on (empty means all), and two cross-option rules: requires (this option pulls in another) and excludes (this option can't be chosen alongside another).

One rules engine, everywhere. Those rules aren't just UI hints — they're a single, pure validator. Picking two options from a pick-one group, leaving a required group empty, choosing an option not offered on the unit's type, selecting one that requires a missing option or excludes one already chosen, or adding anything past its cutoff — each is a named violation. The exact same validator runs three places: in staff's live deal panel, in the buyer's configurator as they toggle, and — authoritatively — server-side before anything is written. The browser preview and the server can never disagree, because they're the same code.

Authoring the catalog. Every project has an Options tab where you build the groups and options: name a group, set its kind and selection mode, mark it required, add options with their price deltas, restrict them to unit types, and wire up requires/excludes against the project's other options. Prices carry the project's currency, enforced end-to-end.

[ Screenshot: Options catalog ]

Selecting options on a deal. On a deal, an options panel shows every group for that unit's type with its current selections and a live grand total — the unit's sale price plus the selected option deltas — that updates as you toggle, validated instantly. When you commit, each selected option's price is snapshotted onto the deal, so a later catalog price change never rewrites a deal you've already written. And options are grandfathered: if an option (or its group) is archived after a deal already chose it, that line stays on the deal, keeps its snapshotted price in the total, and is clearly marked as no longer offered — you can still remove it, you just can't newly add a retired option.

[ Screenshot: Deal options and grand total ]

The buyer configurator. Every published project site can let a shopper configure a home themselves. From an available unit they open the configurator, move through the same groups and options — filtered to that unit type — and watch a running total (unit list price plus options) update live, with the same rules engine giving instant, honest feedback. When they're happy, they leave their name and email and request that configuration.

[ Screenshot: Public buyer configurator ]

Configured requests are leads, not deals. A buyer submission does not open a deal or hold any inventory — it lands as a configuration request: a lead that captures the buyer, the unit, the chosen options, and an estimated total, created atomically through the same audited command path staff use (so there are never orphaned contacts, and the option set is re-validated server-side before anything is saved). Because it's a lead, a unit can collect many requests without any of them locking its single deal slot. Staff review them on the project's Requests tab — each showing the buyer's details, unit, chosen options, estimate, and status — and either convert the one they want into a real deal (which is where the unit's exclusive deal is intentionally taken, options carried over and re-validated against the current catalog) or dismiss it.

[ Screenshot: Configuration requests — review and convert ]

Like every other domain in Buildventory, options and upgrades are exposed through the one command registry — so the option, group, selection, and configuration-request commands are in the ⌘K palette and available to an AI agent over MCP, all through the same permission-checked path — and every change, from authoring a group to converting a request, lands on the activity timeline.

Selection worksheets and allocation

Before a hot release opens, interest outruns inventory: several buyers and their realtors all want the same few homes. Buildventory captures that with selection worksheets — expressions of interest recorded *ahead* of allocation — and turns them into a clear picture of where demand concentrates, so the developer can allocate deliberately instead of first-come-first-served.

A worksheet is an expression of interest, not a hold. Each project has a Worksheets tab where staff capture a worksheet on behalf of a buyer or realtor: the interested party (any contact), optionally the realtor who submitted it, and up to three ranked unit choices (first, second, third) drawn from the project's available units — a unit can't be picked twice, and the ranks are unique. A worksheet holds no inventory; many worksheets can rank the same unit. It starts submitted and waits.

[ Screenshot: Worksheets — expressions of interest with ranked choices ]

The demand view — where interest concentrates. A Demand view turns the live (submitted) worksheets into a per-unit table: for each unit, how many worksheets rank it, split into first-, second-, and third-choice counts, ordered by first-choice demand. At a glance you see the units three buyers all want first versus the ones picked up only as fallbacks — the launch question "where is demand concentrated?" answered from real interest.

[ Screenshot: Demand — per-unit rank splits ordered by first-choice count ]

Allocation converts interest into a real hold. When the developer decides, they allocate a worksheet to one of its chosen units. Allocation is gated: the unit must be one of that worksheet's choices *and* currently available. In a single transaction it puts the unit on hold — the exact same inventory event as a manual hold, driven through the same unit state machine, so there's no shadow "allocated" state to reconcile — and stamps the worksheet allocated. If two worksheets are allocated to the same unit, the unit machine serializes them and only the first wins; the loser's allocation rolls back cleanly, unit and worksheet untouched. Staff can also decline a worksheet (the developer passes) or withdraw it (the party pulls out); neither touches inventory. Once a worksheet leaves *submitted* it's terminal, and its choices are frozen.

Worksheets can be staff-entered *or* submitted by a realtor through the broker portal (below); because every action is one audited command, broker self-submission plugs into the same worksheet.create command — an entry path, not a new system. And like every other domain, the worksheet commands are in the ⌘K palette and available to an AI agent over MCP, with each allocate, decline, and withdraw on the activity timeline.

The broker portal — live inventory for the realtors who sell it

Condo pre-sale volume is broker-driven, so Buildventory gives your realtors a controlled, live window into inventory and a way to submit interest for their clients — without ever making them members of your organization. Access is a capability grant, not an account: a realtor stays a contact in your CRM, and you grant them per-project access that you can revoke at any time.

Sharing a project. Every project has a Sharing tab. You pick a realtor contact, choose how much they see — Full (status *and* price) or Availability only (status, no prices) — and grant access. Buildventory mints a single-use invite link. With email configured it's sent to the realtor; either way you can copy the link and send it yourself, so the feature works with zero email setup. The tab shows each realtor's grant at a glance: their tier, whether the invite is still outstanding or the grant is active, the transparency setting, when they were last seen, and how many worksheets they've submitted for the project. Revoke is one click and takes effect immediately — the realtor's very next request is refused.

[ Screenshot: Project Sharing — realtor grants, transparency, last-seen, and tier ]

What the realtor sees. Opening their invite link signs the realtor into a dedicated broker portal (a separate, cookie-based session — never your app login). They see only the projects you've shared, and for each one a live availability board built from the same inventory the public site uses: the site plan with status-colored markers and a unit grid. Prices appear only when you granted Full transparency; an Availability only grant never sends prices to the portal at all — the board simply reads "price on request."

[ Screenshot: Broker portal — the live availability board for a shared project ]

Submitting for a client. From a shared project the realtor can submit a worksheet for a client: the client's details (captured as a buyer contact in your CRM) and up to three ranked unit choices. It lands in your project's Worksheets tab as a portal-sourced worksheet, attributed to the submitting realtor — the allocation decision stays entirely yours. The realtor sees their own submissions and statuses under My worksheets, and nothing beyond the projects you've granted.

A note on contacts. For privacy and isolation, a realtor's portal submission only ever reuses a buyer contact *they* created through the portal — it never attaches to, or reveals, a contact already in your book under the same email. This means a portal submission can create a duplicate contact (e.g. a buyer your team already entered). That's deliberate: it keeps one realtor from probing or writing to another party's record. Reconciling duplicates is a quick staff task today; contact-merge tooling is on the roadmap.

[ Screenshot: Broker portal — a realtor's own submitted worksheets ]

Agent tiers. Each realtor carries a display-only tierbronze, silver, or gold — derived from their firm-and-closed deal volume (deals that carry a commission to that realtor). It shows on the Sharing tab and on the realtor's contact page, so you can see at a glance who your top-producing agents are. Tiers are informational in this version; automation on them is a deliberate next step.

The buyer portal — a home, for the people buying it

The sale doesn't end when a deal goes firm — that's when the two-year wait for a pre-construction home *begins*. Buildventory gives each buyer a private "My Home" portal so they can follow construction, see their contract, and find their documents without emailing your team for every update. Like the broker portal it's a capability grant, not an account: the buyer stays a contact in your CRM, and access is tied to their deal.

It opens itself. When a deal goes firm, Buildventory automatically invites the buyer — no step to remember. With email configured the invite link is sent to them; either way the deal's Buyer portal card (on the deal page) shows the grant, lets you copy the link, re-send a fresh one, or revoke access — which takes effect immediately. If a deal is cancelled, the buyer's access is revoked automatically. (You can also invite a buyer manually before firm from the same card.)

[ Screenshot: Deal page — the buyer portal access card (auto-invited on firm; copy link, re-send, revoke) ]

What the buyer sees. Opening their link signs them into My Home (a separate cookie-based session — never your app login, and never the broker portal). They see exactly their deal and nothing else:

  • Construction progress — a timeline of the project's milestones (excavation, structure, interiors, occupancy), newest first. This is the same story for every buyer in the project — you write it once.
  • My home — their unit, their option selections, and their purchase price. This is their own contract, so the price is always shown (there's no "availability only" gating here).
  • Deposit schedule — a read-only mirror of their deposits with due dates and statuses, so the deposit-reminder emails finally have a landing page.
  • Documents — the documents attached to their deal, each downloadable through the authenticated portal (never a public link).

[ Screenshot: My Home — the buyer portal: construction progress, their home and price, and the deposit schedule ]

Milestones — write the story once. Every project has a Milestones tab: a timeline editor where you add construction updates with a title, a date, an optional note, and a visibility — *Shown to buyers* (appears in every buyer's portal for that project) or *Internal* (a staff-only note that is never sent to the portal). Archive an entry to retire it from the timeline; it's kept for history. A firm deal whose project has no milestones yet shows an honest "construction updates will appear here," so the portal is never empty or misleading.

A note on scope. The buyer portal started as a read-only window. Warranty and deficiency tracking (below) is the first buyer *workflow* built on this same session — the one place a buyer writes back to your team.

Warranty and deficiency tracking

Possession is a beginning, not an end. The months after a buyer moves in are when the punch list gets worked: the tap that drips, the door that rubs, the window seal that lets a draft in. Buildventory tracks each of these as a deficiency on the buyer's deal, and bundles them into formal warranty claims — so nothing gets lost in a text thread.

Buyers report from their portal. The buyer's My Home portal gains a Deficiencies section: their open list with live statuses, and a short form to report a new one — a title, where it is (kitchen, ensuite), how serious it is (cosmetic, functional, or major), and a description. It's text-only in this version — the form says so plainly, and photo attachments are the next step. Every buyer report is born Reported; a submission can never arrive already resolved.

Staff work the queue on the deal page. Each deal has a Deficiencies card listing every item on that home — buyer-reported and staff-entered alike — with the buttons that move each one along its lifecycle:

Reported → Acknowledged → Scheduled → Resolved (or Disputed). You acknowledge you've seen it, schedule a remediation date, resolve it with a note on what was done, or dispute it with a reason (out of warranty scope, pre-existing, buyer-caused). Every transition is stamped and lands on the activity timeline, and the buyer sees the new status in their portal immediately — no notification email needed in this version, because the portal *is* the channel. Staff can also enter a deficiency directly (a walkthrough punch-list item), starting it at whatever stage it's really at.

[ Screenshot: Deal page — the deficiency queue: report, acknowledge, schedule, resolve, dispute ]

The project punch list. Every project has a Deficiencies tab — the PM's whole-project view across all deals, filterable by status and severity, with a summary card counting what's open by status and by severity. It's the one screen that answers "what's still outstanding across the building?"

Warranty claims bundle the work. A warranty claim is the formal wrapper — an org-vocabulary kind (e.g. *Tarion 30-day*; there's no jurisdiction engine, you name it) that bundles a deal's deficiencies as its work items. A claim tracks Submitted → In review → Settled, and because a claim can only bundle deficiencies from its own deal, a claim can never accidentally span two homes.

[ Screenshot: Buyer portal — the Deficiencies section: report a new item, follow its status ]

Activity timeline

Everything that happens is on the timeline, and AI actions are labeled with the agent that took them — so you can always answer "what did the AI do?"

[ Screenshot: Activity timeline ]

Team and settings

A dedicated settings area covers your account, security, organization, and team.

  • Account — your name and where to manage security.
  • Security — password, and two-factor authentication: enroll with an authenticator app (QR + secret) and save your backup codes.
  • Organization — the org's name and subdomain.
  • Members — invite teammates by email, set their role (owner/admin/member), see pending invitations, and remove members. Invitees get an email with an accept link.

[ Screenshot: Settings — account ]

[ Screenshot: Settings — two-factor ]

[ Screenshot: Settings — organization ]

[ Screenshot: Members ]

[ Screenshot: Invite a member ]

Integrations — webhooks and QuickBooks

Buildventory ships the integration substrate, not a wall of bespoke connectors: outbound webhooks (Zapier-compatible), accounting exports, and a QuickBooks connection.

  • Webhook endpoints. Register an https URL and pick which events it should receive — deal.created/reserved/firm/closed/cancelled, deposit.received/refunded, commission.payable/paid, contact.created, unit.status_changed, and configuration_request.created. Buildventory emits each event the moment the underlying action commits (never for a rolled-back one), and delivers a small, versioned JSON payload — ids and accounting-shaped facts, never a third party's email or a document's bytes. A signing secret is shown once at creation (copy it then; regenerate to rotate). Every endpoint has a delivery log with per-attempt status, the last response, and a Retry button for failures. Deliveries retry automatically with backoff (1m → 10m → 1h → 6h) before they're marked exhausted. Each POST is HMAC-signed (X-BuilderDesk-Signature) so your receiver can verify it's really from Buildventory. Point one at Zapier/Make and you've wired the long tail of tools at once.
  • QuickBooks. A Connect QuickBooks card links your accounting so received deposits and paid commissions post as draft journal entries for your bookkeeper to review (drafts only — Buildventory never posts to your ledger). Until the owner provisions the QuickBooks app credentials on the deployment, the card says so plainly and connects nothing — the same honest, config-off posture as e-signature.

Everything here is owner/admin only; a read-only member sees the current state but no controls.

Reports — accounting exports

The Reports tab downloads the numbers your bookkeeper actually wants as CSV: deals (sale price, status, the reserved/firm/closed dates), deposits (amount, status, due and received dates, method), and commissions (recipient, rate, amount, status, the payment reference). Money exports in exact cents-based decimals, dates and ids in machine-friendly columns, so a spreadsheet imports them cleanly — and cells are guarded against spreadsheet formula-injection. The same rows are available programmatically via the export.* queries (REST + MCP) with cursor pagination for a live sync.


Buyer-facing public sites

Every project can publish a public, no-login buyer page where prospects browse live availability. From the project's "Public site" tab you set the headline and copy, upload a site-plan image, and run site-plan-from-image: AI reads the plan and places a hotspot on each unit, which you review and confirm before publishing. The public page then shows the plan with availability-colored hotspots and the available units.

[ Screenshot: Buyer-facing public site ]

The public page is strictly published-only and field-whitelisted: it exposes just the project name, your marketing copy, the plan, confirmed hotspots, and per-unit number / type / beds-baths-sqft / list price / status. Internal costs, notes, contacts, deals, and other orgs' data are never reachable, and an unpublished or unknown site is an indistinguishable 404.

Online reservations — let a buyer hold a home themselves

Everything else a visitor can do on a public page creates a lead, which holds no inventory: five people can enquire about the same home and you decide who gets it. A reservation is the opposite — it takes the home off the market for a set number of hours and names the person it is held for.

Because that hands a stranger real inventory, it is off until you turn it on. In the hold policy you enable online reservations and set how long one lasts (1–168 hours). Until you do, the public drawer shows no reserve action at all — the feature does not appear to exist on your site.

Once it is on, a buyer opens a home, fills in the same details an enquiry asks for, and presses Reserve this home. In one step Buildventory captures them as a lead *and* places a real hold, and tells them exactly when it runs out. If two buyers press it at the same instant, exactly one wins; the other is told the home was just taken — never who took it. Your realtors can do the same from the broker portal for their clients.

In the units table those holds look like any other — held for, expires — with one addition: an Online or Broker badge, so nobody on your team wonders which colleague placed a hold at 2am. The reservation expires and auto-releases on its own, or you release it by hand like any hold, and the buyer is already sitting in your leads list either way.

The guards, because a bot reserving inventory is a denial-of-sales attack: reservations are opt-in per org and off by default; the reserve form is rate-limited far harder than any other public form; one active reservation per email per community; and every online hold expires. If you run this in production, configure Turnstile — it is what makes each attempt cost something.

The hold policy — how long a hold lasts

Separately from the above, you can set a default hold duration for your own team's holds (1–720 hours). With it set, reserving a deal or allocating a worksheet stamps an expiry that far out, and the hold auto-releases when it lapses instead of sitting on your inventory forever. It is a default, not a cap: name a different expiry when you place a hold and yours wins, or clear it to keep the hold open-ended. Leave the policy unset and holds behave exactly as they always have — open-ended, released only when you say so.


AI and MCP control

Buildventory is built so that every operation lives in one place — a command and query registry — and is exposed identically to three surfaces: the web app, a REST API, and an MCP server. An AI agent connecting over MCP can do anything the UI can — create projects, add units, put a unit on hold, open a deal, read your board — all with the same permission checks and the same audit trail.

Connecting is standard MCP OAuth. When you connect an agent, you see a consent screen that tells you plainly what you're authorizing, warns you if the app isn't a verified first-party client, and shows where you'll be sent — so granting an AI the ability to run commands in your organization is a deliberate, informed step.

[ Screenshot: OAuth consent ]

Because the AI uses the exact same audited command path as a person, every agent action lands on your activity timeline, tagged with the agent's name. There's no privileged back-channel — the AI is just another authenticated actor with a role. (The same AI engine also powers the file-drop onboarding and the site-plan hotspot extraction.)


Platform administration

Separate from the per-organization app, there's an operator console at /admin for you as the platform owner — a cross-organization view of every org, its users, and recent activity.

[ Screenshot: Platform admin — organizations ]

[ Screenshot: Platform admin — organization detail ]

[ Screenshot: Platform admin — users ]

You can suspend an organization, which immediately blocks its members from the app until you lift it.

[ Screenshot: Platform admin — suspend ]

The console is deliberately undiscoverable: there's no link to it, it's gated on a platform-admin flag granted only from the server (a small script), and to anyone without that flag the whole surface answers "not found" — normal org owners and admins can't reach it. (Org billing/subscription management is the one panel that waits on a Stripe connection.)


Security, tenancy, and accounts

  • Multi-tenant, isolated by design. Every organization's data is walled off. Isolation is enforced in the data layer (not just checked in application code) and is a standing, automated test gate — one org can never see or touch another's data.
  • Real accounts. Email/password sign-in, email verification, password reset, and two-factor authentication (TOTP with backup codes).
  • Roles with real teeth. Members have roles (owner/admin/member) that map to permissions checked at the command layer *before* anything runs — so, for example, the read-only member role (which the demo uses) cannot execute a single write, no matter what the UI shows.
  • Canada and US, first-class. Currency per project (CAD/USD), province/state and postal/ZIP validation, and locale-aware formatting throughout.

Running it locally

Prerequisites: Node 22+, pnpm, and Docker (for local Postgres + Redis).

# 1. Start local Postgres + Redis
docker compose -f infra/docker-compose.yml up -d

# 2. Configure environment (the defaults work for local dev)
cp .env.example .env

# 3. Install and set up the database
pnpm install
pnpm --filter @builderdesk/db run db:migrate

# 4. Run everything (web, API, MCP server, and the analysis worker)
pnpm dev

Then open the web app. Ports: web 3000 · API 4000 · MCP 4010 · Postgres 5433 · Redis 6379.

To watch the full test suite: pnpm turbo run test (unit + integration against real Postgres/Redis). The browser end-to-end suite is pnpm --filter @builderdesk/web test:e2e:browser, and it also regenerates every screenshot in this guide.

Want the populated demo locally? Set DEMO_ENABLED=1 and NEXT_PUBLIC_DEMO_ENABLED=1 in .env, run pnpm --filter @builderdesk/core run seed:demo, and the landing page's "Explore the live demo" button will drop you into it read-only.


Going live

Everything the product needs is built — going live is provisioning hosting and setting secrets, nothing more. The complete, step-by-step deployment guide is in `docs/DEPLOY.md`. In short, you'll:

  • Deploy four services (web, API, MCP, worker) plus managed Postgres and Redis. Railway service manifests are provided in infra/railway/.
  • Set the required environment variables (documented in full in .env.example and DEPLOY.md): a strong auth secret, your public URLs, an SES sender for email, an S3 bucket for uploaded files, and your Anthropic API key for AI extraction.
  • The database migration runs automatically as a pre-deploy step; background maintenance jobs (session cleanup, retries) are scheduled automatically by the worker.

A few capabilities are wired to degrade honestly until you connect them, each showing a plain "connect X" state rather than a fake flow:

  • AI onboarding + site-plan extraction fall back to email capture / manual placement without an Anthropic key.
  • Online deposit collection and billing wait on a Stripe connection (deposits are tracked and reconciled by hand until then).
  • Contract e-signature sending waits on DocuSign — contract templates, generation, download, and the manual signed path all work without it; only the automated envelope send degrades.
  • All three anonymous write surfaces — onboarding session creation, the public buyer configurator, and the marketing lead-capture form — are protected by per-IP rate limiting; add a Cloudflare Turnstile key (TURNSTILE_SECRET_KEY + NEXT_PUBLIC_TURNSTILE_SITE_KEY) and each also requires a human-verification challenge, verified server-side (unset → rate-limited only, degrades invisibly).

What's built, and what's next

Buildventory today is a complete, tested platform:

  • Foundation — the AI-native command layer, multi-tenant data model, auth (team invites + two-factor), and the MCP server.
  • Inventory — projects, unit types, units (with the status state machine), extras, and the availability board.
  • Deals — the deal pipeline wired to inventory, with deposit tracking.
  • Commissions — per-deal commission entries and splits, org/project auto-include rules applied at firm, and a payable → paid tracking workflow (payouts tracked, not moved).
  • Options & upgrades — a per-project catalog with a shared rules engine, staff deal selection with live totals, and a public buyer configurator that lands rules-validated leads.
  • Selection worksheets — ranked expressions of interest, a demand view, and allocation that converts the chosen worksheet into a real unit hold.
  • Disclosures — statement/amendment delivery with tracked buyer receipts.
  • Contract generation — org-authored, revisioned templates merged with deal data into generated PDFs, a per-deal package lifecycle (draft → generated → signed / voided), a first-class manual signed path, and a config-off-safe DocuSign send + signed webhook seam.
  • Broker portal — per-project capability grants to realtor contacts (never org members), a cookie-gated portal with a transparency-controlled availability board, realtor worksheet submission into the same worksheet domain, and display-only agent tiers.
  • Contacts — a lightweight CRM linkable to deals.
  • Documents — organization-scoped file storage, attachable to projects and deals.
  • AI onboarding — the file-drop → analyze → review → seed acquisition flow.
  • Buyer-facing public sites — published project pages with AI site-plan-from-image.
  • Platform admin — the cross-organization operator console.
  • Read-only demo — one-click, seeded, safe.

Every one of these is driven from the web, the REST API, the ⌘K palette, and MCP at once, and every change is audited. What's next is short and mostly one third-party connection away: online payments + subscription billing (Stripe) and contract e-signature (DocuSign). The architecture — one command registry, one audited path, provider-agnostic integrations — is built to grow into those without rework.