---
title: "Join: the application"
url: https://services.dev/docs/people/pool/join
captured: [members-add.txt, pool-join.txt]
---
# Join: the application

URL: https://services.dev/docs/people/pool/join

> The application a Pool writes becomes its /join funnel, email first and resumable, ending on the member's payout account. What a member says is an application; only the verifier writes what is true.

How does a person get into a Pool, and what makes them verified?

## The application

`join` is the application, field by field, in the same type words a Service's input uses. The legal example's Pool asks for a name, a credential, where it's held and what work the person wants:

```bash
grep -A5 "join:" pools/attorneys.ts
```

```txt title="magic values: client.email = signs@example.test"
  join: {
    name: 'text: your name as admitted', credential: ['bar admission', 'patent attorney', 'patent agent', 'law school enrollment'],
    number: 'text: your bar or registration number', regions: 'jurisdiction[]: every place you are admitted',
    areas: ['patents', 'trademarks', 'contracts', 'entities', 'collections'], hours: 'number: spare hours a week',
    turnaround: ['same day', '24h', '48h'], min_fee: 'money: the least you would take per review',
  },
```

It becomes the Pool's `/join` funnel: email first, resumable, three fields a step. The feed reads what a member declares here, such as their areas, minimum fee and hours, and [what they are offered is theirs to set](https://services.dev/docs/people/pool/dispatch.md#what-you-are-offered-is-yours-to-set). Each call brings the answers it has, and the member learns what the next step asks:

```txt
→ POST /attorneys/join {"email":"grace@example.com"}
```

```txt title="magic values: client.email = signs@example.test"
← 200 mbr_7Hq2 · attorneys · grace@example.com · applying, step 1 of 3: name, credential, number · on
→ POST /attorneys/join {"email":"grace@example.com","answers":{"name":"Grace Hopper","credential":"bar admission","number":"CA-301"}}
← 200 mbr_7Hq2 · attorneys · grace@example.com · applying, step 2 of 3: regions, areas, hours · on
→ POST /attorneys/join {"email":"grace@example.com","answers":{"regions":["US-CA","US-NY"],"areas":["contracts","collections"],"hours":10}}
← 200 mbr_7Hq2 · attorneys · grace@example.com · applying, step 3 of 3: turnaround, min_fee · on
→ POST /attorneys/join {"email":"grace@example.com","answers":{"turnaround":"24h","min_fee":40}}
← 200 mbr_7Hq2 · attorneys · grace@example.com · verified (simulated): bar admission in US-CA, US-NY · on
  verified {"credentials":{"bar admission":["US-CA","US-NY"]},"coverage":{"per_act":1000000,"until":"2027-10-25T14:02:11.000Z"},"at":"2026-10-25T14:02:11.000Z","by":"fixture","fresh_until":"2026-11-24T14:02:11.000Z","fresh":true}
```

## Only the verifier writes what is true

A member never self-reports. What they apply with is what they say. What the Pool relies on is what `verify` writes, on its own write path with its own `vt_` token. A secret key is refused there:

```txt
→ POST /attorneys/signers/mbr_7Hq2/verify {"facts":{"credentials":{"bar admission":["US-CA","US-NY","US-NJ"]},"coverage":{"per_act":2000000,"until":"2027-10-25T00:00:00Z"}}}   Authorization: Bearer sk_test_7Hq2
```

```txt title="magic values: client.email = signs@example.test"
← 401 key_invalid: This door takes the network's verifier token, Authorization: Bearer vt_…, and nothing else.
```

```txt
→ POST /attorneys/signers/mbr_7Hq2/verify {"facts":{"credentials":{"bar admission":["US-CA","US-NY","US-NJ"]},"coverage":{"per_act":2000000,"until":"2027-10-25T00:00:00Z"}}}   Authorization: Bearer vt_test_7Hq2
```

```txt title="magic values: client.email = signs@example.test"
← 200 mbr_7Hq2 · attorneys · grace@example.com · verified: bar admission in US-CA, US-NY, US-NJ · on
```

Every write joins the member's recheck journal, and the latest is what is true, read fresh at each use: [Verify a member](https://services.dev/docs/people/pool/verify.md) has the verifier and its clock. On localhost a fixture verifier writes what the application claims and labels it simulated, so you can run the whole market before you wire your own.

The last step of `/join` connects the member's [payout account](https://services.dev/docs/people/pool/pay.md#your-payout-account), once for every Pool and brand.

## Add a member yourself

The owner adds a person from the CLI, through the same funnel. Answers that leave a step short keep it open, and `next` names what it still needs:

```bash
npx services.dev signers add attorneys grace@example.com "credential=bar admission" number=CA-301 regions=US-CA,US-NY areas=contracts hours=10 turnaround=24h min_fee=40
```

```txt title="magic values: none"
mbr_7Hq2 · attorneys · grace@example.com · applying, step 1 of 3: name, credential, number · on
  login   http://localhost:8787/attorneys/login?t=ms_bU0qS0IkbnMxlMCPdfIc9j
  next    npx services.dev signers add attorneys grace@example.com name=… credential=… number=…
```

A member acts only in their own session. On localhost, `members login` mints one and prints the link. A key that tries a member's act is handed the link to pass on:

```bash
npx services.dev signers login mbr_7Hq2
```

```txt title="magic values: none"
mbr_7Hq2 · a signer session, on localhost
  link    http://localhost:8787/attorneys/login?t=ms_bU0qS0IkbnMxlMCPdfIc9j
  next    export SERVICES_DEV_SESSION=ms_bU0qS0IkbnMxlMCPdfIc9j
```

```bash
npx services.dev reviews list
```

```txt title="magic values: none"
Only a signer, in their own session at http://localhost:8787/attorneys, does this. A key never can.
  next    hand http://localhost:8787/attorneys to the person it asks
  docs    https://api.lawyer/docs/errors/needs_a_person
```

These captures speak the brand's own words, as [your brand's words](https://services.dev/docs/service/brand-words.md) shows:

*legal's words: matter is a job, asset is a subject, review is a task, signer is a member, network is a pool, outcome is a end (examples/legal/brand.ts).*

## Keys

| Key | Written | Left out | Does |
|---|---|---|---|
| `join` | `Shape` | required | The application, field by field, in the type words: the /join funnel, email first, resumable, three fields a step. The feed reads `areas`, `min_fee` (dollars), `hours` and `turnaround` when it asks them. |
| `verify` | `Url \| Fn` | required | The verifier: the only writer of verified facts, on its write path with its `vt_` token. [Verify a member](https://services.dev/docs/people/pool/verify.md). |

---

- Up: [Pool(): your marketplace](https://services.dev/docs/people/pool.md)
- Previous: [Approval or completion: a Pool's register](https://services.dev/docs/people/pool/register.md)
- Next: [The door: where members join and work](https://services.dev/docs/people/pool/door.md)
