DocsPricingBlogChangelog
Examples use: Lease

Every code example, in your word.

Sign inStart building

API

Open

Leaseclock's API is at leaseclock.com/api. Every Noun has list, get, create and update operations, and every Verb has its own POST. Every response uses the same JSON shape, and every error links to the page that explains how to fix it.

In short

  • The base URL is https://leaseclock.com/api. Paths use each Noun's plural name: /api/leases, /api/units.
  • Requests and responses are JSON. A record is the same object in every response, from a list, a get, a write or a Verb.
  • Every request carries a key in the Authorization header. A key acts with the role and the workspace of the person who created it.
  • Every write is recorded as a version, returns an undo link and appears in the record's history.
  • The OpenAPI file is at https://leaseclock.com/openapi.json, and every operation has a page at leaseclock.com/docs, so your customers and their tools read the same reference you do.

Leaseclock over HTTP

Priya Shah, the leasing coordinator at Northgate Rentals, renews Dana Whitfield's lease on Unit 4B for another year. renew is a Verb in Lease.mdx, so it is a POST on the lease.

Terminal
curl https://leaseclock.com/api/leases/4b/renew \
  -H "Authorization: Bearer $LEASECLOCK_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "endsOn": "2027-11-30" }'
200 OK
{
  "$id": "https://leaseclock.com/api/leases/4b",
  "$type": "Lease",
  "unit": "4b",
  "renter": "dana.whitfield@example.com",
  "endsOn": "2027-11-30",
  "status": "active",
  "renewedBy": "priya@northgaterentals.com",
  "undo": "https://leaseclock.com/api/leases/4b/undo"
}

The SDK and the CLI send this same request and get this same record back. So do the app's Renew button and an agent calling do over MCP.

Operations

name: Lease gives Lease its list, get, create and update operations at /api/leases. Each Verb under verbs: adds a POST, and a GET on the same URL previews it.

OperationRequest
List leasesGET /api/leases
Get a leaseGET /api/leases/{id}
Create a leasePOST /api/leases
Update a leasePATCH /api/leases/{id}
Renew a leasePOST /api/leases/{id}/renew
End a leasePOST /api/leases/{id}/end
Preview a renewalGET /api/leases/{id}/renew
Undo the last writePOST /api/leases/{id}/undo
List every versionGET /api/leases/{id}/history

Unit has the same operations at /api/units, without Verbs of its own. Add a Noun or a Verb to the definition and push it, and its operations answer on the next request.

List records

A list returns the records the key can see, sorted by the first indexed field. Lease sorts by endsOn, so the leases ending soonest come first.

ParameterWhat it does
Any field, as status=active or unit=4bReturns records whose field has that value. A choice field accepts only its options.
endsOn.before, endsOn.afterReturns records before or after a date. Every indexed field takes both.
sortSorts by an indexed field, as sort=endsOn or sort=-endsOn for newest first
expandReturns a linked record in place of its id, as expand=unit
limitReturns fewer records a page. A page holds up to 200.
afterStarts the page after a record. next holds the full URL of the next page, and is null on the last one.
Terminal
curl -G https://leaseclock.com/api/leases \
  -H "Authorization: Bearer $LEASECLOCK_TOKEN" \
  -d status=active \
  -d endsOn.before=2027-01-01
200 OK
{
  "$id": "https://leaseclock.com/api/leases?status=active&endsOn.before=2027-01-01",
  "items": [
    {
      "$id": "https://leaseclock.com/api/leases/4b",
      "$type": "Lease",
      "unit": "4b",
      "renter": "dana.whitfield@example.com",
      "endsOn": "2026-11-30",
      "status": "active"
    },
    {
      "$id": "https://leaseclock.com/api/leases/2a",
      "$type": "Lease",
      "unit": "2a",
      "renter": "marcus.bell@example.com",
      "endsOn": "2026-12-15",
      "status": "active"
    }
  ],
  "next": null
}

Get a record

Terminal
curl "https://leaseclock.com/api/leases/4b?expand=unit" \
  -H "Authorization: Bearer $LEASECLOCK_TOKEN"
