All Work

Full-Stack SaaS Platform

PermitFalcon

A B2B platform that turns municipal building permit data into sales opportunities for contractors, tradespeople, and building material suppliers.

  • Next.js
  • React
  • TypeScript
  • Supabase
  • PostgreSQL
  • Stripe API
  • Cloudflare Workers
  • Turborepo
  • Resend
  • Sentry
  • Zod
  • Vitest
  • GitHub Actions
PermitFalcon permit list with one permit's details open in the side panel

Overview

A newly filed building permit is an early signal that a property owner is about to start a project, which makes it valuable to anyone who wants to bid on the work. The data is public but hard to use: every county publishes it through its own portal, in its own format, on its own schedule.

PermitFalcon collects those filings into a single catalog. Teams search and filter permits, open any record to see the property and the people and companies attached to it, and organize what they find with shared lists, color-coded tags, and notes.

Automated Ingestion

Permit data reaches the catalog without anyone touching it. A Cloudflare Worker wakes on a schedule, asks the database which sources are due, and starts a durable Workflow for each one. Only the first step knows anything about a particular county. Once the file exists, every source follows the same path through storage, deduplication, parsing, and import.

  • Connectors handle the formats counties actually publish in: session-based report exports, permitting portal searches, and public GIS feature layers, merged into one file per run.
  • Each file is hashed before it is stored, so an export that has not changed is recognized and skipped.
  • Imports read the stored file in byte ranges and upsert on stable identity keys, so a run can resume where it stopped and importing the same data twice never creates duplicates.
  • Scheduling lives in Postgres, not in the Worker. Each source carries its own interval, sources are claimed with a lease, and a failed fetch is retried on a later tick.
  • A daily check opens each source's live page and verifies that the form controls its connector depends on are still there, sending an alert when they are not.
Staff automation page showing ingestion health and recent fetch activity for each county source

The Permit Workspace

The catalog is too large to send to the browser, so the list sorts, filters, and pages in Postgres and the browser renders one page at a time. Sort order, filters, and the open permit all live in the URL, which keeps any view shareable and stable across a refresh.

  • Full-text search that finds a permit by its address, owner, or contractor as well as its own text, backed by a search index that database triggers keep current.
  • County-specific permit types and statuses mapped onto one standard vocabulary, so a single filter works across every source.
  • Shared lists, tags, and notes for each organization, with bulk actions across selected permits.
A saved list of permits with tags applied and the filter panel open

Security and Data Isolation

Access control is enforced by the database. Every member-facing query runs under the signed-in user's own session with Postgres row level security, so an organization can only read and write its own lists, tags, and notes, even if application code gets a query wrong.

  • The privileged service-role client is confined to imports and background jobs, in modules marked server-only so they cannot be bundled into client code.
  • The ingestion Worker has no public URL. It runs only from its schedule, so the credentials it holds are not reachable from the internet.
  • Server Actions check authorization themselves instead of relying on the page that renders them.
  • Confidential contacts are left out of the search index, so a search cannot reveal a name that the permit page withholds.

Engineering Practices

The project is a Turborepo monorepo: a marketing site, the application, the ingestion Worker, and shared packages for database access, email, and the import pipeline.

  • Schema changes ship as versioned SQL migrations, with TypeScript types generated from the database.
  • The import and ingestion packages are covered by Vitest unit tests.
  • GitHub Actions runs format, lint, and type checks plus a Worker bundle check on every pull request.
  • Transactional email is built with React Email and sent through Resend.