Shazil Khan

Pricing Sheets

My own SaaS. Instead of a PDF quote, the client gets a live proposal: they pick a package, switch add-ons on and off, watch the total update, and sign on the same page.

Role
Founder and sole developer
Timeline
2026
Stack
Next.js, FastAPI, PostgreSQL with row-level security, a Yjs collaboration service, Hetzner
Live
pricingsheets.com ↗
Pricing Sheets home page
469API tests passing
51proposal templates
2pricing engines, Python and TypeScript, proven identical
1page to read, configure and sign

The situation

Agencies and IT providers still send quotes as PDFs. The buyer can't try a different package or drop an add-on, so every change is another email and another PDF.

I built Pricing Sheets to compete with tools like Smart Pricing Table and PandaDoc: a proposal the buyer can actually use.

What I built

  • Multi-page proposals and an interactive buyer view: packages, add-ons and a total that updates as they choose.
  • Multi-currency, conditional pages that show only when they apply, and a reusable clause library.
  • Multi-signer e-signature with a hash-chained audit trail and a completion certificate.
  • PDF export and per-page analytics, so the seller sees what the buyer actually read.
  • A public REST API with API keys, HMAC-signed webhooks, and Slack and Zapier hooks.
  • Website-to-brand onboarding: paste a website and the proposal picks up its logo and colours.
  • One pricing engine written twice, in Python for the server and TypeScript for the browser, kept identical by shared test files.
  • Real-time collaborative editing through a Yjs service, with row-level security enforced in PostgreSQL.
  • AI drafting and pay-on-accept are built and being switched on.
Pricing Sheets live demo page
The public demo: paste a website and the sample proposal re-skins itself with that brand.

What was hard

A sign-in that failed across two domains

Google sign-in returned a configuration error. It started on the marketing domain and came back to the app domain, so the session cookie never matched. I moved the whole sign-in onto the app domain.

A demo that broke after one use

The public demo proposal could be accepted, and once it was, it stayed accepted for every visitor after. I made it non-consumable so every visitor gets a fresh one.

A website fetcher that could be pointed inward

The brand onboarding fetches any website a user pastes. I closed a DNS-rebinding gap so it can never be tricked into reaching internal addresses.