Blog

API keys, the SDK, and MCP from one spec

Everything a customer can do by clicking in the console, they can do from a script. An API key is a principal in the same permission model as a person, the SDK is generated from the API's own description, and one specification becomes the console's client, the developer's SDK and an agent's toolbox. The API was built for a machine, and the humans got the same surface for free.

Wrapping one web app for desktop and mobile

The console now runs as a native desktop app and I have not shipped it. Because the console is a pure API client, a new surface costs a shell rather than a rewrite, which is what made the experiment worth running and the negative result cheap. The surface that decides whether the product works is the phone, because everything the platform makes waits on an approval.

Invitations need notifications

An owner invites a teammate to their organization. Three sentences of product, and a teammate who is not a user yet cannot be reached inside the app. Reaching a stranger by email needs a notifications layer with the provider behind an interface, which took longer to build than the feature that required it.

Connecting a custom domain

This site is the case. jessejamesrichard.com is registered elsewhere, built in the console, hosted on Giant Context, and served over HTTPS on its own name. Getting there runs through four systems and I own one of them, across a six-state machine where four of the states are the word waiting.

Three test suites that never overlap

A platform this size, run by one person, keeps working through three test suites that never overlap. Unit checks the logic, integration checks the whole flow against a real database, and a generated suite checks that the right people, and only the right people, can reach each route. Each checks exactly one thing, and never the thing another one checks.

Multitenant serving and cache invalidation

Every customer domain resolves to the same load balancer IP, and a router reads the Host header to decide which site it is. That lookup is the tenant boundary. Pages are rendered before they are asked for and served from a store, so the only way one can be wrong is the site changing after it was stored, which is what the purge on save is for.

Generating a permission test for every route

Every route declares which permission guards it, and that declaration is a description while the enforcement is a behaviour. Nothing in a type system makes them agree, so a route can say guarded while its handler quietly stopped checking. The fix is a generator that reads those declarations and writes a test per route per role, in both directions, against a live API.

Names steer the code

Finding the name is how I work out whether I understand the thing I am about to build. A name draws a boundary before any code exists, then steers everything added under it afterwards, and the audience for a good one has doubled now that most of the code is written by tooling that takes names literally. Includes what the research actually says about camelCase, and the one seam in this codebase where the casing changes.

The localization I skipped

I skipped localization to prove the product first, and I cannot tell you what the retrofit cost because it never had a boundary. Here is how to do it on day one instead: two field types rather than one, a name that stays a string and a title that becomes jsonb keyed by locale, languages configured per project, and an explicit list of what may be translated that a test derives from the types.

The deploy pipeline

A few times a day I push code, and twenty minutes later it is running live and confirmed healthy, untouched by me in between. What a bad deploy costs on a multitenant platform with no second engineer, and the twenty-nine jobs that stand between a push and a customer's website.