---
title: "Add a signer"
url: https://services.dev/docs/grow/role
captured: [grow-role-tabs.txt, grow-role-unstaffed.txt, grow-role.txt]
---
# Add a signer

URL: https://services.dev/docs/grow/role

> A sworn translator from your Pool signs every job. A rules row reserves the act to their credential, and the signature is stamped into the file it binds.

Some work is finished only when a credentialed person signs it. A Service meets that by design: the signer is a role filled from your Pool, a rules row reserves signing to their credential, and the gate commits the signature, bound to the exact file. AI still does the work. This is Grow 3.

## Add the signer

Three things join Translation: a role, the Pool that fills it, and one row of your rules table.

```ts title="grow/role.ts"
import { Pool, Rules, Service } from 'services.dev'

// Grow 3, add a signer: a sworn translator from lingua's Pool signs every job before delivery, and the
// stamp writes the member, their credential and the date into the file the signature binds. The rules
// row reserves signing to that credential, so only a verified member can take the step. AI does the work.
export default Service({
  name: 'Translation',
  description: 'A document, translated and signed.',
  input: { document: 'file', to: 'language: a BCP 47 tag, such as fr-CA' },
  output: { document: 'file' },
  rules: rules(),
  roles: { translator: { pool: translators(), pay: 30 } },
  deliver: {
    glossary: { ask: 'buyer', to: 'answer', items: () => [{ id: 'terms', label: 'Which terms stay in English?' }] },
    translate: 'ai',
    harden: { review: 'adversarial', lenses: ['meaning', 'terms'], rounds: 2 },
    sign: { by: 'translator', stamp: true, est: '20min' },
  },
  price: 89,
})

// lingua's Pool: sworn translators apply with their name, credential and languages, and the verifier checks each.
function translators() {
  return Pool({
    name: 'sworn-translators',
    roles: { translator: { credential: 'sworn translator' } },
    join: { name: 'text: your name as sworn', credential: ['sworn translator'], languages: 'language[]: each language you are sworn in' },
    verify: 'https://verify.example.test/sworn-translators',
  })
}

// lingua's rules table, one illustrative row: signing a translation is reserved to a sworn translator.
function rules() {
  return Rules({
    version: '2026-10',
    services: { translation: { sign: { reserved: 'sworn translator', position: 'A registry accepts a translation a sworn translator signs.' } } },
  })
}
```

| Key | What it does |
|---|---|
| `roles.translator` | Who takes the `sign` step: filled from `pool`, and paid `pay` dollars an act, posted on the Task. A role with no `pool` is your own staff |
| `Pool({ roles, join, verify })` | Your marketplace: the credential each role needs, what a person sends to join, and the verifier, the only writer of that credential |
| `Rules({ version, services })` | Your table. This row reserves `sign` to a sworn translator, and `position` records why |
| `sign.stamp` | Writes the member, their credential and the date into the file before the signature hashes it |
| `sign.est` | How long signing takes, so an offer shows its rate |

Here the Pool and the table sit below the Service. In a brand they're files of their own, `pools/` and `rules.ts`, and either can ship as a package several brands import. The row is illustrative; your counsel writes the real one ([law.org.ai](https://law.org.ai)).

## Check it

```bash
npx services.dev check
```

```txt title="magic values: none"
services/translation.ts · 13 lines · $89 per job, within 48 hours, charged on delivery.
  who     A translator signs every document before delivery. (rules 2026-10) The buyer answers at glossary.
  roles   translator · sworn translator · from sworn-translators · $30 an act
  steps   glossary · buyer · answer → translate · ai → harden · ai · adversarial · 2 lenses → sign · translator · * · stamped
  bounds  reserved sign to sworn translator
  agent   order_translation · 25 of 30 words · job_7Hq2 waits on party
  ledger  +$89.00 · AI $1.26 (example) · translator $30.00 · you $57.74
  margin  at least $41.20 when the first translator signs ($89.00 − $30.00 translator − $17.80 AI cap) · $26.20 at the ladder's cap, the lower of 50% and what the floor leaves · $11.20 after a decline and a second translator
          a job that stops costs you at most $47.80
  apps    customer portal · delivery portal · owner console

mcp     13 buyer tools · 4,490 of 8,000 tokens · 9 more with ?toolset=sandbox
✓ ready to sell · 1 Service · it keeps at least 46% of every job
  next    npx services.dev dev

AI and money are simulated until you wire them. $0.42 a call is an example.
```

