Back to Blog

Pilot Billing: Turning Flight Logs into Invoices

July 2, 2026Jeff Conn
Building in PublicPilot BillingAviationCustom Software

I fly as a contract pilot. It's the best side of my working life — and the worst invoice I send all month. Billing an aircraft owner means reconstructing the month from flight logs: which days I flew, which days were standby or travel, what the daily rate was, how many per-diem days, plus a stack of fuel and hotel receipts. Every month, spreadsheet archaeology. Every month, something missed.

Pilot Billing is the app I built to make the invoice assemble itself.

The flights import themselves

The core trick: the airplane already knows where it went. A scheduled job pulls flight data from an aviation tracking API three times a day and files each leg under the right aircraft. Every leg stores the provider's ID, so overlapping sync windows can't create duplicates — a sync is always safe to run again. And legs are dated by the Eastern-time calendar day, not UTC, because a 9pm departure shouldn't bill as tomorrow.

Flights group into billable days — the unit pilots actually bill in. A multi-leg day reads as one coherent trip because the app re-orders the legs into a connected itinerary (each leg departing where the last one arrived) instead of trusting raw storage order. Days the tracker can't see — standby, travel, repositioning — get added manually and flow through the same pipeline.

A review workflow instead of a spreadsheet

Every day moves through a small state machine: imported → assigned → reviewed → ready → invoiced. Along the way I can override the rate for one day, adjust per diem, attach receipts with the expense, mark a day non-billable, or split a day between two owners who shared the airplane — each with their own rate and per-diem override. When new legs land on a day that's already been invoiced, the app flags it instead of silently changing history.

A month in the billing workspace — flight days, receipts, and the period's invoices. Customer names and totals redacted; the blueprint grid is just how it looks.

Invoices generate per owner, per month, with pilot services, per diem, and expense reimbursements as separate lines. Before anything goes out, the lines get normalized — a week of per-diem entries collapses to one clean grouped line — so the owner sees a professional invoice, not my internal bookkeeping. Payment is their choice: a card-payment checkout link, or good old check and ACH with manual tracking. Receipts are downloadable through tokenized links, so an owner's bookkeeper can pull the backup documentation without ever needing an account.

The Flight Deck look

The design theme is what I call Flight Deck: a vivid teal on a canvas with a faint blueprint grid, Inter for text and JetBrains Mono for numbers, uppercase micro-labels on data fields. It reads like an instrument panel without cosplaying as one. Every color, radius, and spacing value is a CSS custom property, so the whole skin is swappable from one palette file. In my own workspace it wears the name JMC Billing — the company that does the flying — but Pilot Billing is what it is.

The stack

React 18 with Vite on Vercel; Supabase for Postgres, auth, storage, and nine Deno edge functions that handle the flight sync, invoice emails, payment links, webhook confirmation, and tokenized receipt access; Stripe for card payments; Resend for branded invoice email from the company's billing subdomain; GitHub Actions as the cron that fires the daily syncs. The whole thing also runs in a local demo mode against browser storage with no backend at all, which made development and testing fast — no cloud required until real data shows up.

Two details I'm glad I did on day one: the schema is multi-tenant (more pilots and more aircraft are a config change, not a rewrite), and rapid edits autosave through an ordered write queue so fast typing can never overwrite newer data with older data.

Forty-eight commits over about three weeks. The June invoice was the first one that built itself; I mostly just read it and hit send.