200 OK
{
  "$id": "https://leaseclock.com/api/leases/4b",
  "$type": "Lease",
  "unit": {
    "$id": "https://leaseclock.com/api/units/4b",
    "$type": "Unit",
    "label": "4B",
    "building": "Alder Court"
  },
  "renter": "dana.whitfield@example.com",
  "endsOn": "2027-11-30",
  "status": "active"
}

A record's id comes from its name or label, so Unit 4B is 4b. Ids are stable: a later change to the label keeps the id.

Create a record

Terminal
curl https://leaseclock.com/api/units \
  -H "Authorization: Bearer $LEASECLOCK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "label": "6E", "building": "Birch House" }'
201 Created
{
  "$id": "https://leaseclock.com/api/units/6e",
  "$type": "Unit",
  "label": "6E",
  "building": "Birch House",
  "undo": "https://leaseclock.com/api/units/6e/undo"
}

Required fields must be present, and every value must match its field's type. A missing label returns 422 invalid_field naming label.

Update a record

PATCH changes only the fields it sends.

Terminal
curl -X PATCH https://leaseclock.com/api/leases/2a \
  -H "Authorization: Bearer $LEASECLOCK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "renter": "marcus.j.bell@example.com" }'

A field a Verb moves cannot be patched. Lease's status changes only through end, so a PATCH that sends status returns 422 field_set_by_verb, with POST /api/leases/2a/end as the fix.

Run a Verb

A Verb is a POST on the record, with the Verb's input as the body. renew declares one input, endsOn, and runs only from: [active]. end takes no input.

Terminal
curl -X POST https://leaseclock.com/api/leases/7c/end \
  -H "Authorization: Bearer $LEASECLOCK_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"

Every Verb POST requires an Idempotency-Key header. A retry with the same key returns the first response and runs nothing a second time, so a dropped connection never renews a lease twice.

Preview a Verb

A GET on a Verb's URL returns the record as the Verb would leave it, and changes nothing. The input goes in the query string.

Terminal
curl -G https://leaseclock.com/api/leases/4b/renew \
  -H "Authorization: Bearer $LEASECLOCK_TOKEN" \
  -d endsOn=2027-11-30
200 OK
{
  "$id": "https://leaseclock.com/api/leases/4b",
  "$type": "Lease",
  "unit": "4b",
  "renter": "dana.whitfield@example.com",
  "endsOn": "2027-11-30",
  "status": "active",
  "preview": true
}

A preview checks everything the POST checks: the key's role, the lease's state and the input. A preview that would fail returns the same error the POST would.

Undo and history

Every write returns an undo link. A POST to it puts the record back as it was before that write, and records the undo as a version of its own.

history lists every version of a record, newest first, with who made the change and from where.

Terminal
curl -X POST https://leaseclock.com/api/leases/4b/undo \
  -H "Authorization: Bearer $LEASECLOCK_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"
GET /api/leases/4b/history
{
  "$id": "https://leaseclock.com/api/leases/4b/history",
  "items": [
    {
      "version": 2,
      "at": "2026-10-21T14:02:11Z",
      "by": "priya@northgaterentals.com",
      "via": "API",
      "verb": "renew",
      "changes": { "endsOn": ["2026-11-30", "2027-11-30"] }
    },
    {
      "version": 1,
      "at": "2026-08-21T09:40:03Z",
      "by": "priya@northgaterentals.com",
      "via": "App",
      "verb": "create"
    }
  ],
  "next": null
}

Keys

Your customers create keys in the Leaseclock app, under Settings → API keys. The key is shown once. Scripts read it from the environment as LEASECLOCK_TOKEN, and it can be revoked on the same screen at any time.

Terminal
export LEASECLOCK_TOKEN=…   # from Settings → API keys
curl https://leaseclock.com/api/leases -H "Authorization: Bearer $LEASECLOCK_TOKEN"

Every key is an id.org.ai token. It acts with the role of the person who created it:

RoleCan call
ViewerList, get, preview and history
MemberEverything a viewer can, plus create, update, every Verb and undo
AdminEverything a member can, plus creating and revoking keys for the workspace
OwnerEverything an admin can, plus the workspace's plan and billing

