> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.yunojuno.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.yunojuno.com/_mcp/server.

# Directory & Contractors

## What is the Directory?

On the YunoJuno platform, the **directory** is your view of the contractors you work with. It is built around the **client contact** record: a stable entity that represents the long-term relationship between your organisation and a single contractor.

Unlike a contractor's global profile, the client contact belongs to the relationship itself. Client-contact custom fields and directory-level lifecycle state all sit on this record. As a contractor moves through registration and onboarding with you, the same `client_contact_id` remains the stable anchor across every lifecycle event.

This matters because the contractor lifecycle and the client-contact lifecycle are related but not identical. A client contact can be created for a brand-new invitee, or it can be created for a contractor who is already registered and onboarded elsewhere on YunoJuno. Directory events describe what is happening for your specific relationship with a contractor, not for the contractor globally.

---

## Actors

| Actor          | Who they are                                         | What they do                                                                                                                      |
| -------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| **Hirer**      | A person or service account at your organisation     | Invites contractors into your directory, adds existing YunoJuno contractors, and manages client-contact custom fields.            |
| **Contractor** | The independent worker being added to your directory | Accepts the invite (if new to YunoJuno), completes registration, and works through onboarding until they become bookable.         |
| **Platform**   | Automated YunoJuno lifecycle logic                   | Tracks registration, onboarding, and bookability for each client-contact relationship, and emits webhooks as those states change. |

---

## Lifecycle Overview

Each client-contact relationship progresses through a sequence of directory lifecycle events:

```mermaid
flowchart TD
    Created["client_contact.created"]
    Registered["client_contact.registered"]
    Onboarded["client_contact.onboarding_completed"]
    Updated["client_contact.custom_fields_updated"]

    Created --> Registered --> Onboarded
    Onboarded -.->|any time after creation| Updated
    Created -.->|any time after creation| Updated
```

Not every relationship passes through every event. A contractor who is already registered and onboarded when you add them will trigger `created` with a lifecycle snapshot that already shows them as registered and finished onboarding — no separate `registered` or `onboarding_completed` event will follow.

---

## Lifecycle Events

### `client.client_contact.created`

Emitted when the client-contact relationship is first established. This covers both of the ways a client contact can come into existence:

* you invite a new contractor into your directory
* you add a contractor who already exists on YunoJuno

Because both routes share this event, `created` does not imply that the contractor is still invited. The webhook snapshot may already show `registration_state = REGISTERED` and `onboarding_state = FINISHED` if the contractor was already established on-platform when the relationship began.

### `client.client_contact.registered`

Emitted only for invite-driven flows, once the contractor completes account registration. In platform terms, this is the point at which the contractor leaves the invited state, becomes registered, and begins onboarding for your organisation.

### `client.client_contact.onboarding_completed`

Emitted when an invited contractor finishes onboarding. Consumers should treat `status.bookability_state` on the payload as the source of truth for bookability, rather than inferring it from the event name alone. In most directory-invite flows this event coincides with the contractor becoming bookable, but the payload remains authoritative.

### `client.client_contact.custom_fields_updated`

Emitted whenever client-contact custom fields are updated. This event shares the same `data.object` snapshot as the other lifecycle events, and additionally carries `data.changes` so the exact fields touched in this update are visible.

---

## Status Fields

Every lifecycle webhook carries a `status` object describing the current state of the client-contact relationship. The shape is intended to be integration-friendly rather than a raw mirror of internal model names.

| Field                | What it means                                                                                    |
| -------------------- | ------------------------------------------------------------------------------------------------ |
| `registration_state` | Whether the contractor is still invited or now registered in the context of this client contact. |
| `onboarding_state`   | The current onboarding milestone for the relationship.                                           |
| `bookability_state`  | Whether the contractor can currently be booked by you.                                           |

The onboarding states exposed today are:

| State                        | What it means                                                                |
| ---------------------------- | ---------------------------------------------------------------------------- |
| `WAITING_EMAIL_CONFIRMATION` | The contractor has registered but has not yet confirmed their email address. |
| `STARTED`                    | The contractor has begun onboarding but has not yet completed it.            |
| `FINISHED`                   | Onboarding is complete for this relationship.                                |

---

## Custom Field Diffs

When client-contact custom fields change, the webhook carries both the full current snapshot and a diff of what changed in this event:

* `data.object.custom_fields` is the full current snapshot of all custom fields after the update
* `data.changes.fields` lists only the fields touched by this event
* each entry in `data.changes.fields` carries `old` and `new` value snapshots so the exact before-and-after can be read directly

### Change Types

| Change type | What it means                                               |
| ----------- | ----------------------------------------------------------- |
| `ADDED`     | The field did not have a value before and now does.         |
| `UPDATED`   | The field had a value before and has a different value now. |
| `CLEARED`   | The field had a value before and has been cleared.          |

In current directory flows these are the common change types. The webhook primitives are intentionally future-friendly so the API can support richer custom-field change semantics over time without changing the outer event shape.

### Example

```json
{
  "event_type": "client.client_contact.custom_fields_updated",
  "data": {
    "object": {
      "client_contact_id": "clct_c2k8wm3du8fe1fnxg6ch1hbr0",
      "custom_fields": [
        { "name": "cost_center", "value": "CC-200", "metadata": null },
        { "name": "secondary_approver", "value": "", "metadata": null }
      ]
    },
    "changes": {
      "changed_fields_count": 2,
      "fields": [
        {
          "name": "cost_center",
          "change_type": "UPDATED",
          "old": { "value": "CC-100", "metadata": null },
          "new": { "value": "CC-200", "metadata": null }
        },
        {
          "name": "secondary_approver",
          "change_type": "CLEARED",
          "old": { "value": "jane.smith@example.com", "metadata": null },
          "new": { "value": "", "metadata": null }
        }
      ]
    }
  }
}
```