# API : contacts

10 opérations REST du groupe contacts.

Base : `/api/v1`. [Conventions](/documentation/api-conventions/) · [Toutes les ressources](/documentation/api-reference/)

- GET /contacts : Lister les contacts (hors corbeille)
- POST /contacts : Créer un contact (affiliation initiale optionnelle, transactionnelle)
- GET /contacts/{id} : Détail d'un contact
- PATCH /contacts/{id} : Modifier un contact (patch partiel - champs omis intacts)
- DELETE /contacts/{id} : Mettre à la corbeille (soft delete T-261)
- PUT /contacts/{id}/external-refs/{source} : Poser l'identifiant d'une source sur un contact (idempotent)
- DELETE /contacts/{id}/external-refs/{source} : Retirer l'identifiant d'une source sur un contact
- POST /contacts/{id}/tags : Poser des étiquettes sur un contact
- GET /contacts/{id}/marketing-consent : Le consentement marketing d'un contact (canal email)
- PUT /contacts/{id}/marketing-consent : Écrire le consentement marketing d'un contact, preuve obligatoire
## GET /contacts

```json
{
  "summary": "Lister les contacts (hors corbeille)",
  "tags": [
    "Contacts"
  ],
  "parameters": [
    {
      "name": "cursor",
      "in": "query",
      "required": false,
      "schema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "description": "Curseur opaque de la page précédente",
        "type": "string"
      }
    },
    {
      "name": "limit",
      "in": "query",
      "required": false,
      "schema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "description": "Taille de page - défaut 50, max 200 (clampé)",
        "type": "integer",
        "minimum": 1,
        "maximum": 9007199254740991
      }
    },
    {
      "name": "updatedSince",
      "in": "query",
      "required": false,
      "schema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "description": "ISO 8601 - objets modifiés depuis (inclusif), le filtre des syncs incrémentales",
        "type": "string"
      }
    },
    {
      "name": "ids",
      "in": "query",
      "required": false,
      "schema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "description": "Répétable - restriction à une liste d'ids (max 200)",
        "type": "string"
      }
    },
    {
      "name": "q",
      "in": "query",
      "required": false,
      "schema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "description": "Recherche sur prénom, nom ou email",
        "type": "string"
      }
    },
    {
      "name": "externalId",
      "in": "query",
      "required": false,
      "schema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "description": "Objets qui portent cet identifiant externe (exact) - avec `externalSource` pour une source précise",
        "type": "string"
      }
    },
    {
      "name": "externalSource",
      "in": "query",
      "required": false,
      "schema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "description": "Source de l'identifiant externe (`tacto`…)",
        "type": "string"
      }
    }
  ],
  "security": [
    {
      "oauth2": [
        "contacts:read"
      ]
    },
    {
      "userSession": []
    }
  ],
  "responses": {
    "200": {
      "description": "Page de contacts",
      "content": {
        "application/json": {
          "schema": {
            "$schema": "https://json-schema.org/draft/2020-12/schema",
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "salutation": {
                      "anyOf": [
                        {
                          "type": "string",
                          "enum": [
                            "mr",
                            "mrs"
                          ]
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "firstName": {
                      "type": "string"
                    },
                    "lastName": {
                      "type": "string"
                    },
                    "email": {
                      "description": "Adresse PRINCIPALE du contact (T-809 : multi-emails - la fiche peut porter d'autres adresses, ce champ reste le miroir de la principale).",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "phone": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "title": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "avatar": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "linkedin": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "birthday": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                        },
                        {
                          "type": "null"
                        }
                      ],
                      "description": "Date de naissance « YYYY-MM-DD », null = inconnue"
                    },
                    "timeZone": {
                      "description": "Fuseau IANA explicitement connu ; null = inconnu, jamais déduit du pays",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "addressLine1": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "addressLine2": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "postalCode": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "city": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "country": {
                      "description": "Code pays ISO 3166-1 alpha-2",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "customFields": {
                      "type": "object",
                      "propertyNames": {
                        "type": "string"
                      },
                      "additionalProperties": {
                        "type": [
                          "string",
                          "number",
                          "boolean"
                        ]
                      }
                    },
                    "doNotContact": {
                      "type": "boolean",
                      "description": "Opposition RGPD : le contact ne doit pas être sollicité (E183 #514, badge « Ne pas contacter » à côté de l'email)"
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                    },
                    "updatedAt": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                    },
                    "externalRefs": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "source": {
                            "type": "string",
                            "description": "Système source (minuscules) - `tacto`, `erp`…"
                          },
                          "externalId": {
                            "type": "string"
                          },
                          "sourceUpdatedAt": {
                            "anyOf": [
                              {
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "description": "Dernière modification connue dans la source (curseur du delta), null si inconnue"
                          }
                        },
                        "required": [
                          "source",
                          "externalId",
                          "sourceUpdatedAt"
                        ],
                        "additionalProperties": false
                      },
                      "description": "Identifiants de cet objet dans les systèmes tiers (external_ref, ADR-138 D1, ADR-156 D6)"
                    }
                  },
                  "required": [
                    "id",
                    "salutation",
                    "firstName",
                    "lastName",
                    "email",
                    "phone",
                    "title",
                    "avatar",
                    "linkedin",
                    "birthday",
                    "timeZone",
                    "addressLine1",
                    "addressLine2",
                    "postalCode",
                    "city",
                    "country",
                    "customFields",
                    "doNotContact",
                    "createdAt",
                    "updatedAt"
                  ],
                  "additionalProperties": false
                }
              },
              "nextCursor": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "data",
              "nextCursor"
            ],
            "additionalProperties": false
          }
        }
      }
    },
    "default": {
      "description": "Erreur - enveloppe problem+json (RFC 9457)",
      "content": {
        "application/problem+json": {
          "schema": {
            "$ref": "#/components/schemas/Problem"
          }
        }
      }
    }
  }
}
```

