Design an API
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 and Verbs. Routes, ids, the response shape and the errors follow from them by rule, so two Startups' APIs read alike.
Start from the Nouns
Name the things a 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: |
Lease.get | GET https: |
Lease.renew | POST https: |
Lease.notify | POST https: |
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.
{
"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 and the id rules 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.
{
"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=. Pagination and filtering covers it.