Skip to content

SDK

The startup-builder package exports $, a typed client with a method for every Verb, the three Modes as prefixes, and Events as callbacks.

The startup-builder package exports $, a typed client for one Startup. $.Offer.subscribe(input) is the same operation as POST /offers/{offer}/subscribe, with the same input, result, and checks.

Calls

Install the SDK first: one package and one key.

Every Noun is a property of $, and every Verb on it is a function that takes one object. The examples follow leaseline.app, which tracks lease renewals for small property managers at $29 a month.

TypeScript
import { $ } from 'startup-builder'

const { subscription } = await $.Offer.subscribe({ offer: 'offer_1', quantity: 1 })

console.log(subscription.$id) // https://api.sb/leaseline.app/subscriptions/subscription_1

The types are generated from the same definitions as the routes. place, subscribe, and every other method accept only the fields the Verb declares, and the editor completes them. The Startup's own Nouns are there too: once leaseline.app declares Lease, $.Lease.list is typed from .sb/types/, rewritten on every save.

A list resolves to one page, an array with an after cursor:

TypeScript
const page = await $.Lease.list({ limit: 20 })
const next = page.after ? await $.Lease.list({ limit: 20, after: page.after }) : []

Modes

A prefix picks the Mode for one call. They are the same three Modes the API takes in ?mode=.

TypeScript
await $.Offer.subscribe({ offer: 'offer_1' })                     // the Verb default, do
await $.try.Offer.subscribe({ offer: 'offer_1' })                 // a preview: nothing is stored
const action = await $.send.Offer.subscribe({ offer: 'offer_1' }) // the pending Action, at once

$.try makes a write safe in tests and in an agent's planning step. $.send suits work that takes minutes or waits on a person, such as a renewal that waits for a tenant to sign.

Results and failures

await resolves to the result, or throws when the Action ends failed, refused, or canceled. The thrown value is the Action itself, and its error.type is the URL of the error's page. Code that branches on outcome reads .settled, which resolves to the Action in any final status and never throws:

TypeScript
const action = await $.Offer.subscribe({ offer: 'offer_1' }, { idempotencyKey: 'manager-4417' }).settled

if (action.status === 'done') console.log(action.result?.subscription.$id)
else console.log(action.error?.fix) // what to change before retrying

idempotencyKey makes a retry safe: a second call with the same key returns the first Action and runs nothing.

Events

$.on subscribes to Events by Noun and past tense. The handler receives the same payload a webhook delivers, typed:

TypeScript
const stop = $.on.Offer.subscribed((event) => {
  console.log(event.seq, event.data.subscription.$id)
})

stop() // unsubscribes

Inside the Startup, the same handler lives in a file and needs no server to host it. on/offer.subscribed.ts runs on every Offer.subscribed, as a Trigger with its own Action record.

Types per caller

The package's types cover every public operation. Each caller also gets its own: https://api.sb/leaseline.app/$.d.ts holds only the Verbs that caller's Role may run. A missing Permission then fails in the editor, before any call.

The MCP server hands agents that same file, and its do tool runs the same $. A program written against the SDK runs there unchanged.

An agent runs the same $ over MCP.