# SDK

URL: /code/sdk · Id: `page:code/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](/business/nouns/startup). `$.Offer.subscribe(input)` is the same operation as `POST /offers/{offer}/subscribe`, with the same input, result, and checks.

## Calls
[Install the SDK](/code/sdk/guides/install) first: one package and one key.

Every [Noun](/business/nouns/noun) is a property of `$`, and every [Verb](/business/nouns/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.

```ts
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/`](/code/file-conventions/generated#types-in-the-editor), rewritten on every save.

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

```ts
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](/business/nouns/mode) for one call. They are the same three Modes the API takes in `?mode=`.

```ts
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](/business/nouns/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:

```ts
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](/business/nouns/event) by Noun and past tense. The handler receives the same payload a webhook delivers, typed:

```ts
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](/business/nouns/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](/business/nouns/role) may run. A missing [Permission](/business/nouns/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](/code/mcp).