This week we shipped the first endpoints of an outgoing read API. The idea is simple: your revenue, your errors, your read-cost, and the cross-match (who and what spent each database read) shouldn't be trapped inside our dashboard. You can pull them into your own backend and render them inside your own product.
Building the access was the easy half. The half that took the real thinking was restraint.
Take the revenue endpoint. The obvious version returns everything — every charge, every customer, maximum granularity. We didn't ship that. /v1/revenue returns aggregates only: MRR, paying-customer counts, revenue split by rail (Stripe, Apple, Google), an optional daily series. No named customer. No individual charge row.
Two reasons. First, financial data is the highest-sensitivity surface we touch — the cost of getting an export wrong there isn't symmetric with the convenience of getting it right. Second, secret-key only: the browser never talks to this API, a publishable key is rejected outright. If data is leaving the building, it leaves from your server, scoped to your project, fail-closed by default.
The lesson we keep relearning: when you build the door that lets data out, the design work is in what you refuse, not what you allow. Access is a feature anyone can add. Restraint is the thing customers actually trust.
And there's a quieter point underneath it. The same logic that makes a platform safe to build on is what makes it safe to leave. If pulling your data out is easy, staying is a choice you keep making — not a cage you're stuck in. Locking data in feels like leverage. In our experience it's fragility wearing a costume.
If you're building anything multi-tenant: ship the export early, and spend most of your time on the subtractions.