A key reaches one workspace. A key from Northgate Rentals lists Northgate's leases and nobody else's, whatever the path or filter asks for. Your own team signs in as Leaseclock's admins and names a customer with the Workspace header, as in Workspace: northgate-rentals.

Test and Live

Every workspace has a Test copy with its own records, and checkout in Stripe's test mode. The key decides which copy a call reaches: a Test key reaches Test, and a Live key reaches Live, on the API, the SDK, the CLI and MCP alike. Switch the app to Test, then create the key under Settings → API keys.

The web surfaces of the Test copy answer at test.leaseclock.com, and at my-product.test.saas.dev before a product has a domain of its own. Test and Live share the definition and every operation, so a script that passes in Test runs unchanged in Live with a Live key.

Terminal
export LEASECLOCK_TOKEN=…   # a Test key from Settings → API keys
curl https://leaseclock.com/api/leases -H "Authorization: Bearer $LEASECLOCK_TOKEN"

Limits

LimitValueWhen it is reached
Requests100 a second per key in Live, 25 in Test429 rate_limited, with Retry-After in seconds
Records a page200A limit over 200 returns 200
Request body1 MB413 body_too_large
The Offer's limitsLeaseclock Pro: 200 Units402 limit_reached, with the link to upgrade

Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset, so a script can slow down before it is stopped.

The Offer's limits come from Offer/pro.mdx. limits: { Unit: 200 } is the line the API checks before the 201st Unit is created, and the same line sets the pricing page and the upgrade prompt in the app.

Responses

Every response is one of three shapes.

A record has its $id, its $type and its fields. A write adds undo, and a Verb adds who ran it.

A list has its $id, the records in items, and next.

An error has a code, the field it refers to, a message, the fix and the page that explains it:

409 Conflict
{
  "error": {
    "code": "wrong_state",
    "message": "Lease 5a is ended, and renew runs only on an active lease.",
    "fix": "Renew an active lease, or create a new lease for Unit 5A.",
    "docs": "https://leaseclock.com/docs/errors#wrong_state"
  }
}

Field values in JSON follow their type in the definition:

Field typeIn JSON
text, emailA string
dateA string, as 2027-11-30
choiceThe option's key, as "active"
linkThe linked record's id, as "4b", or the whole record with expand
moneyA number in the currency's units, as 1850

Errors

StatusCodeWhen
400invalid_jsonThe body is not valid JSON
400idempotency_key_requiredA Verb POST has no Idempotency-Key
401invalid_keyThe key is missing, revoked or mistyped
402limit_reachedThe write would pass a limit of the customer's Offer
403not_allowedThe key's role cannot make this call
404not_foundNo record with that id in the key's workspace
409wrong_stateThe Verb does not run from the record's current state
409idempotency_key_reusedThe Idempotency-Key was used for a different request
413body_too_largeThe body is over 1 MB
422invalid_fieldA field is missing, unknown or the wrong type
422field_set_by_verbA PATCH sends a field that only a Verb changes
422verb_stoppedThe Verb's own function threw
429rate_limitedThe key sent more requests a second than its limit
500server_errorSomething failed inside SaaS.Dev

Each code's docs link opens Leaseclock's own page for it at leaseclock.com/docs/errors, written in Leaseclock's names, so your customers fix the call without writing to you.

Questions

Does my product's API mention SaaS.Dev

No. The host, the paths, the headers, the keys and every error message use Leaseclock's names and Leaseclock's domain. A property manager calling the API sees Leaseclock.

What happens to my customers' scripts when I change the definition

Adding a field, a Noun or a Verb adds to the API, and every existing call keeps working. Renaming or removing one changes paths that scripts may call, so the push shows each changed path before anything goes Live, and the changelog lists the change for your customers.

Can a record be deleted

Yes. DELETE /api/units/6e removes the unit from every list and records the removal as a version, so its undo link brings it back with its history.

How do I call SaaS.Dev itself

api.saas.dev has the same shape for your own work: your products, their definitions and their versions. It reads $SAAS_DEV_TOKEN, which npx saas.dev login stores. See the Quickstart.

Can I change what an operation does

Yes. A Verb's logic is a function you write in its definition, and it runs inside the same checks, preview and undo. Any operation can be replaced with your own code. See Defaults and overrides.