Build your own

The seam is not gated by anything

It is MIT, it lives in core, and it is the code that runs in your Worker. Restricting it would make you can build your own client against your own Worker untrue.

Register a key you generated yourself

pithy dashboard connect --public-key <file>

No dashboard involved — connecting without a browser is the whole procedure — and every route works exactly the same way.

Rotating without one is your own call to make, literally

Register the successor with a signed POST /control-plane/keys, using the key you are replacing — exactly the append-prove-expire order a managed connection follows.

connect --public-key registers a first key and refuses a successor while one is live, naming that call.

Two reasons, and both matter. The CLI has no private half to sign with. And doing it for you would take the registration out of your own audit trail — the append is a signed management call, and a signed management call is a row in your trail.

The contract is a module you can import

@pithy-sh/cli/src/dashboard/contract carries the six calls, the six response shapes, and the hosted origin.

It reaches for no timer, no fetch, and nothing from node — so it compiles in a Worker as readily as in a build script.

Implement the client interface against it and let the compiler tell you what you owe, rather than copying the field sets into a test that can drift.

What your client has to do per call

Mint a token that is — and every check your Worker runs on it is worth reading beside this list:

SignedEd25519, alg exactly EdDSA
Audience-boundThe connection id, and this Worker’s environment
Issuer-boundThe issuer the connection was registered against
Short-livedWithin the configured maximum. Sixty seconds is the shape
Single-scopeThe one scope this call needs, never the whole grant
Single-useA fresh token id each time. It is spent on arrival
Body-boundCarrying the SHA-256 digest of the raw body

The body digest is not optional ceremony. A signature over claims that do not bind the body would let an attacker swap the payload under a valid signature.

Discover, do not configure

Compose your navigation and your calls from GET /control-plane/manifest.

Knowing a capability is installed is not enough to call it. Every capability’s mount point is configurable, and each builds its declaration from its resolved config — so the manifest names where things actually are. A client that hardcoded /payments would 404 against exactly the adopters who customized anything.

Each declared route names the scope it needs. Held against the granted scopes, that is what lets you gray out an action that is not granted here instead of offering a button that answers 403.

A capability with no management surface reports an empty list rather than being absent. Composed, but nothing to administer and not installed are different facts, and a client that cannot tell them apart renders the wrong thing for both.

The declaration is checked, not trusted. A hand-maintained list beside generated behavior is a list that rots, and a drifted manifest is worse than none — a client believes it, calls a path nothing serves, and the adopter sees a management client broken for reasons inside somebody else’s package. Every declared route is compared against the router that actually mounted, in both directions, by each capability’s own contract test.

Do not model a scope you do not need

Ping requires a verified caller and no scope at all. It has to work for a connection granted nothing, because it is how a new key is proven before the old one is expired.

ESC