Your brand's words
Brand.words renames the core's generic words on every surface your buyers, their agents and your members read. The core says job; your routes, events, commands, tools, SDK, Site, docs and apps say yours.
Brand.words answers one question: what does your brand call the things the core calls a job, a Task and a member?
One key in brand.ts, beside the four-line Translation. check reads it back:
git add -A && git diff --stageddiff --git a/brand.ts b/brand.ts
index d78f630..cbf0f47 100644
--- a/brand.ts
+++ b/brand.ts
@@ -2,5 +2,5 @@ import { Brand } from 'services.dev'
export default Brand({
domain: 'lingua.example',
- words: { job: 'order' },
+ words: { job: 'project' },
})npx services.dev checkbrand.ts · 2 lines · lingua · lingua.example · words job → project
✓ ready to sell · 1 Service · AI delivers it alone · it keeps at least 80% of every jobOn the brand
| Key | Written | Left out | Does |
|---|---|---|---|
words | { <generic word>: string } | none | The brand's own words for the generic ones, on every Layer B surface: paths, events, commands, tools, SDK names, docs, the Site and the apps. Each plural is the word's own. |
also | { <op id>: path | path[] } | none | Paths of the brand's own at which an operation is also served, beside the row's: { 'member.ledger.get': '/conflicts' }. A declared column of the operation registry, like words: the same handler, the same callers, in the HTTP column of every surface that renders it. An id that names no operation, or a path that collides with one services.dev serves, fails word_collision. |
The generic words a brand may rename on its own surfaces.
| Generic word | Many |
|---|---|
job | jobs |
subject | subjects |
task | tasks |
member | members |
pool | pools |
end | ends |
ask | asks |
step | steps |
client | clients |
buyer | buyers |
client is the party whose matter it is and buyer the party who pays; a brand where they are always one party gives both one word, as the sales brand names each the seller, and word_repeated allows that pair alone. The core, these docs and every op id keep the generic words. Your surfaces render yours. The worked legal brand sets six words, and each row renders in them:
cat brand.ts words: { job: 'matter', subject: 'asset', task: 'review', member: 'signer', pool: 'network', end: 'outcome' },
http GET /jobs/:id → GET /matters/:id ✓ a route
cli jobs get → matters get ✓ a command
mcp get_job → get_matter ✓ a tool in tools/list
sdk jobs.get() → matters.get() ✓ a method
http POST /jobs → POST /matters ✓ a route
cli contract-review → contract-review ✓ a command
mcp order_contract_review → order_matter ✓ a tool in tools/list
sdk contractReview.order() → contractReview.order() ✓ a method
emits job.ordered, charge.held → matter.ordered, charge.held ✓ on GET /events, after the order belowEach row of the contract renders once per word: the path takes the plural, an event the singular, a CLI command the plural, an MCP tool the singular and an SDK namespace the plural. The manifest carries both columns, so an agent reading it knows job.get by either name:
→ GET /manifest.json words.job {"generic":{"one":"job","many":"jobs"},"brand":{"one":"matter","many":"matters"}}
rendered 46 rows · "job.get": {"http":"GET /matters/:id","cli":"matters get <matter>","mcp":"get_matter","sdk":"matters.get()"}An order, its next call and its feed then speak the brand's words, and so does every refusal, with its page at the brand's own origin:
→ POST /matters {"service":"contract-review","contract":"lease.pdf","client":{"name":"Ada Lovelace","email":"ada@example.com"},"client_region":"US-CA","max_usd":99,"key":"lease"} next {"do":"get","arguments":{"id":"job_7Hq2"},"tool":"get_matter","http":"GET http://localhost:8787/matters/job_7Hq2?wait=50"}
→ GET /events
← matter.ordered · charge.held→ GET /matters/job_none← 404 not_found: No matter job_none.
doc_url https://api.lawyer/docs/errors/not_foundEvery surface follows: the routes in openapi.json, the MCP tools, the generated SDK, the CLI's help, llms.txt and the Site. What the brand gets prints every one.
also serves a row at a path of your own beside the rendered one, so a brand porting an existing API keeps its old paths. Renaming happens only here: nothing else in a Service can rename a generic word.
A brand word may not take a generic word, a word an op id is spelled with, or a path services.dev already serves. check fails word_collision, names the word and the row it collides with, and reads every other gate in the generic words until you choose another:
npx services.dev checkbrand.ts · 2 lines · lingua · lingua.example · words job → order
✗ A Brand.words value takes a generic word, a word an operation's id is spelled with or a path services.dev already serves, or makes two rows render the same, so two things would read the same. Choose a word of your own for it. words.job is 'order', a word of the operation service.order.
https://services.dev/docs/reference/check/word_collision
✗ 1 to fix
next Choose a word of your own for it. words.job is 'order', a word of the operation service.order.check gate | Why |
|---|---|
word_collision | A Brand.words value takes a generic word, a word an operation's id is spelled with or a path services.dev already serves, or makes two rows render the same, so two things would read the same. |
| When the file won't load | Thrown by | Why |
|---|---|---|
word_repeated | Brand() | Brand: words.job and words.task are both 'order', so two things would read the same. |
word_unlowercase | Brand() | Brand: words.job is 'Matter'. A word is lowercase letters and hyphens, and its plural is made for it: 'matter'. |