# Member Directory

URL: /business/nouns/member-directory · Id: `noun:member-directory`

> A Member Directory is a Customer’s own list of people and groups, pushed into its Tenant so access follows employment.

A Member Directory is a [Customer](/business/nouns/customer)’s own list of people and groups, pushed into its [Tenant](/business/nouns/tenant) so access follows employment.

## Properties
| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `state` | `string` | Yes | Where it stands; only `active` fills Roles. |
| `groupRoles` | `json` | No | Which directory group fills which Role in the Tenant. |
| `lastChangeAt` | `datetime` | No | When the Customer’s directory last pushed a change. |

## Object
```json
{
  "$id": "https://api.sb/kilnbakery.com/member-directories/member_directory_1",
  "$type": "MemberDirectory",
  "state": "draft",
  "groupRoles": {},
  "lastChangeAt": "2026-10-22T12:00:00Z"
}
```

## Relationships
- <a id="rel-provisions"></a>A Member Directory **provisions** one [Tenant](/business/nouns/tenant): a Member Directory fills and empties [Roles](/business/nouns/role) in one [Tenant](/business/nouns/tenant) as people join and leave the [Customer](/business/nouns/customer). `provisions`, inverse [`provisionedBy`](/business/nouns/tenant#rel-provisioned-by).

## Lifecycle

| State | Meaning | Moves to |
| --- | --- | --- |
| `draft` (first) | Waiting for the Customer’s directory to connect. | `active` by [MemberDirectory.provision](/business/nouns/member-directory/provision) |
| `active` | The Customer’s directory pushes every hire, change and departure, and its groups fill Roles. | `failing` on [`MemberDirectory.failed`](/business/nouns/member-directory#memberdirectoryfailed); `inactive` by [MemberDirectory.deactivate](/business/nouns/member-directory/deactivate) |
| `failing` | Pushes stopped arriving or are refused, so access no longer follows the directory. | `active` by [MemberDirectory.provision](/business/nouns/member-directory/provision); `inactive` by [MemberDirectory.deactivate](/business/nouns/member-directory/deactivate) |
| `inactive` | Turned off: everyone it added keeps their Roles until an admin removes them. | `active` by [MemberDirectory.provision](/business/nouns/member-directory/provision) |

## Verbs
| Verb | Description | Subject | Event | Class |
| --- | --- | --- | --- | --- |
| [MemberDirectory.deactivate](/business/nouns/member-directory/deactivate) | Deactivate stops a Member Directory’s pushes; everyone it added keeps their Roles until an admin removes them. | [Security Engineer](/agents/engineering/security-engineer) | [`MemberDirectory.deactivated`](/business/events/MemberDirectory.deactivated) | Two-way: `try`, `send`, `do` |
| [MemberDirectory.deprovision](/business/nouns/member-directory/deprovision) | Deprovision ends one person’s access to a Tenant in one Action when the Customer’s directory removes them. | [Security Engineer](/agents/engineering/security-engineer) | [`MemberDirectory.deprovisioned`](/business/events/MemberDirectory.deprovisioned) | Two-way: `try`, `send`, `do` |
| [MemberDirectory.provision](/business/nouns/member-directory/provision) | Provision starts a Customer’s Member Directory pushing people and groups into its Tenant, with groups mapped to Roles. | [Security Engineer](/agents/engineering/security-engineer) | [`MemberDirectory.provisioned`](/business/events/MemberDirectory.provisioned) | Two-way: `try`, `send`, `do` |
| <a id="create"></a>[MemberDirectory.create](/business/nouns/member-directory/create) | Creates a Member Directory. Inherited from [Noun](/business/nouns/noun). | None. | `MemberDirectory.created` | Two-way: `try`, `send`, `do` |
| <a id="get"></a>[MemberDirectory.get](/business/nouns/member-directory/get) | Returns one Member Directory by id. Inherited from [Noun](/business/nouns/noun). | None. | None. | Read: `try`, `do` |
| <a id="list"></a>[MemberDirectory.list](/business/nouns/member-directory/list) | Returns Member Directories, newest first, one page at a time. Inherited from [Noun](/business/nouns/noun). | None. | None. | Read: `try`, `do` |
| <a id="update"></a>[MemberDirectory.update](/business/nouns/member-directory/update) | Changes the given properties of a Member Directory. Inherited from [Noun](/business/nouns/noun). | None. | `MemberDirectory.updated` | Two-way: `try`, `send`, `do` |

## Events
Events no Verb emits. Each Verb’s Event is in the Verbs table above; every Event is delivered in the same shape.

### MemberDirectory.failed
A Customer’s directory stopped pushing, or its pushes are refused, so access no longer follows it. Raised by the rail.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | Yes | `MemberDirectory.failed`. |
| `at` | `datetime` | Yes | When it happened. |
| `seq` | `integer` | No | Its position in the namespace, one more than the Event before; any seq is a cursor into the history. |
| `data` | `json` | Yes | The Thing as the Action left it. |
| `data.memberDirectory` | [Member Directory](/business/nouns/member-directory) | Yes | The Member Directory as the change left it. |
| `data.memberDirectory.state` | `string` | Yes | Where it stands; only `active` fills Roles. |
| `data.memberDirectory.groupRoles` | `json` | No | Which directory group fills which Role in the Tenant. |
| `data.memberDirectory.lastChangeAt` | `datetime` | No | When the Customer’s directory last pushed a change. |
| `data.cause` | `string` | Yes | What failed: `tokenRevoked`, `noPushIn24h`, or `pushRefused`. |
| `data.previous.state` | `string` | Yes | The `state` before this Event: `active`. |

```json
{
  "$id": "https://api.sb/kilnbakery.com/events/event_1",
  "$type": "Event",
  "name": "MemberDirectory.failed",
  "at": "2026-10-22T12:00:00Z",
  "data": {
    "memberDirectory": {
      "$id": "https://api.sb/kilnbakery.com/member-directories/member_directory_1",
      "$type": "MemberDirectory",
      "state": "failing",
      "groupRoles": {},
      "lastChangeAt": "2026-10-22T12:00:00Z"
    },
    "cause": "tokenRevoked",
    "previous": {
      "state": "active"
    }
  },
  "seq": 1
}
```

## Permissions
Which Access level may call each Verb. One-way Verbs wait their delay window.

| Operation | `owner` | `admin` | `member` | `viewer` |
| --- | --- | --- | --- | --- |
| `MemberDirectory.create` | Yes | Yes | No | No |
| `MemberDirectory.get` | Yes | Yes | Yes | Yes |
| `MemberDirectory.list` | Yes | Yes | Yes | Yes |
| `MemberDirectory.update` | Yes | Yes | No | No |
| `MemberDirectory.deactivate` | Yes | Yes | No | No |
| `MemberDirectory.deprovision` | Yes | Yes | No | No |
| `MemberDirectory.provision` | Yes | Yes | No | No |

## Call it

**Ask**

```text
Provision the latest member directory with a tenant.
```

**API**

```bash
curl -X POST https://api.sb/tutorloop.com/member-directories/provision \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tenant":"tenant_1"}'
```

```json
{
  "api": "https://api.sb/tutorloop.com",
  "directory": {
    "$id": "https://api.sb/tutorloop.com/member-directories/member_directory_1",
    "$type": "MemberDirectory",
    "state": "draft"
  },
  "action": {
    "$id": "https://api.sb/tutorloop.com/actions/action_1",
    "operation": "MemberDirectory.provision",
    "status": "done"
  },
  "links": {
    "self": "https://api.sb/tutorloop.com/actions/action_1"
  }
}
```

**SDK**

```ts
import { $ } from 'startup-builder'

const directory = await $.MemberDirectory.provision({ tenant: 'tenant_1' })
```

**CLI**

```bash
npx startup-builder do MemberDirectory.provision --json '{"tenant":"tenant_1"}'
```

**MCP**

```json
{
  "name": "do",
  "arguments": {
    "code": "return await $.MemberDirectory.provision({ tenant: 'tenant_1' })"
  }
}
```

## Example
A 400-person [Customer](/business/nouns/customer) pushes its staff directory into its [Tenant](/business/nouns/tenant). The `finance` group fills the [Role](/business/nouns/role) that approves [Bills](/business/nouns/bill), and a person who leaves loses access the same hour.