Routing
How each file in a Startup project becomes URLs, paths and names on every Interface, with no route table to maintain.
Every route in a Startup derives from its files. A Noun gets API paths, a Site folder gets URLs, an App gets screens, and a Workflow gets a name on every Interface.
API
| Call | Route |
|---|---|
| List a Collection | GET /{ns}/ |
| Read a record | GET /{ns}/ |
| Create | POST /{ns}/ |
| Run a Verb | POST /{ns}/ |
| Run a Collection Verb | POST /{ns}/ |
| A record as Markdown | GET /{ns}/ |
| A record at a past moment | GET /{ns}/ |
A Verb is a path segment, never :verb or .verb. The dot appears only in names: $.Offer.publish, do Offer.publish, the MCP program and the OpenAPI operationId. A record or Collection named like a declared Verb is refused at write time.
curl -X POST https: //api.sb/ leaseline.app/ offers/ offer_1/ publish \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"Sites
| File or folder | URL |
|---|---|
sites/ | / |
sites/ | /pricing |
sites/ | /security |
sites/ | /blog/, one Page per member |
sites/ | /pricing, as an Experiment Variant |
sites/ | none |
sites/ | every unmatched path below |
Every Site also serves a Markdown twin of each Page, /llms.txt, /agents.json, /sitemap.xml and /openapi.json. /terms and /privacy are generated and locked.
Apps and Interfaces
| Path | Serves |
|---|---|
/app/ | An App's list screen |
/app/ | One record |
/app/ | The form for one Verb |
/api | The API on the Startup's own Domain |
/docs, /blog, /dashboard, /admin | Docs, the blog Collection, Dashboards, the admin App |
https: | MCP, scoped by namespace |
The same App URL returns HTML, JSON or Markdown, by reader. The CLI and the SDK have no route.
Workflows
A Workflow's path in workflows/ is its name everywhere. workflows/ is $.renewals.noticeAt60Days in the SDK and renewals.noticeAt60Days in MCP.
Custom routes
Bespoke routes are the exception. A need that seems to call for one first changes the generic parts: records, Verbs, saved queries or MCP. When none serves, startup.ts routes a path to a program in code/ and states why:
routes: { '/webhooks/ partner': { to: 'code/ partner-webhook', reason: 'The partner posts form-encoded webhooks that no Verb accepts.' } }The check refuses a route with no reason. There is no routes/ folder and no route file.