Your brand's words

The running jobjob_7Hq2 · working · $49.00 held

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:

Run
git add -A && git diff --staged
magic values: none
diff --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' },
 })
Run
npx services.dev check
magic values: none
brand.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 job

On the brand

KeyWrittenLeft outDoes
words{ <generic word>: string }noneThe 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[] }nonePaths 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 wordMany
jobjobs
subjectsubjects
tasktasks
membermembers
poolpools
endends
askasks
stepsteps
clientclients
buyerbuyers

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:

Run
cat brand.ts
magic values: none
  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 below

Each 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:

Call
→ GET /manifest.json
magic values: none
  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:

Call
→ 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"}
202 · job_7Hq2 · working · $99.00 held · magic values: none
  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
Call
→ GET /matters/job_none
magic values: none
← 404 not_found: No matter job_none.
  doc_url https://api.lawyer/docs/errors/not_found

Every 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:

Run
npx services.dev check
magic values: none
brand.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 gateWhy
word_collisionA 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 loadThrown byWhy
word_repeatedBrand()Brand: words.job and words.task are both 'order', so two things would read the same.
word_unlowercaseBrand()Brand: words.job is 'Matter'. A word is lowercase letters and hyphens, and its plural is made for it: 'matter'.