`bounds` is the rules row. `margin` prices each way the signature goes: the first translator, the [fill ladder](https://services.dev/docs/people/pool/fill.md)'s cap, or a second translator after a decline. Give the role another credential and `check` refuses with [`reserved_act_unstaffed`](https://services.dev/docs/reference/check/reserved_act_unstaffed.md):

```bash
npx services.dev check
```

```txt title="magic values: none"
services/translation.ts · 13 lines · $89 per job, within 48 hours, charged on delivery.
  who     A translator signs every document before delivery. (rules 2026-10) The buyer answers at glossary.
  roles   translator · certified translator · from sworn-translators · $30 an act
  steps   glossary · buyer · answer → translate · ai → harden · ai · adversarial · 2 lenses → sign · translator · * · stamped
  bounds  reserved sign to sworn translator
  agent   order_translation · 25 of 30 words · job_7Hq2 waits on party
  ledger  +$89.00 · AI $1.26 (example) · translator $30.00 · you $57.74
  margin  at least $41.20 when the first translator signs ($89.00 − $30.00 translator − $17.80 AI cap) · $26.20 at the ladder's cap, the lower of 50% and what the floor leaves · $11.20 after a decline and a second translator
          a job that stops costs you at most $47.80
  apps    customer portal · delivery portal · owner console
  ✗ A step performs a reserved act, and no role that does it holds the credential its row reserves it to. Give the step to a role that holds the credential, or set the role's credential to one that meets it. sign performs sign, reserved to sworn translator (rules 2026-10), and the translator role holds only certified translator.
    at services/translation.ts:26 · roles.translator.credential
    https://services.dev/docs/reference/check/reserved_act_unstaffed

mcp     13 buyer tools · 4,490 of 8,000 tokens · 9 more with ?toolset=sandbox
✗ 1 to fix
  next    Give the step to a role that holds the credential, or set the role's credential to one that meets it. sign performs sign, reserved to sworn translator (rules 2026-10), and the translator role holds only certified translator.

AI and money are simulated until you wire them. $0.42 a call is an example.
```

## Run one job

A sworn translator joins first. On localhost a fixture verifier writes their credential, marked simulated:

```txt
→ POST /sworn-translators/join {"email":"ines@example.com","answers":{"name":"Inès Martin","credential":"sworn translator","languages":["fr","en"]}}
```

```txt title="magic values: none"
← 200 mbr_7Hq2 · sworn-translators · ines@example.com · verified (simulated): sworn translator in * · on
```

The job waits on the Task until the translator signs, then the buyer's next wait answers delivered:

**CLI**

```bash title="CLI"
npx services.dev translation document=https://example.com/lease.pdf to=fr-CA --max 89 --key grow
```

```txt title="magic values: none"
job_7Hq2 · working · $89.00 held
job_7Hq2 · waiting · on party · $89.00 held
  record  
  next    hand http://localhost:8787/a/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ to the person it asks

# 2 hours later, the buyer answers through the link
→ POST /asks/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ {"items":{"terms":{"answer":"Lessee, Lessor, Premises"}}}

$ npx services.dev jobs wait job_7Hq2
job_7Hq2 · waiting · on claim · $89.00 held
  record  glossary.terms · answered | glossary · buyer · answer · clock paused 2h | translate · ai | harden · ai · adversarial · 2 lenses · pass
  next    a member claims tsk_7Hq2: npx services.dev tasks sheet tsk_7Hq2, in their session

# mbr_7Hq2, the sworn translator, claims tsk_7Hq2 in the delivery portal and signs, in their own session
→ POST /tasks/tsk_7Hq2/claim · POST /tasks/tsk_7Hq2/clear · POST /tasks/tsk_7Hq2/attest

$ npx services.dev jobs wait job_7Hq2
job_7Hq2 · delivered · lease.fr-CA.pdf · $89.00 charged
```

**API**

```bash title="API"
curl -X POST http://localhost:8787/jobs -H 'content-type: application/json' -d '{"service":"translation","document":{"url":"https://example.com/lease.pdf","name":"lease.pdf"},"to":"fr-CA","key":"grow","max_usd":89}'
```

```txt title="202 · job_7Hq2 · working · $89.00 held · magic values: none"
  next    get_job {"id":"job_7Hq2"}

$ curl http://localhost:8787/jobs/job_7Hq2?wait=50
← 200 · job_7Hq2 · waiting · on party · $89.00 held
  next    hand_to_person {"ask":"ask_7Hq2"}

# 2 hours later, the buyer answers through the link
→ POST /asks/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ {"items":{"terms":{"answer":"Lessee, Lessor, Premises"}}}

$ curl http://localhost:8787/jobs/job_7Hq2?wait=50
← 200 · job_7Hq2 · waiting · on claim · $89.00 held
  next    get_job {"id":"job_7Hq2"}

# mbr_7Hq2, the sworn translator, claims tsk_7Hq2 in the delivery portal and signs, in their own session
→ POST /tasks/tsk_7Hq2/claim · POST /tasks/tsk_7Hq2/clear · POST /tasks/tsk_7Hq2/attest

$ curl http://localhost:8787/jobs/job_7Hq2?wait=50
← 200 · job_7Hq2 · delivered · lease.fr-CA.pdf · $89.00 charged
```

**MCP**

```txt title="MCP"
→ tools/call order_translation {"document":{"url":"https://example.com/lease.pdf","name":"lease.pdf"},"to":"fr-CA","key":"grow","max_usd":89}
```

```txt title="magic values: none"
← job_7Hq2 · working · $89.00 held
  next    get_job {"id":"job_7Hq2"}

→ resources/read job://job_7Hq2?wait=50
← job_7Hq2 · waiting · on party · $89.00 held
  next    hand_to_person {"ask":"ask_7Hq2"}

# 2 hours later, the buyer answers through the link
→ POST /asks/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ {"items":{"terms":{"answer":"Lessee, Lessor, Premises"}}}

→ resources/read job://job_7Hq2?wait=50
← job_7Hq2 · waiting · on claim · $89.00 held
  next    get_job {"id":"job_7Hq2"}

# mbr_7Hq2, the sworn translator, claims tsk_7Hq2 in the delivery portal and signs, in their own session
→ POST /tasks/tsk_7Hq2/claim · POST /tasks/tsk_7Hq2/clear · POST /tasks/tsk_7Hq2/attest

→ resources/read job://job_7Hq2?wait=50
← job_7Hq2 · delivered · lease.fr-CA.pdf · $89.00 charged
```

**SDK**

```ts title="SDK"
await lingua.translation.order({"document":{"url":"https://example.com/lease.pdf","name":"lease.pdf"},"to":"fr-CA","key":"grow","max_usd":89})
```

```txt title="magic values: none"
← job_7Hq2 · working · $89.00 held
  next    get_job {"id":"job_7Hq2"}

await lingua.jobs.wait("job_7Hq2")
← job_7Hq2 · waiting · on party · $89.00 held
  next    hand_to_person {"ask":"ask_7Hq2"}

# 2 hours later, the buyer answers through the link
→ POST /asks/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ {"items":{"terms":{"answer":"Lessee, Lessor, Premises"}}}

await lingua.jobs.wait("job_7Hq2")
← job_7Hq2 · waiting · on claim · $89.00 held
  next    get_job {"id":"job_7Hq2"}

# mbr_7Hq2, the sworn translator, claims tsk_7Hq2 in the delivery portal and signs, in their own session
→ POST /tasks/tsk_7Hq2/claim · POST /tasks/tsk_7Hq2/clear · POST /tasks/tsk_7Hq2/attest

await lingua.jobs.wait("job_7Hq2")
← job_7Hq2 · delivered · lease.fr-CA.pdf · $89.00 charged
```

## Only the credential signs

You own the brand, and you still can't sign:

```txt
→ POST /sworn-translators/jobs/job_7Hq2/approve
```

```txt title="magic values: none"
← 403 needs_a_person: sign is tsk_7Hq2, a Task for a member of sworn-translators: the member who claims and clears it attests, in their own session.
```

Only the member who claims the Task attests, in their own session:

```txt
→ POST /tasks/tsk_7Hq2/claim
```

```txt title="magic values: none"
← tsk_7Hq2 · claimed · translator · sign · * · $30.00 · 20 min · $90.00 an hour · due 2026-10-27 16:02
→ POST /tasks/tsk_7Hq2/clear · GET /tasks/tsk_7Hq2/packet
→ POST /tasks/tsk_7Hq2/attest
← tsk_7Hq2 · done · translator · sign · * · $30.00 · attested · 20 min · $90.00 an hour · due 2026-10-27 16:02

← job_7Hq2 · delivered · lease.fr-CA.pdf · $89.00 charged
  record  glossary.terms · answered | glossary · buyer · answer · clock paused 2h | translate · ai | harden · ai · adversarial · 2 lenses · pass | sign · person · sworn translator · * · mbr_7Hq2
  ledger  +$89.00 · AI $1.26 · translator $30.00 · you $57.74
  signed  {"member":"mbr_7Hq2","credential":"sworn translator","region":"*","rules":"2026-10","fields":["document"],"hash":"sha256:7fc0ab6fb17cb034de1bdb37a90b2e59a9dd4191277212ea85890e001962353f"}
  stamp   Signed by mbr_7Hq2, sworn translator, 2026-10-25.
```

`signed` is what the gate committed: who, under which credential and rules version, and a hash of the file as signed. A later change voids it. [The gate](https://services.dev/docs/service/gate.md) commits AI and people the same way.

## What changed

```bash
npx services.dev diff grow/ask.ts grow/role.ts
```

```txt title="magic values: none"
grow/ask.ts (9 lines) → grow/role.ts (13 lines)
Translation · changed description, deliver, human, price, portal, roles, rules · adds a person: the human line, or a step a role does; a role from a Pool: its members, and the Tasks they claim

API   ~ operation service.order.translation  POST /translation  item.description
      ~ operation service.quote.translation  GET /translation.json  answers.description, answers.deliver, answers.human.sentence
      + operation job.approve                POST /{portal}/jobs/{id}/approve
      + operation member.join                POST /{portal}/join
      + operation member.verify              POST /{portal}/members/{id}/verify
      + operation member.availability        POST /me/availability
      + operation member.ledger.get          GET /me/ledger
      + operation member.ledger.add          POST /me/ledger
      + operation member.ledger.checks       GET /me/ledger/checks
      + operation member.missed              GET /me/missed
      + operation task.list                  GET /tasks
      + operation task.get                   GET /tasks/{id}
      + operation task.sheet                 GET /tasks/{id}/sheet
      + operation task.pass                  POST /tasks/{id}/pass
      + operation task.claim                 POST /tasks/{id}/claim
      + operation task.clear                 POST /tasks/{id}/clear
      + operation task.packet                GET /tasks/{id}/packet
      + operation task.release               POST /tasks/{id}/release
      + operation task.offer                 POST /owner/tasks/{id}/offer
      + operation task.attest                POST /tasks/{id}/attest
      + operation task.send_back             POST /tasks/{id}/send_back
      + operation task.decline               POST /tasks/{id}/decline
      + operation task.recuse                POST /tasks/{id}/recuse
      + operation task.extend                POST /tasks/{id}/extend
      + operation task.contest               POST /tasks/{id}/contest
      + operation task.message               POST /tasks/{id}/messages
      + operation member.login               POST /owner/members/{id}/login
      ~ operation brand.manifest             GET /manifest.json  answers.services, answers.portals
      + object tsk_                          task
      + object mbr_                          member
      + 11 errors                            needs_a_person, not_waiting, ineligible, not_cleared, task_taken, defects_required, memo_required, needs_direction, not_contestable, reserved, question_open
CLI   ~ operation service.order.translation  services.dev translation document=… to=…  price
      + operation job.approve                services.dev jobs approve <job>
      + operation member.join                services.dev members add <pool> <email> k=v …
      + operation member.availability        services.dev me availability <availability>
      + operation member.ledger.get          services.dev me ledger
      + operation member.ledger.add          services.dev me ledger add <name> k=v …
      + operation member.ledger.checks       services.dev me ledger checks
      + operation member.missed              services.dev me missed
      + operation task.list                  services.dev tasks list
      + operation task.get                   services.dev tasks get <task>
      + operation task.sheet                 services.dev tasks sheet <task>
      + operation task.pass                  services.dev tasks pass <task> <reason>
      + operation task.claim                 services.dev tasks claim <task>
      + operation task.clear                 services.dev tasks clear <task>
      + operation task.packet                services.dev tasks packet <task>
      + operation task.release               services.dev tasks release <task>
      + operation task.offer                 services.dev tasks offer <task> <member>
      + operation task.attest                services.dev tasks attest <task> k=v …
      + operation task.send_back             services.dev tasks send-back <task> <defects>
      + operation task.decline               services.dev tasks decline <task> <memo>
      + operation task.recuse                services.dev tasks recuse <task>
      + operation task.extend                services.dev tasks extend <task>
      + operation task.contest               services.dev tasks contest <task> <reason>
      + operation task.message               services.dev tasks message <task> <kind> <text>
      + operation member.login               services.dev members login <member>
      + 11 errors                            needs_a_person, not_waiting, ineligible, not_cleared, task_taken, defects_required, memo_required, needs_direction, not_contestable, reserved, question_open
MCP   ~ operation order_translation          order_translation  description
      ~ prompt buy_translation               prompts/get buy_translation  description
SDK   ~ object job_ (translation)            Job<"translation">  + stage: "glossary" | "translate" | "harden" | "sign"
      + object tsk_                          task
      + object mbr_                          member
      + 11 errors                            needs_a_person, not_waiting, ineligible, not_cleared, task_taken, defects_required, memo_required, needs_direction, not_contestable, reserved, question_open
Site  ~ operation service.order.translation  /translation  + A document, translated and signed., + $89 per job, within 48 hours, charged on delivery. A translator signs at sign, stamped. Th
      ~ operation service.quote.translation  /translation.json  description, deliver, human.sentence
Docs  ~ operation service.order.translation  /llms.txt · Translation  + A document, translated and signed. $89 per job, within 48 hours, charged on delivery. A tr, + curl http://localhost:8787/jobs -H 'content-type: application/json' -d '{"service":"transl
      + 11 errors                            needs_a_person, not_waiting, ineligible, not_cleared, task_taken, defects_required, memo_required, needs_direction, not_contestable, reserved, question_open
App   ~ operation job.list                   /portal · customer portal  + translation: A translator signs at sign, stamped. The buyer answers at glossary.
      + operation job.approve                /sworn-translators
      ~ operation job.ledger                 /console · owner console  + translation: $89 per job, within 48 hours, charged on delivery. A translator signs at sign
```

A Pool brings its own surface: joining and every Task act on the API and CLI, and [the delivery portal](https://services.dev/docs/app/members.md) in the App. A person acts in their own session, so none of it is an MCP tool.

## Next

The tutorial goes on to [File it somewhere](https://services.dev/docs/grow/submit.md). On the licensed path, read [human: the line](https://services.dev/docs/people.md), then [rules](https://services.dev/docs/people/rules.md) and [roles](https://services.dev/docs/people/roles.md).

---

- Up: [Your first paid job](https://services.dev/docs.md)
- Previous: [Ask the buyer](https://services.dev/docs/grow/ask.md)
- Next: [File it somewhere](https://services.dev/docs/grow/submit.md)