## POST /contacts

```json
{
  "summary": "Créer un contact (affiliation initiale optionnelle, transactionnelle)",
  "description": "Dédup T-257 : fort = même email (409 en mode block), sinon `duplicateWarning`. companyId + role créent l'affiliation initiale dans la même transaction.",
  "tags": [
    "Contacts"
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "type": "object",
          "properties": {
            "salutation": {
              "anyOf": [
                {
                  "type": "string",
                  "const": ""
                },
                {
                  "type": "string",
                  "enum": [
                    "mr",
                    "mrs"
                  ]
                }
              ]
            },
            "firstName": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "lastName": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "email": {
              "type": "string",
              "format": "email",
              "pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
            },
            "phone": {
              "anyOf": [
                {
                  "type": "null"
                },
                {
                  "type": "string",
                  "maxLength": 30
                }
              ]
            },
            "title": {
              "anyOf": [
                {
                  "type": "null"
                },
                {
                  "type": "string",
                  "maxLength": 120
                }
              ]
            },
            "linkedin": {
              "type": [
                "null",
                "string"
              ]
            },
            "birthday": {
              "description": "Date de naissance « YYYY-MM-DD », jamais future ; null = inconnue",
              "type": [
                "null",
                "string"
              ]
            },
            "timeZone": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 100
                },
                {
                  "type": "null"
                }
              ]
            },
            "addressLine1": {
              "anyOf": [
                {
                  "type": "null"
                },
                {
                  "type": "string",
                  "maxLength": 160
                }
              ]
            },
            "addressLine2": {
              "anyOf": [
                {
                  "type": "null"
                },
                {
                  "type": "string",
                  "maxLength": 160
                }
              ]
            },
            "postalCode": {
              "anyOf": [
                {
                  "type": "null"
                },
                {
                  "type": "string",
                  "maxLength": 16
                }
              ]
            },
            "city": {
              "anyOf": [
                {
                  "type": "null"
                },
                {
                  "type": "string",
                  "maxLength": 120
                }
              ]
            },
            "country": {
              "description": "Code pays ISO 3166-1 alpha-2",
              "type": [
                "null",
                "string"
              ]
            },
            "companyId": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid",
                  "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                },
                {
                  "type": "null"
                }
              ]
            },
            "role": {
              "type": "string",
              "enum": [
                "principal",
                "operational",
                "billing",
                "other"
              ]
            },
            "startsOn": {
              "type": "string",
              "format": "date",
              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
            },
            "customFields": {
              "type": "object",
              "propertyNames": {
                "type": "string",
                "maxLength": 80
              },
              "additionalProperties": {
                "anyOf": [
                  {
                    "type": "string",
                    "maxLength": 2000
                  },
                  {
                    "type": "number"
                  },
                  {
                    "type": "boolean"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          },
          "required": [
            "firstName",
            "lastName",
            "email"
          ],
          "additionalProperties": false
        }
      }
    }
  },
  "security": [
    {
      "oauth2": [
        "contacts:write"
      ]
    },
    {
      "userSession": []
    }
  ],
  "responses": {
    "201": {
      "description": "Contact créé",
      "content": {
        "application/json": {
          "schema": {
            "$schema": "https://json-schema.org/draft/2020-12/schema",
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
              },
              "salutation": {
                "anyOf": [
                  {
                    "type": "string",
                    "enum": [
                      "mr",
                      "mrs"
                    ]
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "firstName": {
                "type": "string"
              },
              "lastName": {
                "type": "string"
              },
              "email": {
                "description": "Adresse PRINCIPALE du contact (T-809 : multi-emails - la fiche peut porter d'autres adresses, ce champ reste le miroir de la principale).",
                "type": [
                  "string",
                  "null"
                ]
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "title": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "avatar": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "linkedin": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "birthday": {
                "anyOf": [
                  {
                    "type": "string",
                    "format": "date",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Date de naissance « YYYY-MM-DD », null = inconnue"
              },
              "timeZone": {
                "description": "Fuseau IANA explicitement connu ; null = inconnu, jamais déduit du pays",
                "type": [
                  "string",
                  "null"
                ]
              },
              "addressLine1": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "addressLine2": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "postalCode": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "city": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "country": {
                "description": "Code pays ISO 3166-1 alpha-2",
                "type": [
                  "string",
                  "null"
                ]
              },
              "customFields": {
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": [
                    "string",
                    "number",
                    "boolean"
                  ]
                }
              },
              "doNotContact": {
                "type": "boolean",
                "description": "Opposition RGPD : le contact ne doit pas être sollicité (E183 #514, badge « Ne pas contacter » à côté de l'email)"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
              },
              "externalRefs": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "source": {
                      "type": "string",
                      "description": "Système source (minuscules) - `tacto`, `erp`…"
                    },
                    "externalId": {
                      "type": "string"
                    },
                    "sourceUpdatedAt": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                        },
                        {
                          "type": "null"
                        }
                      ],
                      "description": "Dernière modification connue dans la source (curseur du delta), null si inconnue"
                    }
                  },
                  "required": [
                    "source",
                    "externalId",
                    "sourceUpdatedAt"
                  ],
                  "additionalProperties": false
                },
                "description": "Identifiants de cet objet dans les systèmes tiers (external_ref, ADR-138 D1, ADR-156 D6)"
              },
              "duplicateWarning": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid",
                    "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                  },
                  "name": {
                    "type": "string"
                  },
                  "severity": {
                    "type": "string",
                    "enum": [
                      "strong",
                      "medium"
                    ]
                  }
                },
                "required": [
                  "id",
                  "name",
                  "severity"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "id",
              "salutation",
              "firstName",
              "lastName",
              "email",
              "phone",
              "title",
              "avatar",
              "linkedin",
              "birthday",
              "timeZone",
              "addressLine1",
              "addressLine2",
              "postalCode",
              "city",
              "country",
              "customFields",
              "doNotContact",
              "createdAt",
              "updatedAt"
            ],
            "additionalProperties": false
          }
        }
      }
    },
    "default": {
      "description": "Erreur - enveloppe problem+json (RFC 9457)",
      "content": {
        "application/problem+json": {
          "schema": {
            "$ref": "#/components/schemas/Problem"
          }
        }
      }
    }
  }
}
```

