Skip to content

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

CallRoute
List a CollectionGET /{ns}/{plural}
Read a recordGET /{ns}/{plural}/{id}
CreatePOST /{ns}/{plural}
Run a VerbPOST /{ns}/{plural}/{id}/{verb}
Run a Collection VerbPOST /{ns}/{plural}/{verb}
A record as MarkdownGET /{ns}/{plural}/{id}.md
A record at a past momentGET /{ns}/{plural}/{id}@2026-10-01

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.

Terminal
curl -X POST https://api.sb/leaseline.app/offers/offer_1/publish \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"

Sites

File or folderURL
sites/www/page.mdx/
sites/www/pricing/page.mdx/pricing
sites/www/(legal)/security/page.mdx/security
sites/www/blog/[post]/page.mdx/blog/:post, one Page per member
sites/www/pricing/@annual/page.mdx/pricing, as an Experiment Variant
sites/www/_drafts/none
sites/www/not-found.mdxevery 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

PathServes
/app/{plural}An App's list screen
/app/{plural}/{id}One record
/app/{plural}/{id}/{verb}The form for one Verb
/apiThe API on the Startup's own Domain
/docs, /blog, /dashboard, /adminDocs, the blog Collection, Dashboards, the admin App
https://api.sb/mcpMCP, 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/renewals/notice-at-60-days.mdx 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:

TypeScript
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.