# Design an API

URL: /products/api/design · Id: `page:products/api/design`

> How to shape an API a Customer's code and agents read at a glance, by naming its Nouns and Verbs and letting every route and reply follow by rule.

An API's design is its [Nouns](/business/nouns/noun) and [Verbs](/business/nouns/verb). Routes, ids, the response shape and the errors follow from them by rule, so two [Startups](/business/nouns/startup)' APIs read alike.

## Start from the Nouns
Name the things a [Customer](/business/nouns/customer)'s code reads and changes, and the states each one moves through. Leaseline declares a `Lease` Noun: one resident's agreement on one unit, `active`, `renewing`, `renewed` or `ended`.

Every Noun gets `create`, `get`, `list`, `update` and `delete` from the platform. A delete is soft, so a deleted `Lease` leaves every list and keeps its history.

## Verbs are the routes
Each Verb is one route, named for the Noun and the action.

| Verb | Route |
| --- | --- |
| `Lease.list` | `GET https://api.leaseline.app/leases` |
| `Lease.get` | `GET https://api.leaseline.app/leases/{lease}` |
| `Lease.renew` | `POST https://api.leaseline.app/leases/{lease}/renew` |
| `Lease.notify` | `POST https://api.leaseline.app/leases/{lease}/notify` |

Prefer a Verb with a business name to an `update` that sets a status. `Lease.renew` can refuse an `ended` lease and emit `Lease.renewed`, and a raw field write can do neither.

## One response shape
Every response is one envelope: the record under its Noun's name, its `$id`, `links` to read, and the `actions` this caller may run now.

```json
{
  "api": "https://api.leaseline.app",
  "$type": "Lease",
  "$id": "https://api.leaseline.app/leases/lease_4loEed1JG4",
  "lease": {
    "$id": "https://api.leaseline.app/leases/lease_4loEed1JG4",
    "$type": "Lease",
    "unit": "Unit 5C, 76 Harbor Way",
    "endsOn": "2026-11-05",
    "status": "renewing"
  },
  "links": { "self": "https://api.leaseline.app/leases/lease_4loEed1JG4", "up": "https://api.leaseline.app/leases" },
  "actions": { "renew": "https://api.leaseline.app/leases/lease_4loEed1JG4/renew" }
}
```

An id is the Noun's prefix and ten characters, such as `lease_4loEed1JG4`. [The response envelope](/code/api/guides/envelope) and [the id rules](/code/api/guides/ids) cover both.

## Errors that name the fix
A refusal is a problem document on the Startup's own error namespace. It says what failed, how to fix it, and which call to make next.

```json
{
  "type": "https://leaseline.app/errors/invalid-input",
  "title": "Invalid input",
  "status": 400,
  "detail": "endsOn must be a date after today.",
  "fix": "Send endsOn as a future date, such as 2027-11-05."
}
```

Writes take an `Idempotency-Key`, so a retry runs once. Lists page by cursor and filter with one grammar: `where=status = renewing`. [Pagination and filtering](/code/api/guides/pagination-and-filtering) covers it.