## GET /contacts/{id}

```json
{
  "summary": "Détail d'un contact",
  "description": "`include` (répétable - T-687, fiche mobile) : affiliations (sociétés hors visibilité masquées), activity, notes (répond sous `noteEntries`, nom historique gardé pour les clients existants). Absent = réponse historique inchangée.",
  "tags": [
    "Contacts"
  ],
  "parameters": [
    {
      "name": "include",
      "in": "query",
      "required": false,
      "schema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "description": "Bloc à embarquer (répétable) - fiche en un appel",
        "type": "string",
        "enum": [
          "affiliations",
          "activity",
          "notes"
        ]
      }
    }
  ],
  "security": [
    {
      "oauth2": [
        "contacts:read"
      ]
    },
    {
      "userSession": []
    }
  ],
  "responses": {
    "200": {
      "description": "Contact (+ blocs demandés)",
      "content": {
        "application/json": {
          "schema": {
            "$schema": "https://json-schema.org/draft/2020-12/schema",
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
              },
              "salutation": {
                "anyOf": [
                  {
                    "type": "string",
                    "enum": [
                      "mr",
                      "mrs"
                    ]
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "firstName": {
                "type": "string"
              },
              "lastName": {
                "type": "string"
              },
              "email": {
                "description": "Adresse PRINCIPALE du contact (T-809 : multi-emails - la fiche peut porter d'autres adresses, ce champ reste le miroir de la principale).",
                "type": [
                  "string",
                  "null"
                ]
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "title": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "avatar": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "linkedin": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "birthday": {
                "anyOf": [
                  {
                    "type": "string",
                    "format": "date",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Date de naissance « YYYY-MM-DD », null = inconnue"
              },
              "timeZone": {
                "description": "Fuseau IANA explicitement connu ; null = inconnu, jamais déduit du pays",
                "type": [
                  "string",
                  "null"
                ]
              },
              "addressLine1": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "addressLine2": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "postalCode": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "city": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "country": {
                "description": "Code pays ISO 3166-1 alpha-2",
                "type": [
                  "string",
                  "null"
                ]
              },
              "customFields": {
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": [
                    "string",
                    "number",
                    "boolean"
                  ]
                }
              },
              "doNotContact": {
                "type": "boolean",
                "description": "Opposition RGPD : le contact ne doit pas être sollicité (E183 #514, badge « Ne pas contacter » à côté de l'email)"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
              },
              "externalRefs": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "source": {
                      "type": "string",
                      "description": "Système source (minuscules) - `tacto`, `erp`…"
                    },
                    "externalId": {
                      "type": "string"
                    },
                    "sourceUpdatedAt": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                        },
                        {
                          "type": "null"
                        }
                      ],
                      "description": "Dernière modification connue dans la source (curseur du delta), null si inconnue"
                    }
                  },
                  "required": [
                    "source",
                    "externalId",
                    "sourceUpdatedAt"
                  ],
                  "additionalProperties": false
                },
                "description": "Identifiants de cet objet dans les systèmes tiers (external_ref, ADR-138 D1, ADR-156 D6)"
              },
              "affiliations": {
                "description": "Bloc `include=affiliations` - sociétés hors visibilité masquées",
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "affiliationId": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "companyId": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "companyName": {
                      "type": "string"
                    },
                    "role": {
                      "type": "string",
                      "enum": [
                        "principal",
                        "operational",
                        "billing",
                        "other"
                      ]
                    },
                    "startsOn": {
                      "type": "string",
                      "format": "date",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                    },
                    "endsOn": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "affiliationId",
                    "companyId",
                    "companyName",
                    "role",
                    "startsOn",
                    "endsOn"
                  ],
                  "additionalProperties": false
                }
              },
              "activity": {
                "description": "Bloc `include=activity` - fil d'activité, 20 derniers",
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "kind": {
                      "type": "string",
                      "enum": [
                        "quote_created",
                        "quote_sent",
                        "quote_signed",
                        "invoice_issued",
                        "invoice_paid",
                        "invoice_late",
                        "mail_in",
                        "mail_out",
                        "note",
                        "record_updated",
                        "call_logged",
                        "meeting_held",
                        "task"
                      ]
                    },
                    "occurredAt": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                    },
                    "ref": {
                      "description": "N° du devis ou de la facture concerné",
                      "type": "string"
                    },
                    "quoteId": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "amount": {
                      "type": "number"
                    },
                    "note": {
                      "type": "string"
                    },
                    "title": {
                      "description": "Titre de l'activité commerciale",
                      "type": "string"
                    },
                    "outcome": {
                      "type": "string",
                      "enum": [
                        "done",
                        "cancelled"
                      ]
                    },
                    "author": {
                      "type": "string"
                    },
                    "mail": {
                      "type": "object",
                      "properties": {
                        "subject": {
                          "type": "string"
                        },
                        "from": {
                          "type": "string"
                        },
                        "to": {
                          "type": "string"
                        },
                        "preview": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "subject",
                        "from",
                        "to",
                        "preview"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "kind",
                    "occurredAt"
                  ],
                  "additionalProperties": false
                }
              },
              "noteEntries": {
                "description": "Bloc `include=notes` - notes de la fiche (même forme que /comments) ; répond sous `noteEntries` (nom historique, gardé pour les clients existants)",
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "authorId": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "authorName": {
                      "type": "string"
                    },
                    "authorAvatar": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "body": {
                      "type": "string"
                    },
                    "createdAt": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "authorId",
                    "authorName",
                    "authorAvatar",
                    "body",
                    "createdAt"
                  ],
                  "additionalProperties": false
                }
              }
            },
            "required": [
              "id",
              "salutation",
              "firstName",
              "lastName",
              "email",
              "phone",
              "title",
              "avatar",
              "linkedin",
              "birthday",
              "timeZone",
              "addressLine1",
              "addressLine2",
              "postalCode",
              "city",
              "country",
              "customFields",
              "doNotContact",
              "createdAt",
              "updatedAt"
            ],
            "additionalProperties": false
          }
        }
      }
    },
    "default": {
      "description": "Erreur - enveloppe problem+json (RFC 9457)",
      "content": {
        "application/problem+json": {
          "schema": {
            "$ref": "#/components/schemas/Problem"
          }
        }
      }
    }
  }
}
```

