burneh

Multi-tenant platforms, and the dashboards behind them.

Software other people log into every day. Built multi-tenant, typed end to end, and instrumented so you can see it run.

app.acme.io
ws

Overview

MRR
$48.2k
▲ +4.1%
Active users
1,284
▲ +124
Churn
1.8%
▲ -0.3%
Net revenue
$41.9k
▲ +3.4%
revenue · 30dAcme
customerplanstatusmrr
NorthwindScaleactive$4,200
ContosoProactive$1,850
FabrikamPropast due$3,100
Wonka CoScaleactive$5,400
InitrodeFreeactive$0
activity
Northwind upgraded to Scale
2m ago
invoice #1042 paid · $4,200
14m ago
Fabrikam payment failed
1h ago
new member added to Contoso
3h ago
same console, per workspace · every widget is scoped to one tenant, enforced by the database

The same console, per workspace.

Switch the workspace and every widget changes. Each tenant sees only its own data, enforced by the database, not the UI. That is the whole product, on one screen.

The preview above is a still. The one behind this button is the real thing, running on seeded data, with row-level isolation you can try to break.

The platform under that screen.

The dashboard is the easy half. The half that keeps one customer out of another's data is this: a workspace pinned on every request, a typed API, and row-level security in the database, all instrumented so the numbers on that screen are real queries you can trust.

request+ sessionauthenticateuser + workspacetenant contextpinned on the conntyped APIrequest / responsePostgresrow-level securitydashboardlive metrics + auditinstrumented0102030405
01
Authenticate

Every request proves who it is and which workspace it belongs to before it reads a byte.

02
Tenant context

The workspace is pinned on the connection, so everything downstream is scoped to it automatically.

03
Typed API

Requests and responses are typed end to end, so a wrong shape fails loud, not silently in prod.

04
RLS in Postgres

The isolation rule lives in the database, so a forgotten filter still can't cross tenants.

05
Instrumented

Usage, metrics, and an audit trail feed the dashboard, so you see it run and can bill it.

One codebase. Every customer sealed off.

Every tenant runs on the same code and the same database, and still can't touch each other's rows. The isolation isn't a filter in every query someone has to remember, it's a policy the database enforces on every read and write. A forgotten filter, a crafted request, an injection: none of them reach across.

policy.sql
1-- one codebase, one deploy, isolation in the database
2CREATE POLICY tenant_isolation ON invoices
3 USING (tenant_id = current_setting('app.tenant'));
monorepo · one deployv128 · live
Acme
1,284 rows · sealed
Globex
342 rows · sealed
Initrode
96 rows · sealed
Umbrella
511 rows · sealed

tenant_id scoped · no query crosses the line

Typed, so a bad shape fails loud.

One schema is the contract for both the API and the client, generated into types and validated at the edge. A request with the wrong shape is rejected at the door with the exact field, in code review or a 422, instead of a null that corrupts a row and surfaces as a support ticket next week.

schema.ts
1// schema.ts — the shape both sides agree on
2export const CreateInvoice = z.object({
3 customerId: z.string().uuid(),
4 amount: z.number().int().positive(), // cents
5 currency: z.enum(["USD", "EUR"]),
6 dueInDays: z.number().int().max(90),
7});
8export type CreateInvoice = z.infer<typeof CreateInvoice>;
POST /v1/invoices
{ "amount": "4200", "currency": "usd" }
✕ 422 · amount: expected number, received string
✕ 422 · currency: expected "USD" | "EUR", received "usd"
nothing was written · the row is never half-created

A product is only as good as the parts nobody demos.

The tenancy that keeps customers apart, the types that catch a bad shape before a user does, the dashboard that shows you it's running. They're the least glamorous work and the first thing that decides whether the thing holds up. We build those first, then the features on top.

Tell us what your product has to hold.

Send the shape of the system and the outcome it has to keep. We come back with a scope, a plan, and where a demo proves it.