The first paying customer, and what we could not see
The first paying subscription ran its trial and cancelled without a single successful billable query. This is the writeup, kept in the repository as POSTMORTEM.md and rendered here from that same file. It covers what the product could not see at the time, what was built because of it, and what is still not fixed. The instrument whose absence made this invisible is the usage ledger behind the numbers we now publish, and the reasoning behind the product lives in architecture.
UrbanKit Studio's first paying subscription was created, ran its trial, and cancelled at the end of it. In between, the customer made no successful billable query. Not a slow month: none.
This page is the postmortem. It is public for the same reason the [benchmarks](/benchmarks) are public: the interesting part of a system is what it cannot tell you about itself, and that is usually the part nobody writes down. No customer is identified here. What matters is not who they were, it is that the product could not answer a simple question about them at the time it most needed to.
What happened
The account subscribed, entered a trial, and let the trial expire. The cancellation was scheduled rather than executed by anyone, so it churned by the clock. A win-back email went out once and got no reply, which is a normal outcome and not evidence of anything.
The part that matters is what sat behind it. The customer wanted owner information for parcels in a county where the underlying county endpoint does not publish owner fields at all. The product accepted the subscription anyway, because nothing in the purchase path knew what any given county could actually answer. There was no wrong number on a screen; there was a silence, and the silence was indistinguishable from a county with nothing to say.
What we could not see
At the moment the account went quiet, here is what the system could and could not tell us.
Whether they had ever received a useful answer: no. There was no durable record of API usage. Rate limiting ran on a rolling counter in Upstash, which answers "how many requests in the last window" and then forgets. Nothing wrote a row per request, so "has this customer ever gotten a successful lookup" had no query that could answer it. The number that would have raised the alarm on day two did not exist until after they were gone.
What they tried and failed to get: no. The account was created before the telemetry work landed, so there were no journey rows either. Their intent had to be inferred from a support conversation rather than read from data.
Whether the account was easy to confirm as revenue: barely. The subscriptions table marks a free tier row active the same way it marks a paying row active, so the two are hard to tell apart at a glance, and during this investigation they briefly were. The payment itself was never in doubt; the data trail for confirming it was. Counting subscriptions was never the same thing as counting revenue.
The honest summary is that the product could tell you a customer existed and could not tell you whether the product had ever worked for them.
What changed because of it
Four things, in the order they mattered.
A capability layer, so a county's limits are visible before purchase. Every county page now states which fields that county can answer, per field, derived from the same capability data the API enforces rather than written by hand. The API returns a typed error naming the restriction instead of an empty result, and the MCP tools carry the same wording so an agent gets the reason rather than a blank. A caller who wants owner data in a county that cannot serve it is told so, in the place where they would otherwise pay.
Billing that refunds what it cannot deliver. When a lookup cannot return the field a caller paid for, the charge does not stand. This is the narrow case that started the whole investigation, and it is now handled in the metering path rather than by apology.
A durable usage ledger. Every billable request now writes a row: endpoint, status, reason, billing outcome, and whether it was keyed or anonymous. The question that had no answer has a query now. It is not analytics for its own sake, it is the specific instrument whose absence made this churn invisible.
Correctness that is measured rather than asserted. The [benchmarks page](/benchmarks) exists because "the lookups work" was a claim nobody had tested against independent records. It now reports a pooled accuracy figure across several counties, including the counties where the answer is zero and why.
What this still does not fix
A postmortem that ends with a list of fixes is usually hiding something, so here is what is still true.
One customer is not a pattern. Everything above is a response to a single account. It is entirely possible the same fixes would not have kept them, and it would be dishonest to claim the churn was solved rather than understood.
The ledger started empty. It records everything from the day it shipped and nothing from before it, so the history that would have explained this account does not exist retroactively.
Nobody is watching the ledger yet. Writing rows is not the same as noticing what they say. There is no alert that fires when a paying account records a week of failures, which is the obvious next thing and is not built.
Some counties still cannot answer, and that is not a bug we can fix. Where a county publishes no owner field, no amount of engineering here creates one. The work was to stop selling the expectation, not to invent the data.
Why this page exists
The instinct with a first churn is to file it as bad luck and move on. The more useful reading is that it was a measurement failure before it was a product failure: the product's own limits were invisible to the person buying it, and the customer's experience was invisible to the people building it. Both are fixable. The first is fixed, and you can inspect it from this site rather than take it on faith: a county's limits are stated before anyone pays. The second is not, not fully. The ledger exists and the rows are there, but as the section above says, nobody is watching them yet, and a record nobody reads would not have saved this account either.
The build checks this page against the repository's POSTMORTEM.md on every deploy, and fails rather than publish a difference.