## PATCH /contacts/{id}

```json
{
  "summary": "Modifier un contact (patch partiel - champs omis intacts)",
  "tags": [
    "Contacts"
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "type": "object",
          "properties": {
            "salutation": {
              "anyOf": [
                {
                  "type": "string",
                  "const": ""
                },
                {
                  "type": "string",
                  "enum": [
                    "mr",
                    "mrs"
                  ]
                }
              ]
            },
            "firstName": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "lastName": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "email": {
              "type": "string",
              "format": "email",
              "pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
            },
            "phone": {
              "anyOf": [
                {
                  "type": "null"
                },
                {
                  "type": "string",
                  "maxLength": 30
                }
              ]
            },
            "title": {
              "anyOf": [
                {
                  "type": "null"
                },
                {
                  "type": "string",
                  "maxLength": 120
                }
              ]
            },
            "linkedin": {
              "type": [
                "null",
                "string"
              ]
            },
            "birthday": {
              "description": "Date de naissance « YYYY-MM-DD », jamais future ; null = inconnue",
              "type": [
                "null",
                "string"
              ]
            },
            "timeZone": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 100
                },
                {
                  "type": "null"
                }
              ]
            },
            "addressLine1": {
              "anyOf": [
                {
                  "type": "null"
                },
                {
                  "type": "string",
                  "maxLength": 160
                }
              ]
            },
            "addressLine2": {
              "anyOf": [
                {
                  "type": "null"
                },
                {
                  "type": "string",
                  "maxLength": 160
                }
              ]
            },
            "postalCode": {
              "anyOf": [
                {
                  "type": "null"
                },
                {
                  "type": "string",
                  "maxLength": 16
                }
              ]
            },
            "city": {
              "anyOf": [
                {
                  "type": "null"
                },
                {
                  "type": "string",
                  "maxLength": 120
                }
              ]
            },
            "country": {
              "description": "Code pays ISO 3166-1 alpha-2",
              "type": [
                "null",
                "string"
              ]
            },
            "customFields": {
              "type": "object",
              "propertyNames": {
                "type": "string",
                "maxLength": 80
              },
              "additionalProperties": {
                "anyOf": [
                  {
                    "type": "string",
                    "maxLength": 2000
                  },
                  {
                    "type": "number"
                  },
                  {
                    "type": "boolean"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          },
          "additionalProperties": false
        }
      }
    }
  },
  "security": [
    {
      "oauth2": [
        "contacts:write"
      ]
    },
    {
      "userSession": []
    }
  ],
  "responses": {
    "200": {
      "description": "Contact modifié",
      "content": {
        "application/json": {
          "schema": {
            "$schema": "https://json-schema.org/draft/2020-12/schema",
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
              },
              "salutation": {
                "anyOf": [
                  {
                    "type": "string",
                    "enum": [
                      "mr",
                      "mrs"
                    ]
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "firstName": {
                "type": "string"
              },
              "lastName": {
                "type": "string"
              },
              "email": {
                "description": "Adresse PRINCIPALE du contact (T-809 : multi-emails - la fiche peut porter d'autres adresses, ce champ reste le miroir de la principale).",
                "type": [
                  "string",
                  "null"
                ]
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "title": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "avatar": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "linkedin": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "birthday": {
                "anyOf": [
                  {
                    "type": "string",
                    "format": "date",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Date de naissance « YYYY-MM-DD », null = inconnue"
              },
              "timeZone": {
                "description": "Fuseau IANA explicitement connu ; null = inconnu, jamais déduit du pays",
                "type": [
                  "string",
                  "null"
                ]
              },
              "addressLine1": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "addressLine2": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "postalCode": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "city": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "country": {
                "description": "Code pays ISO 3166-1 alpha-2",
                "type": [
                  "string",
                  "null"
                ]
              },
              "customFields": {
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": [
                    "string",
                    "number",
                    "boolean"
                  ]
                }
              },
              "doNotContact": {
                "type": "boolean",
                "description": "Opposition RGPD : le contact ne doit pas être sollicité (E183 #514, badge « Ne pas contacter » à côté de l'email)"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
              },
              "externalRefs": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "source": {
                      "type": "string",
                      "description": "Système source (minuscules) - `tacto`, `erp`…"
                    },
                    "externalId": {
                      "type": "string"
                    },
                    "sourceUpdatedAt": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                        },
                        {
                          "type": "null"
                        }
                      ],
                      "description": "Dernière modification connue dans la source (curseur du delta), null si inconnue"
                    }
                  },
                  "required": [
                    "source",
                    "externalId",
                    "sourceUpdatedAt"
                  ],
                  "additionalProperties": false
                },
                "description": "Identifiants de cet objet dans les systèmes tiers (external_ref, ADR-138 D1, ADR-156 D6)"
              },
              "duplicateWarning": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid",
                    "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                  },
                  "name": {
                    "type": "string"
                  },
                  "severity": {
                    "type": "string",
                    "enum": [
                      "strong",
                      "medium"
                    ]
                  }
                },
                "required": [
                  "id",
                  "name",
                  "severity"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "id",
              "salutation",
              "firstName",
              "lastName",
              "email",
              "phone",
              "title",
              "avatar",
              "linkedin",
              "birthday",
              "timeZone",
              "addressLine1",
              "addressLine2",
              "postalCode",
              "city",
              "country",
              "customFields",
              "doNotContact",
              "createdAt",
              "updatedAt"
            ],
            "additionalProperties": false
          }
        }
      }
    },
    "default": {
      "description": "Erreur - enveloppe problem+json (RFC 9457)",
      "content": {
        "application/problem+json": {
          "schema": {
            "$ref": "#/components/schemas/Problem"
          }
        }
      }
    }
  }
}
```

