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

# Contacts

> The JMAP Contacts surface (RFC 9610): AddressBook and ContactCard (JSContact), contact groups, cross-account copy, version history — and the vCard/CardDAV round-trip guarantees.

Contacts implement [RFC 9610](https://www.rfc-editor.org/rfc/rfc9610) (`urn:ietf:params:jmap:contacts`), with cards as JSContact objects ([RFC 9553](https://www.rfc-editor.org/rfc/rfc9553)). The same data is served over [CardDAV](../operator/legacy-protocols).

## Objects and methods

| Object                     | Methods                                          | Notes                                                                                                                       |
| -------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| `AddressBook`              | `get`, `set`, `query`, `changes`                 | Collections with `shareWith` grants.                                                                                        |
| `ContactCard`              | `get`, `set`, `query`, `queryChanges`, `changes` | JSContact cards.                                                                                                            |
| `ContactCard` (extensions) | `getHistory`, `restoreVersion`, `promote`        | Version history, restore, and promotion of an auto-collected address to a real card.                                        |
| `Contact/copy`             | —                                                | RFC 8620 `/copy` between accounts (e.g. personal → shared address book).                                                    |
| `ContactGroup`             | `get`, `set`, `query`, `changes`                 | Groups referencing cards.                                                                                                   |
| `ContactDirectory`         | `get`, `set`, `changes`                          | Tenant-plane shared directories — see [below](#tenant-contact-directories). Requires `urn:oximail:params:jmap:v2:contacts`. |
| `ContactDirectoryEntry`    | `get`, `set`, `query`, `changes`                 | The cards inside a directory.                                                                                               |

## Behaviours worth knowing

* **The server owns `uid`.** A client-supplied `uid` on create is rejected (`invalidProperties`) — the server mints it, which is what makes the CardDAV mapping unambiguous.
* **`addressBookIds` is optional on create**: an omitted membership files the card into the default address book.
* **Version history is first-class.** `ContactCard/getHistory` lists prior versions of a card, `restoreVersion` brings one back — edits from any protocol (JMAP or CardDAV) are versioned the same way.
* **Round-trip is lossless.** Name and address components map completely between vCard (3.0/4.0) and JSContact in both directions; vCard properties without a JSContact model are preserved rather than dropped. A CardDAV `PUT` that would duplicate an existing UID from another source is refused, never silently forked.
* **Deprecation is advertised honestly.** The account capability object announces the AddressBook-model posture so clients can adapt, rather than discovering behaviour by trial.

## Tenant contact directories

An organization can publish shared contact sets — "Clients", "Staff" — maintained on the admin plane. The design choice that shapes the whole surface: **a directory is not a container members file cards into**. Members never see a container at all.

What a member sees is their own address book, where the entries arrive as read-only cards with `source: "organization"` and `directorySource: "tenant:<directoryId>"` — the same plane an external LDAP or SCIM connector feeds. A client that already renders organization-sourced contacts needs no new screen to show them.

**Who may write what.** Directory objects themselves are gated on the tenant-admin role. Entries are gated on that directory's `editorIds`, which are principal ids: naming a group is valid and normal, and membership is resolved through the grantee expansion at the access check rather than stored expanded, so adding someone to a group grants them editing without touching the directory.

| Object                  | Properties                                                                                                                                                             |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ContactDirectory`      | `id`, `name`, `description`, `editorIds`, `entryCount` (counted server-side at read), `updatedAt`, `mayEdit`                                                           |
| `ContactDirectoryEntry` | `id`, `directoryId`, `card` (the JSContact card verbatim — one property, not a sub-projection surface), `updatedAt`, `updatedBy` (the principal id of the last editor) |

Two behaviours worth knowing before you build on it:

* **`mayEdit` is computed for you, and you cannot compute it yourself.** It follows the caller's tenant role plus the server-side group expansion, so two accounts legitimately read different values for the same directory — and deriving it from `editorIds` is impossible, since that list is blind to group membership by design. This is exactly why it is declared in [the published canon](./jmap-v2#the-projection-canon) rather than left for a client to infer from the TypeScript type.
* **The server's bookkeeping is not yours to write.** `card` holds the JSContact card and nothing else: projection keys such as `source` or `directoryId` inside it are refused, not quietly overwritten.

Directory content is organization data, not personal data held on behalf of a member: it is purged by deleting entries, and it does not travel with an individual account's erasure.

Strictness rules are identical to the [mail surface](./jmap-mail): unknown ids in `notFound`, `invalidProperties` over coercion, `accountNotFound`, `stateMismatch`.
