---
url: https://saas.dev/docs/definition
md: https://saas.dev/docs/definition.md
reads: [Startup, Noun, Offer]
generates: all 34 outputs
next: https://saas.dev/docs/interfaces.md
---

# The definition

> Four files define Leaseclock: a Startup, two Nouns and an Offer. Every key line carries an anchor and counts the outputs it generates.

## The sample

```mdx title="leaseclock.com/Startup/leaseclock.com.mdx"
---
$type: Startup
$id: leaseclock.com
name: Leaseclock
description: Tracks lease renewals for small property managers.
pitch: No renewal slips past its date.
icp: small property managers
brand: Leaseclock
---
```

```mdx title="leaseclock.com/Noun/Unit.mdx"
---
$type: Noun
$id: leaseclock.com/nouns/Unit
name: Unit
fields:
  label: { type: text, required: true }
  building: { type: text }
---
```

```mdx title="leaseclock.com/Noun/Lease.mdx"
---
$type: Noun
$id: leaseclock.com/nouns/Lease
name: Lease
fields:
  unit: { type: link, target: Unit, required: true }
  renter: { type: email, required: true }
  endsOn: { type: date, required: true, indexed: true }
  status: { type: choice, options: [{ key: active }, { key: ended }] }
verbs:
  renew:
    does: Move the end date forward
    input: { endsOn: { says: the new end date, type: date, required: true } }
    from: [active]
    fn: { kind: code, body: replace }
  end: { does: End the lease, from: [active], to: ended }
---
```

```mdx title="leaseclock.com/Offer/pro.mdx"
---
$type: Offer
$id: leaseclock.com/offers/pro
name: Leaseclock Pro
price: { amount: 29, currency: USD, unit: month }
limits: { Unit: 200 }
personas: Property manager, Leasing coordinator
status: Live
---
```

Each key line is an anchor, such as `#definition--Noun.Lease.name`.

Send the files as they are. Try first; then send the same call without `Prefer` to commit.

```bash title="Terminal"
curl -X POST https://api.sb/leaseclock.com/nouns -H 'Prefer: mode=try' \
  -H "authorization: Bearer $SB_TOKEN" \
  -F file=@Noun/Unit.mdx -F file=@Noun/Lease.mdx
curl -X POST https://api.sb/leaseclock.com/offers -H 'Prefer: mode=try' \
  -H "authorization: Bearer $SB_TOKEN" -F file=@Offer/pro.mdx
```

## Fields

A field's `type` sets its filter and schemas on every [Interface](https://startupbuilder.dev/business/nouns/interface).

| Type | In Leaseclock | Filter | JSON Schema · Zod |
| --- | --- | --- | --- |
| `text` | `Unit.label`, `Unit.building` | exact, in, prefix | `string` · `z.string()` |
| `email` | `Lease.renter` | exact, in | `format: email` · `z.email()` |
| `date` | `Lease.endsOn` | exact and range: `endsOn[lte]=2026-11-30` | `format: date` · `z.iso.date()` |
| `link` | `Lease.unit` | exact, in | `string`, `link: Unit` · `z.string()` |
| `choice` | `Lease.status` | is, is not, in, not in, by option key: `status=active` | `enum` · `z.enum(["active", "ended"])` |
| `money` | `Lease.rent`, from [Change one line](https://saas.dev/docs/how-it-works.md#live) | exact and range | `number` · `z.number()` |

Money is major units plus a currency: the [Offer](https://startupbuilder.dev/business/nouns/offer)'s `amount: 29` is $29.

## Verbs

`does:` becomes the method's doc line, and `says:` becomes the input's. Your agent reads both, and the Renew dialog shows both.

```ts title="$.d.ts"
type LeaseRenewInput = { /** the new end date */ endsOn: string }
/** Move the end date forward · active · modes try|do */
```

| Verb | You write | It generates |
| --- | --- | --- |
| renew | `input: { endsOn: … }` | `LeaseRenewInput`, the Renew dialog's form, and the body of `POST /leases/4b/renew` |
| renew | `from: [active]` | `active` in the doc line; Renew disabled on an ended Lease, with the reason; [`422 Refused`](https://startupbuilder.dev/code/api/errors/refused) on any other call |
| renew | `fn: { kind: code, body: replace }` | The [Function](https://startupbuilder.dev/business/nouns/function) `renew`, run on commit: `replace` writes the input over the Lease, so `endsOn` moves. An override's body lives in the Function it names, and its URL, preview, undo, button and agent call stay generated |
| end | `to: ended` | End sets `status` to `ended`, and an End button on each Lease |

## Names

Name it once. The name is the address.

| You write | It becomes |
| --- | --- |
| `name: Lease` | the Collection `leases`; `/leaseclock.com/leases`; `$.Lease`, with 8 methods; `show /leaseclock.com/leases` on the CLI; the Admin's "Leases" list |
| `does: Move the end date forward` | the doc line your agent reads on `$.Lease.renew`, and the Renew button's tooltip |
| `says: the new end date` | the Renew dialog's one field, and the Docs page's input label |

A name outside this grammar is refused with [`400 Invalid input`](https://startupbuilder.dev/code/api/errors/invalid-input), naming the fix.

| You write | The refusal |
| --- | --- |
| `name: lease` | `name is a Noun's name: PascalCase, letters and digits, at most 64 (got "lease")` |
| `EndsOn: { type: date }` | `field "EndsOn": a field key is camelCase, starting with a lower-case letter` |
| `Renew: { … }` | `verb Renew: a Verb is named by its action form: camelCase` |

Read next: [One Lease, every Interface](https://saas.dev/docs/interfaces.md)