## DELETE /contacts/{id}

```json
{
  "summary": "Mettre à la corbeille (soft delete T-261)",
  "description": "Refusé (409) si des devis ou factures référencent le contact.",
  "tags": [
    "Contacts"
  ],
  "security": [
    {
      "oauth2": [
        "contacts:write"
      ]
    },
    {
      "userSession": []
    }
  ],
  "responses": {
    "200": {
      "description": "Contact mis à la corbeille",
      "content": {
        "application/json": {
          "schema": {
            "$schema": "https://json-schema.org/draft/2020-12/schema",
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
              },
              "deleted": {
                "type": "boolean",
                "const": true
              }
            },
            "required": [
              "id",
              "deleted"
            ],
            "additionalProperties": false
          }
        }
      }
    },
    "default": {
      "description": "Erreur - enveloppe problem+json (RFC 9457)",
      "content": {
        "application/problem+json": {
          "schema": {
            "$ref": "#/components/schemas/Problem"
          }
        }
      }
    }
  }
}
```

## PUT /contacts/{id}/external-refs/{source}

```json
{
  "summary": "Poser l'identifiant d'une source sur un contact (idempotent)",
  "description": "Une source, un identifiant par objet : la même écriture deux fois ne crée qu'une ligne ; un identifiant déjà porté par un AUTRE objet de la même source répond 409 avec l'objet porteur (jamais déplacé en silence). La source `salesforce` est réservée aux scripts de reprise (403). Même garde que le PATCH de l'objet : hors visibilité 404, hors périmètre d'écriture 403.",
  "tags": [
    "Contacts"
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "type": "object",
          "properties": {
            "externalId": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "sourceUpdatedAt": {
              "description": "Dernière modification dans la source - curseur du delta",
              "anyOf": [
                {
                  "type": "string",
                  "format": "date-time",
                  "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
                },
                {
                  "type": "string",
                  "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
                }
              ]
            }
          },
          "required": [
            "externalId"
          ],
          "additionalProperties": false
        }
      }
    }
  },
  "security": [
    {
      "oauth2": [
        "contacts:write"
      ]
    },
    {
      "userSession": []
    }
  ],
  "responses": {
    "200": {
      "description": "Identifiants de l'objet après écriture",
      "content": {
        "application/json": {
          "schema": {
            "$schema": "https://json-schema.org/draft/2020-12/schema",
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                "description": "Objet porteur"
              },
              "externalRefs": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "source": {
                      "type": "string",
                      "description": "Système source (minuscules) - `tacto`, `erp`…"
                    },
                    "externalId": {
                      "type": "string"
                    },
                    "sourceUpdatedAt": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                        },
                        {
                          "type": "null"
                        }
                      ],
                      "description": "Dernière modification connue dans la source (curseur du delta), null si inconnue"
                    }
                  },
                  "required": [
                    "source",
                    "externalId",
                    "sourceUpdatedAt"
                  ],
                  "additionalProperties": false
                },
                "description": "Identifiants de cet objet dans les systèmes tiers (external_ref, ADR-138 D1, ADR-156 D6)"
              }
            },
            "required": [
              "id",
              "externalRefs"
            ],
            "additionalProperties": false
          }
        }
      }
    },
    "default": {
      "description": "Erreur - enveloppe problem+json (RFC 9457)",
      "content": {
        "application/problem+json": {
          "schema": {
            "$ref": "#/components/schemas/Problem"
          }
        }
      }
    }
  }
}
```

