> ## 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

> La surface JMAP Contacts (RFC 9610) : AddressBook et ContactCard (JSContact), les groupes, la copie inter-comptes, l'historique de versions. Et les garanties d'aller-retour vCard/CardDAV.

Les contacts implémentent la [RFC 9610](https://www.rfc-editor.org/rfc/rfc9610) (`urn:ietf:params:jmap:contacts`), avec des fiches en objets JSContact ([RFC 9553](https://www.rfc-editor.org/rfc/rfc9553)). Les mêmes données sont servies en [CardDAV](../operator/legacy-protocols).

## Objets et méthodes

| Objet                      | Méthodes                                         | Notes                                                                                                                        |
| -------------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `AddressBook`              | `get`, `set`, `query`, `changes`                 | Les collections, avec droits `shareWith`.                                                                                    |
| `ContactCard`              | `get`, `set`, `query`, `queryChanges`, `changes` | Les fiches JSContact.                                                                                                        |
| `ContactCard` (extensions) | `getHistory`, `restoreVersion`, `promote`        | L'historique de versions, la restauration, et la promotion d'une adresse auto-collectée en vraie fiche.                      |
| `Contact/copy`             |                                                  | Le `/copy` de la RFC 8620 entre comptes (par exemple personnel vers carnet partagé).                                         |
| `ContactGroup`             | `get`, `set`, `query`, `changes`                 | Les groupes référençant des fiches.                                                                                          |
| `ContactDirectory`         | `get`, `set`, `changes`                          | Les annuaires partagés du tenant : voir [plus bas](#les-annuaires-de-tenant). Demande `urn:oximail:params:jmap:v2:contacts`. |
| `ContactDirectoryEntry`    | `get`, `set`, `query`, `changes`                 | Les fiches contenues dans un annuaire.                                                                                       |

## Comportements à connaître

* **Le serveur possède l'`uid`.** Un `uid` fourni par le client à la création est rejeté (`invalidProperties`) : c'est le serveur qui le frappe, ce qui rend la correspondance CardDAV sans ambiguïté.
* **`addressBookIds` est optionnel à la création** : une appartenance omise classe la fiche dans le carnet par défaut.
* **L'historique de versions est de premier rang.** `ContactCard/getHistory` liste les versions antérieures d'une fiche, `restoreVersion` en ramène une. Les modifications de n'importe quel protocole (JMAP ou CardDAV) sont versionnées pareil.
* **L'aller-retour est sans perte.** Les composantes de nom et d'adresse se transposent complètement entre vCard (3.0/4.0) et JSContact dans les deux sens ; les propriétés vCard sans modèle JSContact sont préservées plutôt qu'abandonnées. Un `PUT` CardDAV qui dupliquerait un UID existant d'une autre source est refusé, jamais dupliqué en silence.
* **La dépréciation est annoncée honnêtement.** L'objet de capacité du compte annonce la posture du modèle AddressBook pour que les clients s'adaptent, au lieu de découvrir le comportement par essais.

## Les annuaires de tenant

Une organisation peut publier des jeux de contacts partagés (« Clients », « Personnel ») tenus sur le plan d'administration. Le choix de conception qui structure toute la surface : **un annuaire n'est pas un conteneur dans lequel les membres classent des fiches**. Les membres ne voient aucun conteneur.

Ce qu'un membre voit, c'est son propre carnet d'adresses, où les entrées arrivent en fiches lisibles seulement, avec `source: "organization"` et `directorySource: "tenant:<directoryId>"` : le plan même qu'alimente un connecteur LDAP ou SCIM externe. Un client qui affiche déjà les contacts d'origine organisationnelle n'a aucun écran à ajouter.

**Qui écrit quoi.** Les objets annuaire sont réservés au rôle d'administrateur de tenant. Les entrées sont réservées aux `editorIds` de leur annuaire, qui sont des identifiants de principal : y nommer un groupe est valide et courant, et l'appartenance est résolue par l'expansion des bénéficiaires au moment du contrôle d'accès, jamais stockée déjà expansée. Ajouter quelqu'un à un groupe lui donne donc le droit d'éditer sans toucher à l'annuaire.

| Objet                   | Propriétés                                                                                                                                                                                  |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ContactDirectory`      | `id`, `name`, `description`, `editorIds`, `entryCount` (compté côté serveur à la lecture), `updatedAt`, `mayEdit`                                                                           |
| `ContactDirectoryEntry` | `id`, `directoryId`, `card` (la fiche JSContact telle quelle : une propriété, pas une surface de sous-projection), `updatedAt`, `updatedBy` (l'identifiant de principal du dernier éditeur) |

Deux comportements à connaître avant de bâtir dessus :

* **`mayEdit` est calculé pour vous, et vous ne pouvez pas le calculer vous-même.** Il suit le rôle de tenant de l'appelant et l'expansion de groupes côté serveur : deux comptes lisent donc légitimement des valeurs différentes pour le même annuaire, et le dériver depuis `editorIds` est impossible, cette liste étant aveugle à l'appartenance aux groupes par conception. C'est exactement pour cela qu'il est déclaré dans [le canon publié](./jmap-v2#le-canon-de-projection) au lieu d'être laissé à la déduction depuis le type TypeScript.
* **La comptabilité du serveur ne vous appartient pas.** La propriété `card` porte la fiche JSContact et rien d'autre : les clés de projection comme `source` ou `directoryId` posées à l'intérieur sont refusées, pas écrasées en silence.

Le contenu d'un annuaire est une donnée de l'organisation, pas une donnée personnelle détenue pour le compte d'un membre : il se purge en supprimant des entrées, et il ne suit pas l'effacement d'un compte individuel.

Les règles de rigueur sont identiques à la [surface courrier](./jmap-mail) : identifiants inconnus dans `notFound`, `invalidProperties` plutôt qu'une coercition, `accountNotFound`, `stateMismatch`.
