> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wellapp.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# PersonPhone

> PersonPhone is a pivot entity that links a People record to a Phone record within a workspace, carrying metadata about the association

PersonPhone is a pivot entity that links a People record to a Phone record within a workspace, carrying metadata about the association. It implements the canonical pivot pattern with `is_primary`, `is_verify`, and `label` fields so a single person can hold multiple phone numbers under different categories. Records are written by the connector sync pipeline and by user-facing People mutations (add/remove phone); there is no standalone PATCH surface for individual `person_phones` rows. Soft-delete semantics via `deleted_at` are enforced, and partial unique indexes guarantee at most one primary phone per person at any time.

| Naming                          | Value                             |
| ------------------------------- | --------------------------------- |
| Object                          | PersonPhone                       |
| Resource type (JSON:API `type`) | `person_phone`                    |
| Collection / records root       | — <sub>(not a records root)</sub> |
| REST base                       | `/v1/person-phones`               |
| Entity class                    | `PersonPhone`                     |

<Note>
  **Internal object.** Not currently exposed on the public REST API. The operations below describe the intended contract.
</Note>

## API operations

| Operation | Method & path                   | Status     |
| --------- | ------------------------------- | ---------- |
| List      | `GET /v1/person-phones`         | 🟡 Planned |
| Retrieve  | `GET /v1/person-phones/{id}`    | 🟡 Planned |
| Create    | `POST /v1/person-phones`        | 🟡 Planned |
| Update    | `PATCH /v1/person-phones/{id}`  | 🟡 Planned |
| Delete    | `DELETE /v1/person-phones/{id}` | 🟡 Planned |

## Data model

### Attributes

| Field       | Type                                                                | Required | Constraints                                                                                                                                                        | Allowed values                      | Description                                                                                                                                                                                                                                                  |
| ----------- | ------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| is\_primary | boolean                                                             | ⚪ No     | DEFAULT false; partial unique index `uniq_person_phones_primary_person` enforces at most one row per `person_pk` WHERE `deleted_at IS NULL AND is_primary IS TRUE` | true / false                        | Marks this phone number as the person's primary contact number. At most one active PersonPhone per person may have `is_primary = true` at a time (enforced by partial unique index).                                                                         |
| is\_verify  | boolean                                                             | ⚪ No     | DEFAULT false                                                                                                                                                      | true / false                        | Indicates whether the phone number has been verified (e.g., via OTP or connector confirmation).                                                                                                                                                              |
| label       | 🔒 system enum — `person_phone_label_enum` (native PostgreSQL enum) | ✅ Yes    | DEFAULT 'other'; stored as native enum `core_api.person_phone_label_enum`; values are the enum VALUE strings (lowercase)                                           | mobile \| work \| personal \| other | Categorises the nature or context of the phone number. Stored in a native PostgreSQL enum; the persisted value is the lowercase string (e.g. `"mobile"`, not the TypeScript key `MOBILE`).                                                                   |
| created\_at | 🔒 system datetime                                                  | ✅ Yes    | Set automatically on INSERT via `onCreate` hook; `timestamptz NOT NULL`                                                                                            | —                                   | Timestamp when the pivot record was created. Set by the MikroORM `onCreate` lifecycle hook; never updated afterward.                                                                                                                                         |
| deleted\_at | 🔒 system datetime \| null                                          | ⚪ No     | Nullable `timestamptz`; all active-record queries must filter `deleted_at IS NULL`                                                                                 | —                                   | Soft-delete timestamp. When set, the link between the person and phone is logically removed but the row is retained for audit. The partial indexes on `person_pk` and the primary-phone uniqueness constraint both scope themselves to `deleted_at IS NULL`. |

### Relationships

| Name   | Type               | Required | Description                                                                                                                                                                                                                                                                          |
| ------ | ------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| person | to-one (ManyToOne) | ✅ Yes    | The People record this phone number is associated with. Foreign key `person_pk` → `core_api.people.pk`. Indexed via partial index `idx_person_phones_person` (`person_pk` WHERE `deleted_at IS NULL`) for forward traversal, plus partial unique index for primary-phone uniqueness. |
| phone  | to-one (ManyToOne) | ✅ Yes    | The atomic Phone record containing the actual phone number value (e.g. `e164_number`). Foreign key `phone_pk` → `core_api.phones.pk`. Indexed via `idx_person_phones_phone` (`phone_pk`) for reverse traversal (phones → person\_phones array relationship).                         |

### System-computed

* pk — auto-increment serial, internal join key only; never exposed in the public API
* created\_at — set on INSERT by MikroORM `onCreate: () => new Date()` hook; no `onUpdate` hook exists on this entity (no `updated_at` column)
* deleted\_at — soft-delete field; set to current timestamp when the phone association is removed via People mutation or connector sync; null on active records
* label default — ORM default `PersonPhoneLabelEnum.OTHER` ('other') applied on entity construction; also enforced as `DEFAULT 'other'` in PostgreSQL via the native enum column
* is\_primary default — ORM default `false`; also `DEFAULT false` in PostgreSQL
* is\_verify default — ORM default `false`; also `DEFAULT false` in PostgreSQL
* Partial unique index `uniq_person_phones_primary_person` — system-enforced constraint preventing more than one primary phone per person among non-deleted rows; managed at DB level, not ORM level

## Example

```json theme={null}
{
  "data": {
    "type": "person_phone",
    "attributes": {
      "is_primary": true,
      "is_verify": false,
      "label": "mobile",
      "created_at": "2025-11-14T09:23:17.000Z",
      "deleted_at": null
    },
    "relationships": {
      "person": {
        "data": { "type": "people", "id": "d3a1c9e7-4f02-4b88-9c11-2e5f7a83bc40" }
      },
      "phone": {
        "data": { "type": "phone", "id": "a7b2e451-0c3d-4e6f-8110-9d4f2c1b5e78" }
      }
    }
  }
}
```

<sub>Source: `apps/api/src/database/entities/PersonPhone.ts` · domain: financial-graph · tier: Supporting</sub>