## DELETE /contacts/{id}/external-refs/{source}

```json
{
  "summary": "Retirer l'identifiant d'une source sur un contact",
  "tags": [
    "Contacts"
  ],
  "security": [
    {
      "oauth2": [
        "contacts:write"
      ]
    },
    {
      "userSession": []
    }
  ],
  "responses": {
    "200": {
      "description": "Identifiants restants",
      "content": {
        "application/json": {
          "schema": {
            "$schema": "https://json-schema.org/draft/2020-12/schema",
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                "description": "Objet porteur"
              },
              "externalRefs": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "source": {
                      "type": "string",
                      "description": "Système source (minuscules) - `tacto`, `erp`…"
                    },
                    "externalId": {
                      "type": "string"
                    },
                    "sourceUpdatedAt": {
                      "anyOf": [
                        {
                          "type": "string",
                          "format": "date-time",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                        },
                        {
                          "type": "null"
                        }
                      ],
                      "description": "Dernière modification connue dans la source (curseur du delta), null si inconnue"
                    }
                  },
                  "required": [
                    "source",
                    "externalId",
                    "sourceUpdatedAt"
                  ],
                  "additionalProperties": false
                },
                "description": "Identifiants de cet objet dans les systèmes tiers (external_ref, ADR-138 D1, ADR-156 D6)"
              },
              "removed": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991,
                "description": "Références retirées (0 si la source n'en portait pas)"
              }
            },
            "required": [
              "id",
              "externalRefs",
              "removed"
            ],
            "additionalProperties": false
          }
        }
      }
    },
    "default": {
      "description": "Erreur - enveloppe problem+json (RFC 9457)",
      "content": {
        "application/problem+json": {
          "schema": {
            "$ref": "#/components/schemas/Problem"
          }
        }
      }
    }
  }
}
```

