One Lease, every Interface

One grammar reaches the Lease on the API, SDK, CLI, MCP and Docs. Every Verb returns one Action, and every refusal names its fix.

Reads
Noun.Lease
Generates
16 outputs
Open

The Lease

Leaseclock's Lease file declares four fields and two Verbs. Every Interface reaches them with one grammar.

OperationAPISDK ($)CLIMCP
List and findGET …/leases?status=active&sort=endsOn$.Lease.list() · $.Lease.find({ status: 'active' })show /<your-domain>/leasesdo runs the $ line
RetrieveGET …/leases/4b$.Lease.get('4b')show /<your-domain>/leases/4bfetch, or do
CreatePOST …/leases$.Lease.create({ unit, renter, endsOn })do Lease.createdo
UpdatePATCH …/leases/4b$.Lease.update('4b', { endsOn })do Lease.update 4bdo
ReplacePUT …/leases/4b———
Delete, kept as a VersionDELETE …/leases/4b———
RevertGET and POST …/leases/4b/revert$.Lease.revert('4b', { to })revert Lease 4bdo
renewPOST …/leases/4b/renew$.Lease.renew('4b', { endsOn })do Lease.renew 4bdo
endPOST …/leases/4b/end$.Lease.end('4b')do Lease.end 4bdo
In all18 operations: Lease 10, Unit 814 methods: Lease 8, Unit 6

Two rows are yours, renew and end; every Noun gets the other seven. … stands for https://api.sb/<your-domain>, and the CLI reads your Startup from SB_STARTUP, which login exports.

Leaseclock's Docs render each operation as a row like the two below, generated from the same file. The request changes with the Interface; the answer does not.

Lease.find

Find the Leases that match a filter, sorted by any indexed field.

Input
A filter on any field, and sort on endsOn
Output
The matching Leases
Refuses
401 Unauthorized
GET /leaseclock.com/leases
curl -G "https://api.sb/leaseclock.com/leases" -H "authorization: Bearer $SB_TOKEN" \
  --data-urlencode status=active --data-urlencode sort=endsOn \
  --data-urlencode 'endsOn[lte]=2026-11-30'
200 · application/json
{
  "api": "https://api.sb/leaseclock.com",
  "leases": [
    {
      "$id": "https://api.sb/leaseclock.com/leases/4b",
      "$type": "Lease",
      "unit": "4b",
      "renter": "maya.ortiz@example.com",
      "endsOn": "2026-11-15",
      "status": "active"
    }
  ]
}

Lease.renew

Move the end date forward. Runs from active, in try or do.

Input
endsOn, a date, required: the new end date
Output
One Action
Refuses
422 Refused · 401 Unauthorized
POST /leaseclock.com/leases/4b/renew
curl -X POST "https://api.sb/leaseclock.com/leases/4b/renew" -H 'Prefer: mode=try' \
  -H "authorization: Bearer $SB_TOKEN" -H 'content-type: application/json' \
  -d '{"endsOn":"2027-11-15"}'
200 · application/json
{
  "name": "you renew Lease 4B",
  "verb": "renew",
  "object": "https://api.sb/leaseclock.com/leases/4b",
  "by": "owner@leaseclock.com",
  "in": "leaseclock.com",
  "mode": "try",
  "status": "Done",
  "input": { "endsOn": "2027-11-15" },
  "event": "Lease.renewed",
  "closes": [],
  "stored": false,
  "events": []
}

The Interfaces

Five Interfaces read the one definition. The hub's Reference documents each once; below is what your files add.

API

Every Noun becomes a Collection at https://api.sb/<your-domain>/<collection>, described by an OpenAPI document generated from your files. An indexed: true field adds its sort and its range filter, ?sort=endsOn&endsOn[lte]=2026-11-30. API reference ↗

SDK

$ is typed from the definition: $.Lease with 8 methods and $.Unit with 6. What you write as says: becomes the doc comment your editor and your agent read. TypeScript, Zod and JSON Schema come from the same compile, published under your name. SDK reference ↗

CLI

show reads any record by its path, and do runs any Verb. A do previews in try and commits with --do. CLI reference ↗

MCP

Three tools, search, fetch and do, never one tool per Verb. do runs the same $ code, in try unless it passes { mode: 'do' }, and a script commits all or nothing. Your Customers' agents get the same three tools on your domain. MCP reference ↗

Docs

A page per Noun and per Verb, built from the rows above, with a Markdown twin of every page for agents. Every example runs on every Interface at build. Interface reference ↗

What ran

Every Verb returns one Action, on every Interface:

Terminal
curl -s 'https://api.sb/$.d.ts' | grep -A7 '^interface Action'
# interface Action {
#   $id?: string; id?: string; name: string; verb: string; object: string; by: string; in: string
#   mode: Mode; status: 'Pending' | 'Done' | 'Refused' | 'Failed' | 'Canceled'; reason?: string; says?: string
#   input: { [k: string]: unknown }; event: string; closes: string[]; stored: boolean
#   events: { n: number; name: string; at: string; subject: string; data?: unknown }[]
#   /** a committed Action's undo, as its Startup's Actions list names it ($.<Noun>.revert(<its $id>) runs it) */
#   revert?: string
# }

Three sentences say what ran, and each one reads a field:

The answerIt says
A GET on a Verb's URL, which returns a preview and no ActionNothing ran.
"stored": falseRan once. Stored nothing.
revert presentDone. Revert with …/revert.

A Verb call outside try carries an Idempotency-Key, and a replay with the same key returns the same Action. The App says the same three sentences: the Renew dialog's preview reads "Nothing ran.", a commit shows "Done." with Undo, and on an ended Lease both buttons are disabled with the reason from from.

Refusals

Each condition has one code on every Interface. Each code links its entry in the hub's refusal catalog, which names the fix.

CallCodeMessage, first clause
Any write with no credential401 Unauthorized"sign in, or send a key"
POST …/leases/2a/renew, an ended Lease422 Refused"renew runs from active; this Lease is ended"
The 201st POST …/units on Leaseclock Pro403 Limit exceeded"Leaseclock Pro holds up to 200 units"
A Noun file with properties:400 Invalid input"properties" is not part of a definition

Access

Sign-in

npx startup-builder login opens your browser, then exports SB_TOKEN and SB_STARTUP. The API, SDK, CLI and MCP take the same token, and your agent acts at your Access level: owner, admin, member or viewer. The API enforces it once; a screen only reflects it. Your Customers sign in on your domain (Customer sign-in).

Environments

Every Startup runs in Live, Test and Dev. Test and Dev are full copies whose books never post, reached by adding ~test or ~dev to your domain: https://api.sb/<your-domain>~test/leases. An Access level is set in Live and applies in every environment.

Export

One export call returns your records as JSON, CSV or Parquet, scoped to a Tenant when a Customer asks for theirs. You keep your four files, your records and your Stripe account.

Contents