BidFlow
Live
BidFlow is an AI-powered platform that transforms the contractor bidding process. Using Anthropic's Claude AI, it generates professional proposals from simple project descriptions. Contractors can manage the full lifecycle from bid to invoice with Stripe Connect handling payments between clients and contractors. Real-time project management keeps everyone in sync, and the AI learns from past proposals to improve over time.
Solo · 3.3 months · 515 files · ~90K lines TypeScript
The Problem
Contractors lose hours on every estimate — measuring rooms, pricing scope, writing proposals, chasing payments. Most send rough numbers via text, lose bids to faster-responding competitors, and never see the data that would help them win the next one. BidFlow turns photos and a short description into a priced, branded proposal in minutes, then runs the marketplace that gets the contractor paid.
Tech Stack
Architecture
Next.js 16 App Router with a Capacitor iOS build target — same TypeScript codebase ships as web and native app from one repo.
Supabase Postgres with row-level security; multi-tenant by contractor org with end-to-end auth via @supabase/ssr cookies.
Six distinct Anthropic Claude integration points: estimate generation, blueprint extraction, receipt scanning, photo classification, septic plan extraction, and pre-flight validation.
Stripe Connect with destination charges and platform fee — contractors onboard via Stripe Express; clients pay through hosted Checkout.
@react-pdf/renderer for branded proposal PDFs; Resend for transactional email; Upstash Redis for rate-limiting AI endpoints.
Sentry for production error tracking, Vercel Analytics, Firebase Admin for push notifications, IndexNow auto-submit for SEO.
Key Features
Code Highlights
Forced tool use for structured AI output
src/app/api/extract-plans/route.ts
The plan-extraction endpoint takes 1–4 photos of a blueprint and returns structured scope. The key move is `tool_choice: { type: "tool", name: "extract_plan_details" }` — Claude must return a tool call matching the schema exactly. Result is Zod-validated before reaching the client. No free-text JSON parsing, no hallucinated keys.
const EXTRACT_TOOL_SCHEMA = {
name: "extract_plan_details",
description: "Extract construction scope from blueprint/plan photos",
input_schema: {
type: "object" as const,
properties: {
rooms: { type: "array", items: { /* ... */ } },
scope_items: { type: "array", items: { /* ... */ } },
questions: { type: "array", description: "Questions for the contractor", items: { /* ... */ } },
},
},
};
const response = await anthropic.messages.create({
model: AI_MODEL,
temperature: 0,
system: EXTRACT_SYSTEM_PROMPT,
tools: [EXTRACT_TOOL_SCHEMA],
tool_choice: { type: "tool", name: "extract_plan_details" },
messages: [{ role: "user", content }],
});
const toolUse = response.content.find((b) => b.type === "tool_use");
return jsonResponse(extractPlansResponseSchema, toolUse.input);Stripe Connect destination charge with platform fee
src/app/api/invoices/[id]/pay/route.ts
BidFlow is a marketplace — the contractor gets paid, the platform takes a cut. This route validates the contractor's Stripe onboarding state, calculates a percentage fee, then creates a Checkout session with transfer_data.destination (routes funds to the connected account) and application_fee_amount (the platform cut).
const contractor = proposal.contractors;
if (!contractor.stripe_account_id || !contractor.stripe_onboarding_complete) {
return NextResponse.json({ error: "Contractor has not set up payments" }, { status: 400 });
}
const amountCents = toCents(Number(invoice.amount));
const feeCents = calculateFeeCents(amountCents, PLATFORM_FEE_PERCENT);
const paymentIntentData: Record<string, unknown> = {
transfer_data: { destination: contractor.stripe_account_id },
};
if (feeCents > 0) {
paymentIntentData.application_fee_amount = feeCents;
}
const session = await stripe.checkout.sessions.create({
mode: "payment",
line_items: [/* ... */],
payment_intent_data: paymentIntentData,
});How I Built This With Claude
I built BidFlow with Claude Code as my IDE — every AI feature, every Supabase migration, every Stripe flow was drafted, debugged, and shipped through Claude conversations. The trick was learning to direct Claude like a senior directs a team: give it the schema first, the contract second, the prompt third. For the AI estimate engine I leaned on Anthropic's tool-use API to force structured outputs — instead of parsing free-text JSON, the model fills a Zod-validated schema. That moved estimate parsing from 'sometimes works' to production-reliable. The six Claude integration points took roughly five days each to ship end-to-end, including UI, error handling, rate limiting, and Sentry instrumentation.
Screenshots
Screenshots coming soon