## POST /contacts/{id}/tags

```json
{
  "summary": "Poser des étiquettes sur un contact",
  "description": "`tagIds` existantes et/ou `names` (créées si absentes, quota du plan). Idempotent : une étiquette déjà posée ne compte pas.",
  "tags": [
    "Marketing"
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "type": "object",
          "properties": {
            "tagIds": {
              "maxItems": 50,
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
              }
            },
            "names": {
              "description": "Créées si absentes (quota du plan)",
              "maxItems": 20,
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1,
                "maxLength": 40
              }
            }
          },
          "additionalProperties": false
        }
      }
    }
  },
  "security": [
    {
      "oauth2": [
        "marketing:write",
        "contacts:read"
      ]
    },
    {
      "userSession": []
    }
  ],
  "responses": {
    "200": {
      "description": "Étiquettes posées",
      "content": {
        "application/json": {
          "schema": {
            "$schema": "https://json-schema.org/draft/2020-12/schema",
            "type": "object",
            "properties": {
              "added": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              },
              "tags": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "name": {
                      "type": "string"
                    },
                    "color": {
                      "type": "string"
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                    },
                    "updatedAt": {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "color",
                    "createdAt",
                    "updatedAt"
                  ],
                  "additionalProperties": false
                }
              }
            },
            "required": [
              "added",
              "tags"
            ],
            "additionalProperties": false
          }
        }
      }
    },
    "default": {
      "description": "Erreur - enveloppe problem+json (RFC 9457)",
      "content": {
        "application/problem+json": {
          "schema": {
            "$ref": "#/components/schemas/Problem"
          }
        }
      }
    }
  }
}
```

## GET /contacts/{id}/marketing-consent

