Skip to main content
@ownsi/sdk is a thin layer over Eden Treaty, which infers the whole surface from the API’s exported App type. There is no code-generation step and nothing to keep in sync.
A route that changes shape becomes a type error at the call site, in the same commit.

Install

Inside this repo it is a workspace dependency and the types come from source. Outside it, the package is not published yet, because the API is still internal.

Create the client

api/ownsi.client.ts
Same origin, so the session cookie rides along with no extra configuration. Pass a fetch if you need to route the calls somewhere else — a test, a worker, a proxy.

Claim a domain

Three resources — a domain is a name, a claim is an episode, a verification is a process — and one sentence over them.
findOrCreate is idempotent on the name: asking twice returns the same domain. domain.claim() answers already_claimed while one is still open — use the one you have, its token is the one that will verify.
Render record.host, not record.name. Almost every panel appends the zone to what you type, and a copy button that hands over the fully qualified name is how people end up with domain_appended.
claim.record is singular because there is one record to write. It comes from the claim alone — so the write this record screen renders with no verification loaded — and it is null once the claim has ended, because there is nothing left to put in a panel.

Follow the verification

The claim knows which process is running against it, so you never carry the id yourself.
Exactly one of diagnosis and waitEstimate is set while a verification is still running. One is your reader’s problem and one is not, which is the split the product exists for. verification.attempts() is every read it has made, newest first — the evidence a proof rests on.

Every act, in one table

Every read answers with a handle: the fields the API sent, plus the acts reachable from them. ownsi.api is the Eden client underneath, for a route the package does not cover yet. list() answers with Claim; get() and create() answer with ClaimDetail, which adds coexistence. The list does not carry a field it did not fetch.

The two dates a proof states

Derived across the domain’s claims, never stored, so neither date can disagree with the claims it is read from. proofOf(claims) is the same function if you already hold the list.

Reading a zone

The public read streams, so it is a generator rather than a value.
Extract on the discriminant narrows any of the tagged unions in this API — zone steps on step, diagnoses on code, wait estimates on reason. ZoneDelegation and ZonePublishing are exported already narrowed.

Errors

Everything throws an OwnsiError, carrying the API’s own code and docsUrl untouched.
RETRYABLE is the set worth trying again. unreachable is in it, and it is the one code the API never sends: it means no answer arrived, or the answer was not ours. A request that never arrived and a request that failed are both our side of the line, so both read as unreachable rather than as something about somebody’s domain.

With TanStack Query

Keep the call in a hook rather than in a component, and let the error’s code drive the retry.
hooks/useVerificationState.ts
Stop polling on proved; keep polling while a waitEstimate says the wait is real. Forcing runs in a loop does not make DNS answer faster — the verification’s own secondsRemaining is the honest interval.

Authentication

Sessions are better-auth, not the SDK. It publishes its own typed client, and wrapping it would buy a second name for every method and nothing else.
api/auth.client.ts
inferAdditionalFields<Auth>() types the session off the server’s configured instance, the same way Treaty types the routes.