```json
{
  "summary": "Le consentement marketing d'un contact (canal email)",
  "tags": [
    "Marketing"
  ],
  "security": [
    {
      "oauth2": [
        "marketing:read",
        "contacts:read"
      ]
    },
    {
      "userSession": []
    }
  ],
  "responses": {
    "200": {
      "description": "Consentement",
      "content": {
        "application/json": {
          "schema": {
            "$schema": "https://json-schema.org/draft/2020-12/schema",
            "type": "object",
            "properties": {
              "contactId": {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
              },
              "channel": {
                "type": "string",
                "const": "email"
              },
              "status": {
                "type": "string",
                "enum": [
                  "opted_in",
                  "opt_out_allowed",
                  "opted_out",
                  "blocked"
                ]
              },
              "basis": {
                "type": "string",
                "enum": [
                  "consent",
                  "legitimate_interest"
                ]
              },
              "source": {
                "type": "string"
              },
              "proof": {
                "anyOf": [
                  {
                    "type": "object",
                    "properties": {
                      "text": {
                        "type": "string"
                      },
                      "version": {
                        "type": "string"
                      },
                      "at": {
                        "type": "string"
                      },
                      "note": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "text",
                      "version",
                      "at"
                    ],
                    "additionalProperties": false
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "measureConsentAt": {
                "anyOf": [
                  {
                    "type": "string",
                    "format": "date-time",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "optedOutAt": {
                "anyOf": [
                  {
                    "type": "string",
                    "format": "date-time",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
              }
            },
            "required": [
              "contactId",
              "channel",
              "status",
              "basis",
              "source",
              "proof",
              "measureConsentAt",
              "optedOutAt",
              "updatedAt"
            ],
            "additionalProperties": false
          }
        }
      }
    },
    "default": {
      "description": "Erreur - enveloppe problem+json (RFC 9457)",
      "content": {
        "application/problem+json": {
          "schema": {
            "$ref": "#/components/schemas/Problem"
          }
        }
      }
    }
  }
}
```

## PUT /contacts/{id}/marketing-consent

```json
{
  "summary": "Écrire le consentement marketing d'un contact, preuve obligatoire",
  "description": "`opted_in` exige la preuve (texte, version, note : où le consentement a été recueilli) ; refusé 409 sur un contact en opposition ou sur la liste de suppression. `opted_out` désinscrit. Source journalisée « preuve saisie », acteur = l'utilisateur d'intégration. `measure` pose ou retire le consentement à la mesure nominative.",
  "tags": [
    "Marketing"
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "type": "object",
          "properties": {
            "status": {
              "type": "string",
              "enum": [
                "opted_in",
                "opted_out"
              ]
            },
            "proof": {
              "type": "object",
              "properties": {
                "text": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 2000,
                  "description": "Texte de consentement montré à la personne"
                },
                "version": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 40
                },
                "at": {
                  "description": "Instant du geste de la personne (défaut : maintenant)",
                  "anyOf": [
                    {
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                    },
                    {
                      "type": "string",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?:Z))$"
                    }
                  ]
                },
                "note": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 500,
                  "description": "Où et comment le consentement a été recueilli (obligatoire)"
                }
              },
              "required": [
                "text",
                "version",
                "note"
              ],
              "additionalProperties": false
            },
            "measure": {
              "description": "Consentement à la mesure nominative des clics et ouvertures",
              "type": "boolean"
            }
          },
          "required": [
            "status",
            "proof"
          ],
          "additionalProperties": false
        }
      }
    }
  },
  "security": [
    {
      "oauth2": [
        "marketing:write",
        "contacts:read"
      ]
    },
    {
      "userSession": []
    }
  ],
  "responses": {
    "200": {
      "description": "Consentement après écriture",
      "content": {
        "application/json": {
          "schema": {
            "$schema": "https://json-schema.org/draft/2020-12/schema",
            "type": "object",
            "properties": {
              "contactId": {
                "type": "string",
                "format": "uuid",
                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
              },
              "channel": {
                "type": "string",
                "const": "email"
              },
              "status": {
                "type": "string",
                "enum": [
                  "opted_in",
                  "opt_out_allowed",
                  "opted_out",
                  "blocked"
                ]
              },
              "basis": {
                "type": "string",
                "enum": [
                  "consent",
                  "legitimate_interest"
                ]
              },
              "source": {
                "type": "string"
              },
              "proof": {
                "anyOf": [
                  {
                    "type": "object",
                    "properties": {
                      "text": {
                        "type": "string"
                      },
                      "version": {
                        "type": "string"
                      },
                      "at": {
                        "type": "string"
                      },
                      "note": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "text",
                      "version",
                      "at"
                    ],
                    "additionalProperties": false
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "measureConsentAt": {
                "anyOf": [
                  {
                    "type": "string",
                    "format": "date-time",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "optedOutAt": {
                "anyOf": [
                  {
                    "type": "string",
                    "format": "date-time",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
              }
            },
            "required": [
              "contactId",
              "channel",
              "status",
              "basis",
              "source",
              "proof",
              "measureConsentAt",
              "optedOutAt",
              "updatedAt"
            ],
            "additionalProperties": false
          }
        }
      }
    },
    "default": {
      "description": "Erreur - enveloppe problem+json (RFC 9457)",
      "content": {
        "application/problem+json": {
          "schema": {
            "$ref": "#/components/schemas/Problem"
          }
        }
      }
    }
  }
}
```
