
# API Contacts

Un contact est une personne unique à qui vous envoyez des messages : son nom, son numéro de téléphone, son e-mail, son canal, ses tags, ses champs personnalisés, ainsi que les listes et campagnes auxquelles il appartient. L'API Contacts vous permet de créer des contacts, de les rechercher, de les mettre à jour, de les taguer, de les importer en masse et de les supprimer, le tout sans utiliser le tableau de bord.

Tous les chemins sur cette page sont relatifs à l'URL de base :

```
https://api.youraiconnector.com/v1
```

Ainsi, `/contacts` signifie `https://api.youraiconnector.com/v1/contacts`.

> **Nouveau sur l'API ?** Lisez d'abord [Accès à l'API](../integrations/api-access.md) — cela couvre la façon de générer votre clé API, les trois méthodes d'authentification, les limites de débit et le format des erreurs. Tout ce qui figure sur cette page suppose que vous disposez déjà d'une clé API fonctionnelle.

---

## À propos des identifiants de contact

Chaque contact possède un identifiant unique. L'identifiant que vous obtenez lorsque vous **créez** un contact (dans `data.contactId`) est le même que celui que vous utilisez partout ailleurs — pour récupérer, mettre à jour, taguer, envoyer un message ou supprimer ce contact. Enregistrez-le une fois et réutilisez-le.

Vous n'êtes pas obligé de créer un contact pour obtenir son identifiant. Vous pouvez également en rechercher un par numéro de téléphone ou par e-mail (voir [Obtenir un contact](#get-a-contact-by-phone-or-email)), ou parcourir tous vos contacts (voir [Lister les contacts](#list-contacts)). Chacune de ces méthodes renvoie le même identifiant.

---

## Créer un contact

`POST /contacts`

Ajoute un nouveau contact à votre compte. Un **numéro de téléphone avec l'indicatif pays est requis** — un e-mail seul ne suffit pas. Tout le reste est facultatif.

Vous pouvez éventuellement ajouter le nouveau contact directement dans une ou plusieurs listes avec `listId` (une seule liste) ou `listIds` (un tableau). Si les deux sont envoyés, `listIds` est prioritaire.

Tout champ envoyé qui ne fait pas partie des champs de création standard listés dans le tableau des champs **Créer un contact** ci-dessous (`phoneNumber`, `firstName`, `lastName`, `email`, `channel`, `is_bot_active`, `is_private`, `lead_profile`, `listId`, `listIds`, `custom_fields`) est automatiquement stocké en tant que **champ personnalisé** — ainsi, une charge utile plate provenant d'un outil comme Make ou Zapier fonctionne sans imbrication. Vous pouvez également transmettre un objet `custom_fields` explicite.

| Champ | Requis | Description |
|---|---|---|
| `phoneNumber` | Oui | Le numéro de téléphone du contact, avec l'indicatif pays (par ex. `+15551234567`). |
| `firstName` | Non | Prénom. |
| `lastName` | Non | Nom de famille. |
| `email` | Non | Adresse e-mail. |
| `channel` | Non | Canal de messagerie. L'un des suivants : `whatsapp`, `sms`, `whatsapp_web`. Par défaut : `whatsapp`. |
| `is_bot_active` | Non | Indique si l'assistant IA répond à ce contact. Par défaut : `true`. |
| `is_private` | Non | Marquer le contact comme privé. Lorsque `true`, l'assistant IA est désactivé pour lui. Par défaut : `false`. |
| `lead_profile` | Non | Notes en texte libre sur le prospect. |
| `listId` | Non | Un identifiant de liste unique pour ajouter le contact. |
| `listIds` | Non | Un tableau d'identifiants de liste pour ajouter le contact (prioritaire sur `listId`). |
| `custom_fields` | Non | Un objet contenant vos propres champs clé/valeur. Vous pouvez également les transmettre en tant que clés de premier niveau. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/contacts?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "+15551234567",
    "firstName": "Jane",
    "lastName": "Smith",
    "email": "jane@example.com",
    "is_bot_active": true,
    "listIds": ["list123", "list456"]
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/contacts", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    phoneNumber: "+15551234567",
    firstName: "Jane",
    lastName: "Smith",
    email: "jane@example.com",
    is_bot_active: true,
    listIds: ["list123", "list456"],
  }),
});
const data = await res.json();
console.log(data.data.contactId);
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/contacts",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "phoneNumber": "+15551234567",
        "firstName": "Jane",
        "lastName": "Smith",
        "email": "jane@example.com",
        "is_bot_active": True,
        "listIds": ["list123", "list456"],
    },
)
print(res.json()["data"]["contactId"])
```

**Réponse**

```json
{
  "success": true,
  "data": {
    "message": "Successfully created new contact",
    "contactId": "contact_abc123",
    "listsAdded": ["list123", "list456"]
  }
}
```

L'ID du nouveau contact se trouve dans `data.contactId`. Les listes auxquelles il a été ajouté sont renvoyées dans `data.listsAdded`.

> **Les doublons ne sont pas créés.** Si un contact avec le même numéro de téléphone existe déjà, l'appel de création ne le crée **pas** et ne le renvoie pas. La réponse renvoie un statut HTTP `200` et un `error_code` de `409` dans le corps de la réponse ; basez donc votre logique sur `error_code` plutôt que sur le statut HTTP :
>
> ```json
> { "success": false, "error_code": 409, "error": "A contact with this phone number already exists for the current user." }
> ```
>
> Pour travailler avec un contact existant après un `error_code` de `409`, recherchez-le avec [Obtenir un contact par téléphone ou e-mail](#get-a-contact-by-phone-or-email) — `GET /contacts?phoneNumber=...` — et réutilisez l'ID qu'il renvoie.

> **Les orthographes équivalentes WhatsApp comptent comme le même numéro.** Certains pays ont deux orthographes valides pour la même ligne mobile et WhatsApp peut signaler l'une ou l'autre : le Mexique (`+52…` et l'ancien `+521…`), le Brésil (avec ou sans le neuvième chiffre) et l'Argentine (avec ou sans le `9` après le `+54`). La vérification des doublons lors de la création et la correspondance `GET /contacts?phoneNumber=` fonctionnent avec les deux orthographes, vous récupérez donc le contact existant quelle que soit la forme envoyée. Le `phone_number` enregistré sur le contact n'est jamais réécrit.

---

## Obtenir un contact par téléphone ou e-mail

`GET /contacts?phoneNumber=...` ou `GET /contacts?email=...`

Recherche un contact unique et renvoie l'objet contact complet et enrichi — incluant ses listes, tags et campagnes résolus en paires `{ id, name }`, ainsi que le dernier message échangé.

Passez **soit** `phoneNumber` (au format international), **soit** `email`. Si vous ne passez aucun des deux, ce même point de terminaison bascule en mode [Lister les contacts](#list-contacts).

**cURL**

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts?phoneNumber=%2B15551234567&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const phone = encodeURIComponent("+15551234567");
const res = await fetch(`https://api.youraiconnector.com/v1/contacts?phoneNumber=${phone}`, {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.contact);
```

**Python**

```python
import requests

res = requests.get(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"phoneNumber": "+15551234567"},
)
print(res.json()["contact"])
```

**Réponse**

```json
{
  "success": true,
  "contactId": "contact_abc123",
  "contact": {
    "id": "contact_abc123",
    "firstName": "Jane",
    "lastName": "Smith",
    "email": "jane@example.com",
    "phoneNumber": "+15551234567",
    "channel": "whatsapp",
    "isBotActive": true,
    "isPrivate": false,
    "doNotDisturb": false,
    "lead_profile": null,
    "avatarUrl": "https://example.com/photo.jpg",
    "customFields": {},
    "lists": [{ "id": "list123", "name": "VIP customers" }],
    "tags": [{ "id": "tagHotLead", "name": "Hot lead" }],
    "campaigns": [{ "id": "campaign789", "name": "Spring promo" }],
    "currentCampaign": { "id": "campaign789", "name": "Spring promo" },
    "lastMessage": {
      "direction": "inbound",
      "body": "Sounds good, thanks!",
      "status": "received",
      "timestamp": "2026-06-09T10:21:00.000Z"
    }
  }
}
```

L'ID du contact est renvoyé à la fois au niveau supérieur (`contactId`) et à l'intérieur de l'objet (`contact.id`). Si aucune correspondance n'est trouvée, vous recevez un `404` avec `{ "success": false, "message": "Contact not found" }`.

> **`avatarUrl`** est la photo de profil du contact, extraite de WhatsApp ou de Meta lorsqu'il vous envoie un message. Elle est en lecture seule : vous ne pouvez pas la définir, et elle est `null` pour les contacts qui n'ont pas de photo ou qui vous contactent via un canal qui n'en partage pas. Considérez le lien comme temporaire plutôt que de le stocker, car certains de ces liens de photos expirent et sont actualisés automatiquement. (Dans le point de terminaison de liste ci-dessous, la même valeur est appelée `avatar_url`.)

> **Numéros de téléphone dans les URL.** Un signe `+` dans une chaîne de requête doit être encodé en URL sous la forme `%2B`, sinon il est lu comme un espace. Les exemples ci-dessus le font pour vous.

---

## Obtenir un contact par ID

`GET /contacts/{contactId}`

Lorsque vous disposez déjà de l'ID d'un contact, récupérez-le directement. La forme de la réponse est identique à celle de la recherche ci-dessus.

**cURL**

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.contact);
```

**Python**

```python
import requests

res = requests.get(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
print(res.json()["contact"])
```

Un ID de contact qui n'existe pas dans votre compte renvoie une `404`.

---

## Obtenir les statistiques d'un contact

`GET /contacts/{contactId}/stats`

Renvoie les statistiques globales des messages pour un contact : totaux, réponses IA vs humaines, crédits dépensés et horodatages du premier et du dernier message.

**cURL**

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/stats?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/stats", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.totalMessages, data.creditsUsed);
```

**Python**

```python
import requests

res = requests.get(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/stats",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
print(data["totalMessages"], data["creditsUsed"])
```

**Réponse**

```json
{
  "success": true,
  "totalMessages": 48,
  "sent": 21,
  "received": 27,
  "aiReplies": 18,
  "humanReplies": 3,
  "creditsUsed": 34,
  "botMessageCount": 18,
  "firstMessageAt": "2026-05-01T09:00:00.000Z",
  "lastMessageAt": "2026-06-09T10:21:00.000Z"
}
```

`botMessageCount` est le même compteur de messages IA que le bouton « réinitialiser » dans l'application remet à zéro pour un contact. `creditsUsed` est le total cumulé des crédits pour ce contact, et non seulement les chiffres de cette réponse. Un identifiant de contact qui n'existe pas dans votre compte renvoie une `404`.

---

## Lister les contacts

`GET /contacts`

Appelez `GET /contacts` **sans** `phoneNumber` ni `email` pour parcourir tous vos contacts, du plus récent au plus ancien. Chaque page renvoie des résumés de contacts compacts (les listes, les tags et les campagnes sont renvoyés sous forme de tableaux d'ID plutôt que d'objets complets) ainsi qu'un `next_cursor`.

| Paramètre de requête | Description |
|---|---|
| `limit` | Taille de la page. Par défaut 50, maximum 100. |
| `cursor` | La valeur `next_cursor` de la page précédente. À omettre sur la première page. |
| `listId` | Optionnel. Ne renvoie que les contacts appartenant à cette liste. |

Pour parcourir chaque page : effectuez le premier appel sans curseur, puis continuez à transmettre le `next_cursor` renvoyé en tant que `cursor`. **Arrêtez-vous lorsque `next_cursor` est `null`** — cela signifie qu'il n'y a plus de résultats.

**cURL**

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts?limit=50&apiKey=YOUR_API_KEY"

# next page:
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts?limit=50&cursor=contact_abc123&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
async function listAllContacts() {
  const all = [];
  let cursor = null;
  do {
    const url = new URL("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts");
    url.searchParams.set("limit", "100");
    if (cursor) url.searchParams.set("cursor", cursor);
    const res = await fetch(url, { headers: { "X-API-Key": "YOUR_API_KEY" } });
    const data = await res.json();
    all.push(...data.contacts);
    cursor = data.next_cursor;
  } while (cursor);
  return all;
}
```

**Python**

```python
import requests

def list_all_contacts():
    all_contacts = []
    cursor = None
    while True:
        params = {"limit": 100}
        if cursor:
            params["cursor"] = cursor
        res = requests.get(
            "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts",
            headers={"X-API-Key": "YOUR_API_KEY"},
            params=params,
        )
        data = res.json()
        all_contacts.extend(data["contacts"])
        cursor = data["next_cursor"]
        if not cursor:
            break
    return all_contacts
```

**Réponse**

```json
{
  "success": true,
  "contacts": [
    {
      "id": "contact_abc123",
      "first_name": "Jane",
      "last_name": "Smith",
      "email": "jane@example.com",
      "phone_number": "+15551234567",
      "channel": "whatsapp",
      "is_bot_active": true,
      "is_private": false,
      "do_not_disturb": false,
      "avatar_url": "https://example.com/photo.jpg",
      "custom_fields": {},
      "created_at": "2026-06-01T09:00:00.000Z",
      "list_ids": ["list123"],
      "tag_ids": ["tagHotLead"],
      "campaign_ids": ["campaign789"],
      "current_campaign_id": "campaign789"
    }
  ],
  "next_cursor": "contact_abc123"
}
```

::: note
**Remarque :** Le filtrage par un `listId` qui n'existe pas sur votre compte renvoie une `404`. Un `cursor` non valide renvoie une `400`.
:::


---

## Compter les contacts

`GET /contacts/count`

Renvoie le nombre de contacts correspondant à un filtre, avec une répartition par canal, sans avoir à parcourir les pages. C'est l'appel idéal pour toute question de type « combien » : une tuile de tableau de bord, une automatisation ou une demande à Champ. Tous les filtres sont facultatifs, et la combinaison de plusieurs d'entre eux affine le résultat (un contact doit correspondre à chacun des filtres envoyés).

| Paramètre de requête | Description |
|---|---|
| `agentId` | Uniquement les contacts assignés à cet agent IA. Passez `none` pour les contacts sans agent assigné (ceux-ci sont pris en charge par l'agent par défaut du canal). |
| `channel` | Uniquement les contacts sur ce canal, par ex. `whatsapp`, `messenger`, `instagram`, `sms`, `email`, `chat_widget`. |
| `tag` | Uniquement les contacts portant ce tag, par **nom** de tag (la casse n'a pas d'importance). Un nom de tag inexistant renvoie `404`. |
| `listId` | Uniquement les contacts sur cette liste. |
| `botActive` | `true` ou `false` — uniquement les contacts dont l'assistant IA est activé ou désactivé. |
| `status` | Uniquement les contacts ayant ce statut, par ex. `Lead`. |
| `rules` | Un objet de règles JSON encodé en URL, utilisant la même structure qu'une liste intelligente (voir [La structure `smart_rules`](#the-smart_rules-shape) plus bas). Ne peut pas être combiné avec les autres filtres. |

N'envoyez aucun filtre pour obtenir le nombre total de contacts sur votre compte.

**cURL**

```bash
# everything
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/count?apiKey=YOUR_API_KEY"

# only the contacts one agent handles on Messenger
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/count?agentId=agent_xyz789&channel=messenger&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const url = new URL("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/count");
url.searchParams.set("agentId", "agent_xyz789");
url.searchParams.set("channel", "messenger");

const res = await fetch(url, { headers: { "X-API-Key": "YOUR_API_KEY" } });
const data = await res.json();
console.log(data.total);
```

**Python**

```python
import requests

res = requests.get(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/count",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"agentId": "agent_xyz789", "channel": "messenger"},
)
data = res.json()
print(data["total"])
```

**Réponse**

```json
{
  "success": true,
  "total": 3423,
  "by_channel": { "messenger": 2744, "instagram": 667, "none": 12 },
  "filters": { "agentId": "agent_xyz789" }
}
```

`by_channel` répartit ce même total par canal ; les contacts qui ne sont sur aucun canal sont comptabilisés sous `none`. `filters` renvoie les filtres qui ont été appliqués, afin que vous puissiez vérifier que l'appel a fait ce que vous souhaitiez.

::: note
**Remarque :** L'envoi de `rules` avec tout autre filtre, ou d'une valeur `rules` qui n'est pas un JSON valide, renvoie `400`. Un nom de tag ou un ID de liste qui n'existe pas sur votre compte renvoie `404`.
:::


---

## Mettre à jour un contact

`PUT /contacts/{contactId}`

Met à jour un contact existant. Seuls les champs que vous incluez sont modifiés — omettez tout ce que vous ne souhaitez pas toucher. Vous devez envoyer au moins un champ, sinon vous recevrez un `400` (« Aucun champ à mettre à jour »).

| Champ | Description |
|---|---|
| `firstName` | Prénom. |
| `lastName` | Nom de famille. |
| `email` | Adresse e-mail. |
| `is_bot_active` | Indique si l'assistant IA répond à ce contact. |
| `is_private` | Marquer comme privé. Définir ceci sur `true` désactive également l'assistant IA. |
| `do_not_disturb` | Suspendre la prospection automatisée pour ce contact. Arrête également les réponses de l'IA. |
| `follow_ups_disabled` | Arrêter tous les suivis automatisés pour ce contact (rapides, cycles et prospects froids) tout en permettant à l'IA de continuer à répondre aux messages qu'ils envoient. Utile une fois qu'une personne a acheté. Reste désactivé jusqu'à ce que vous le remettiez sur `false`. |
| `lead_profile` | Notes de prospect en texte libre. |
| `custom_fields` | Un objet de champs personnalisés. **Fusionné par clé** — seules les clés que vous envoyez sont écrites, le reste des champs personnalisés existants est conservé. Vous pouvez également transmettre des clés de champs personnalisés au niveau supérieur. |

**cURL**

```bash
curl -X PUT "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "firstName": "Jane", "do_not_disturb": true }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123", {
  method: "PUT",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ firstName: "Jane", do_not_disturb: true }),
});
const data = await res.json();
console.log(data.message);
```

**Python**

```python
import requests

res = requests.put(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"firstName": "Jane", "do_not_disturb": True},
)
print(res.json()["message"])
```

**Réponse**

```json
{
  "success": true,
  "message": "Contact updated successfully"
}
```

> **Les champs personnalisés sont fusionnés, et non remplacés.** L'envoi de `{ "custom_fields": { "tier": "gold" } }` définit uniquement `tier` — tous les autres champs personnalisés du contact restent exactement tels qu'ils étaient. Pour supprimer entièrement un champ personnalisé sur tous les contacts, utilisez [Supprimer un champ personnalisé](#delete-a-custom-field).

---

## Ajouter ou supprimer des tags

`POST /contacts/{contactId}/tags`

Ajoute et/ou supprime des tags sur un seul contact en un seul appel. Transmettez les **ID** des tags dans `addTagIds` et `removeTagIds`. Au moins l'un des deux doit être non vide.

Les tags doivent déjà exister sur votre compte — créez-les d'abord via le [point de terminaison des tags](reference.md). Si le contact ou l'un des tags référencés n'existe pas, vous recevrez un `404`.

| Champ | Description |
|---|---|
| `addTagIds` | Tableau des identifiants de tags à ajouter au contact. |
| `removeTagIds` | Tableau des identifiants de tags à supprimer du contact. |

**cURL**

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/tags?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "addTagIds": ["tagHotLead"], "removeTagIds": ["tagColdLead"] }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/tags", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    addTagIds: ["tagHotLead"],
    removeTagIds: ["tagColdLead"],
  }),
});
const data = await res.json();
console.log(data.added, data.removed);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/tags",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"addTagIds": ["tagHotLead"], "removeTagIds": ["tagColdLead"]},
)
data = res.json()
print(data["added"], data["removed"])
```

**Réponse**

```json
{
  "success": true,
  "contact_id": "contact_abc123",
  "added": 1,
  "removed": 1
}
```

---

## Gérer votre bibliothèque de tags

Ces points de terminaison gèrent le tag lui-même — le renommer ou le supprimer de votre compte — contrairement à l'application ou à la suppression d'un tag sur un contact (voir [Ajouter ou supprimer des tags](#add-or-remove-tags) ci-dessus). Chaque tag de votre compte possède un identifiant (`tagId`) : celui affiché dans le gestionnaire de tags de votre tableau de bord, et celui renvoyé en tant que `data.tag_id` lorsque vous créez un tag avec `POST /tags` et un corps JSON `{ "name": "..." }` (sans `phoneNumber`, `email` ou `contactId`).

### Mettre à jour un tag

`PUT /tags/{tagId}`

Envoyez uniquement les champs que vous modifiez.

| Champ | Description |
|---|---|
| `name` | Le nom du tag. |

```bash
curl -X PUT "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/tags/tagHotLead?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Hot lead (Q3)" }'
```

**Réponse**

```json
{ "success": true, "tag_id": "tagHotLead" }
```

Un `tagId` qui n'existe pas dans votre compte renvoie une `404`.

### Supprimer un tag

`DELETE /tags/{tagId}`

Supprime un tag par son identifiant. **Cette action est irréversible** — les contacts portant ce tag le perdent simplement. La suppression d'un tag déjà supprimé (ou qui n'a jamais existé) renvoie `200` avec `deleted: 0` plutôt qu'une `404`, car il n'y a rien à énumérer.

```bash
curl -X DELETE "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/tags/tagColdLead?apiKey=YOUR_API_KEY"
```

**Réponse**

```json
{ "success": true, "deleted": 1 }
```

### Supprimer plusieurs tags à la fois

`DELETE /tags`

| Champ | Description |
|---|---|
| `tagIds` | Tableau des ID de tags à supprimer (1000 max). |

```bash
curl -X DELETE "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/tags?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tagIds": ["tagColdLead", "tagUnsubscribed"] }'
```

**Réponse**

```json
{ "success": true, "deleted": 2 }
```

Les ID qui n'existent pas, ou qui appartiennent à un autre compte, sont ignorés silencieusement et ne sont pas comptabilisés dans `deleted`.

---

## Définir un indicateur en masse

`POST /contacts/bulk-flag`

Définit un indicateur booléen sur plusieurs contacts à la fois. Jusqu'à 500 identifiants de contact par requête. Les identifiants qui n'existent pas dans votre compte sont ignorés et comptabilisés dans `skipped`.

| Champ | Description |
|---|---|
| `contactIds` | Tableau des identifiants de contact à mettre à jour (max 500). |
| `field` | Quel indicateur définir. L'un des suivants : `bot_active` (assistant IA activé/désactivé), `dnd` (suspendre la prospection automatisée), `spam`, `private`. |
| `value` | La valeur booléenne à attribuer à l'indicateur. |

**cURL**

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/bulk-flag?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contactIds": ["contactId1", "contactId2"],
    "field": "bot_active",
    "value": false
  }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/bulk-flag", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    contactIds: ["contactId1", "contactId2"],
    field: "bot_active",
    value: false,
  }),
});
const data = await res.json();
console.log(data.updated, data.skipped);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/bulk-flag",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "contactIds": ["contactId1", "contactId2"],
        "field": "bot_active",
        "value": False,
    },
)
data = res.json()
print(data["updated"], data["skipped"])
```

**Réponse**

```json
{
  "success": true,
  "updated": 2,
  "skipped": 0
}
```

---

## Importation en masse de contacts

`POST /contacts/import`

Crée jusqu'à 500 contacts en un seul appel à partir d'un tableau JSON. Chaque enregistrement nécessite un `phone_number` au format international ; tout le reste est facultatif. Les enregistrements avec des numéros de téléphone invalides ou des canaux non pris en charge sont **ignorés** (non créés), et chaque enregistrement ignoré est signalé avec son index et sa raison — vous pouvez donc corriger uniquement les échecs et réessayer.

Les numéros de téléphone qui existent déjà sur votre compte sont ignorés en tant que `duplicate` par défaut. Envoyez `updateExisting: true` pour **mettre à jour** ces contacts à la place : les champs présents dans l'enregistrement écrasent ceux du contact (`first_name`, `last_name`, `email`, `lead_profile` et `custom_fields` fusionnés clé par clé), les `tags` sont ajoutés, et le contact est ajouté à `listId`. Le canal, le numéro de téléphone et les indicateurs de bot ne sont jamais modifiés sur un contact existant.

Vous pouvez éventuellement ajouter chaque contact importé (ou mis à jour) à une liste avec `listId`, définir un `defaultChannel` pour les enregistrements qui n'en spécifient pas, et étiqueter les enregistrements avec `tags` (noms des étiquettes — les étiquettes manquantes sont créées, les existantes sont mises en correspondance sans tenir compte de la casse).

**Champs de premier niveau**

| Champ | Requis | Description |
|---|---|---|
| `contacts` | Oui | Tableau d'enregistrements de contacts (max 500). |
| `listId` | Non | Liste à laquelle ajouter chaque contact importé (et mis à jour). Doit être une liste sur votre compte. |
| `defaultChannel` | Non | Canal appliqué aux enregistrements qui omettent `channel`. L'un des `whatsapp`, `sms`, `whatsapp_web`. Par défaut à `whatsapp`. |
| `updateExisting` | Non | `true` pour mettre à jour les contacts dont le numéro de téléphone existe déjà au lieu de les ignorer en tant que `duplicate`. Par défaut à `false`. |

**Champs par enregistrement**

| Champ | Requis | Description |
|---|---|---|
| `phone_number` | Oui | Numéro de téléphone au format international (un `+` initial est ajouté s'il manque). |
| `first_name` | Non | Prénom. |
| `last_name` | Non | Nom de famille. |
| `email` | Non | Adresse e-mail. |
| `channel` | Non | L'un des `whatsapp`, `sms`, `whatsapp_web`. Utilise `defaultChannel` par défaut. |
| `is_bot_active` | Non | Si l'assistant IA répond. Par défaut à `true`. |
| `is_private` | Non | Marquer comme privé. Par défaut à `false`. |
| `lead_profile` | Non | Notes de prospect en texte libre. |
| `custom_fields` | Non | Objet de clés et valeurs de champs personnalisés. |
| `tags` | Non | Tableau de noms d'étiquettes (une seule chaîne `"a; b"` fonctionne également). Les étiquettes qui n'existent pas sont créées ; les existantes sont mises en correspondance sans tenir compte de la casse. Max 25 par enregistrement. |

**cURL**

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/import?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contacts": [
      { "phone_number": "+12025551234", "first_name": "Ann", "last_name": "Lee", "tags": ["vip", "newsletter"] },
      { "phone_number": "+12025551235", "first_name": "Bob" }
    ],
    "listId": "list123",
    "defaultChannel": "whatsapp_web",
    "updateExisting": true
  }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/import", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    contacts: [
      { phone_number: "+12025551234", first_name: "Ann", last_name: "Lee", tags: ["vip", "newsletter"] },
      { phone_number: "+12025551235", first_name: "Bob" },
    ],
    listId: "list123",
    defaultChannel: "whatsapp_web",
    updateExisting: true,
  }),
});
const data = await res.json();
console.log(`Imported ${data.imported}, updated ${data.updated}, skipped ${data.skipped.length}`);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/import",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "contacts": [
            {"phone_number": "+12025551234", "first_name": "Ann", "last_name": "Lee", "tags": ["vip", "newsletter"]},
            {"phone_number": "+12025551235", "first_name": "Bob"},
        ],
        "listId": "list123",
        "defaultChannel": "whatsapp_web",
        "updateExisting": True,
    },
)
data = res.json()
print(f"Imported {data['imported']}, updated {data['updated']}, skipped {len(data['skipped'])}")
```

**Réponse**

```json
{
  "success": true,
  "imported": 2,
  "contact_ids": ["contact_abc123", "contact_def456"],
  "updated": 0,
  "updated_contact_ids": [],
  "skipped": []
}
```

Si certains enregistrements ne peuvent pas être créés, ils apparaissent dans `skipped` avec la raison (ici sans `updateExisting`, donc le numéro existant est ignoré) :

```json
{
  "success": true,
  "imported": 1,
  "contact_ids": ["contact_abc123"],
  "updated": 0,
  "updated_contact_ids": [],
  "skipped": [
    { "index": 1, "phone_number": "+12025551235", "reason": "duplicate" }
  ]
}
```

Avec `updateExisting: true`, la même requête signale le contact existant sous `updated` / `updated_contact_ids` à la place.

Raisons possibles pour lesquelles un enregistrement est ignoré : `invalid_record`, `missing_phone_number`, `invalid_phone_number`, `invalid_channel`, `duplicate_in_request`, `duplicate`, `contact_limit_reached`, `create_failed`.

> **Limites du forfait.** Si la limite de contacts de votre forfait ne permet pas d'ajouter autant de nouveaux contacts, la requête entière est rejetée dès le départ avec une erreur `403`. Si la limite est atteinte en cours de traitement, les enregistrements restants sont renvoyés comme ignorés avec la raison `contact_limit_reached`.

---

## Importer des contacts depuis un fichier CSV

Pour des importations plus volumineuses que ce que permet l' [importation en masse](#bulk-import-contacts) (jusqu'à environ 50 000 lignes), mettez en file d'attente une tâche d'importation asynchrone pour un fichier CSV déjà présent dans le stockage de votre compte, puis interrogez-la jusqu'à ce qu'elle soit terminée.

### Démarrer l'importation

`POST /contacts/import-csv`

| Champ | Requis | Description |
|---|---|---|
| `csvStoragePath` | Oui | Chemin de stockage du fichier CSV, sous `users/{your account id}/imports/`, se terminant par `.csv`. |
| `listName` | Oui | Crée (ou réutilise) une liste avec ce nom et y ajoute chaque contact importé. |
| `existingListRefs` | Non | Tableau des ID de listes existantes auxquelles ajouter également chaque contact importé. |
| `defaultChannel` | Non | Canal appliqué aux lignes qui n'en spécifient pas. |

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/import-csv?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "csvStoragePath": "users/abc123/imports/leads.csv",
    "listName": "Webinar signups"
  }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/import-csv", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    csvStoragePath: "users/abc123/imports/leads.csv",
    listName: "Webinar signups",
  }),
});
const data = await res.json();
console.log(data.job_id);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/import-csv",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "csvStoragePath": "users/abc123/imports/leads.csv",
        "listName": "Webinar signups",
    },
)
job_id = res.json()["job_id"]
```

**Réponse** (`202` — l'importation est mise en file d'attente, pas encore terminée)

```json
{
  "success": true,
  "job_id": "csvimp_abc123",
  "status": "queued"
}
```

> **Placer le fichier dans le stockage.** Ce point de terminaison démarre et suit la tâche d'importation ; il n'accepte pas lui-même de téléchargement. Le fichier CSV doit déjà se trouver à `csvStoragePath` avant que vous ne l'appeliez — l'outil d'importation CSV du tableau de bord effectue cette opération comme première étape.

### Interroger la tâche d'importation

`GET /contacts/import-csv/{jobId}`

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/import-csv/csvimp_abc123?apiKey=YOUR_API_KEY"
```

**Réponse**

```json
{
  "success": true,
  "job_id": "csvimp_abc123",
  "status": "completed",
  "imported": 812,
  "updated": 0,
  "skipped": 14,
  "errors": [],
  "error_message": null
}
```

`status` passe par `queued` → `processing` → `completed`, ou `failed` avec la raison dans `error_message`. Un `jobId` qui n'existe pas sur votre compte renvoie une `404`.

---

## Exporter des contacts

Lance une exportation CSV asynchrone de vos contacts et renvoie une tâche que vous pouvez interroger pour vérifier son achèvement.

### Lancer l'exportation

`POST /contacts/export`

| Champ | Requis | Description |
|---|---|---|
| `listId` | Non | Exporter uniquement les contacts appartenant à cette liste. |
| `contactIds` | Non | Exporter uniquement ces identifiants de contact spécifiques. |

Si vous ne remplissez aucun des deux champs, tous les contacts de votre compte seront exportés.

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/export?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "listId": "list123" }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/export", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ listId: "list123" }),
});
const data = await res.json();
console.log(data.job_id);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/export",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"listId": "list123"},
)
job_id = res.json()["job_id"]
```

**Réponse** (`202` — l'exportation est mise en file d'attente)

```json
{
  "success": true,
  "job_id": "export_abc123",
  "status": "queued"
}
```

### Interroger le travail d'exportation

`GET /contacts/export/{jobId}`

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/export/export_abc123?apiKey=YOUR_API_KEY"
```

**Réponse**

```json
{
  "success": true,
  "job_id": "export_abc123",
  "status": "completed",
  "export_id": "exp_xyz789",
  "contact_count": 812,
  "error_message": null
}
```

> Une fois que `status` est `"completed"`, vous recevez `export_id` et `contact_count`. Le téléchargement du fichier CSV généré s'effectue depuis la page Exportations de votre tableau de bord.

---

## Envoyer un message à un contact

`POST /contacts/{contactId}/send-message`

Envoie un message à un contact existant sur le canal qu'il utilise déjà. Le message est mis en file d'attente et envoyé en arrière-plan — la réponse confirme qu'il a été accepté, et non qu'il a déjà été remis.

| Champ | Requis | Description |
|---|---|---|
| `body` | Oui | Le texte du message à envoyer. |
| `mediaUrl` | Non | URL d'un fichier multimédia à joindre. |
| `mediaContentType` | Non | Type MIME du média joint (par ex. `image/jpeg`). |

**cURL**

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/send-message?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "body": "Hi! Your appointment is confirmed." }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/send-message", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ body: "Hi! Your appointment is confirmed." }),
});
const data = await res.json();
console.log(data.messageId);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/send-message",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"body": "Hi! Your appointment is confirmed."},
)
print(res.json()["messageId"])
```

**Réponse**

```json
{
  "success": true,
  "messageId": "aB3dE5fG7hI9jK1lM2nO",
  "contactId": "contact_abc123",
  "channel": "whatsapp",
  "message": "Message created successfully. Delivery is being processed."
}
```

> **Impossible d'envoyer pour le moment ?** Si le contact a activé le mode « ne pas déranger » ou le mode privé, ou s'il n'est pas sur un canal capable de recevoir des messages sortants, la requête est rejetée avec un `422` et un `error` explicatif.

Pour envoyer via un numéro de téléphone, un identifiant Instagram ou une autre identité de canal au lieu d'un identifiant de contact — et pour en savoir plus sur la messagerie en général — consultez l'[API Messages](messages.md).

---

## Assigner un agent IA à un contact

`POST /contacts/{contactId}/assign-agent`

Déplace une conversation existante vers un agent IA différent, à partir du message suivant. Cela équivaut à **Assigner un agent IA** dans le menu d'une discussion, et c'est la même étape que celle utilisée par l'action **Assigner un agent IA ou une campagne** dans les automatisations.

| Champ | Requis | Description |
|---|---|---|
| `agentId` | Oui | L'identifiant de l'agent IA qui doit prendre le relais, ou `null` pour supprimer l'assignation afin que la conversation revienne dans la boîte de réception de votre équipe. |
| `triggerAIResponse` | Non | `true` permet à l'agent nouvellement assigné de répondre immédiatement aux derniers messages sans réponse du contact. La valeur par défaut est `false`. |

> **Attention avec `triggerAIResponse: true`** — cela envoie un message au contact immédiatement, ne l'utilisez donc que lorsque vous voulez qu'il soit contacté sur le moment. Sur Messenger et Instagram, ce message échoue si le contact ne vous a pas écrit depuis plus de 24 heures.

**cURL**

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/assign-agent?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "agentId": "agent_xyz789" }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/assign-agent", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ agentId: "agent_xyz789" }),
});
const data = await res.json();
console.log(data.data.agentId);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/assign-agent",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"agentId": "agent_xyz789"},
)
print(res.json()["data"]["agentId"])
```

**Réponse**

```json
{
  "success": true,
  "data": {
    "contactId": "contact_abc123",
    "agentId": "agent_xyz789",
    "aiResponseTriggered": false
  }
}
```

> L'agent doit appartenir au même compte que le contact ; sinon, la demande est rejetée avec une erreur `404` ou `403`. Trouvez les identifiants des agents sur la page Agents IA (l'URL de chaque agent se termine par son identifiant).

---

## Assigner un agent IA à plusieurs contacts

`POST /contacts/bulk-assign-agent`

Déplace plusieurs conversations vers un agent IA différent en un seul appel — ou efface l'assignation pour tous avec `null`. Il s'agit purement d'un changement de routage : **aucun message n'est envoyé et l'agent ne répond à personne**. Chaque contact reçoit simplement le nouvel agent la prochaine fois qu'il écrit. (C'est pourquoi il n'y a pas de `triggerAIResponse` ici.)

| Champ | Requis | Description |
|---|---|---|
| `agentId` | Oui | L'agent IA qui doit prendre le relais, ou `null` pour effacer l'assignation. |
| `contactIds` | L'un des trois | Jusqu'à 500 identifiants de contact à déplacer. |
| `filter` | L'un des trois | Sélectionne les contacts sur le serveur au lieu de les lister, du plus récent au plus ancien. Accepte les mêmes clés que les filtres du point de terminaison de comptage : `agentId` (ou `none`), `channel`, `tag`, `listId`, `botActive`, `status`. |
| `rules` | L'un des trois | Un objet de règles de liste intelligente — voir [La forme `smart_rules`](#the-smart_rules-shape). |
| `limit` | Non | Nombre de contacts à déplacer dans cet appel lorsque vous sélectionnez avec `filter` ou `rules`. De 1 à 500, par défaut 500. |

Envoyez exactement l'un des paramètres `contactIds`, `filter` ou `rules`.

**cURL**

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/bulk-assign-agent?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "agent_xyz789",
    "filter": { "agentId": "agent_abc123", "channel": "messenger" }
  }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/bulk-assign-agent", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    agentId: "agent_xyz789",
    filter: { agentId: "agent_abc123", channel: "messenger" },
  }),
});
const data = await res.json();
console.log(data.updated, data.remaining);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/bulk-assign-agent",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "agentId": "agent_xyz789",
        "filter": {"agentId": "agent_abc123", "channel": "messenger"},
    },
)
data = res.json()
print(data["updated"], data["remaining"])
```

**Réponse**

```json
{
  "success": true,
  "agentId": "agent_xyz789",
  "matched": 3415,
  "updated": 500,
  "skipped": 0,
  "remaining": 2915,
  "filters": { "agentId": "agent_abc123" }
}
```

`matched` correspond au nombre total de contacts trouvés par la sélection, `updated` au nombre de contacts déplacés par cet appel, `skipped` au nombre d'ID envoyés qui n'ont pas été trouvés sur votre compte, et `remaining` au nombre de contacts qui correspondent encore une fois cet appel terminé.

**Déplacer tout le monde.** Comme un appel déplace au maximum 500 contacts, un grand groupe nécessite plusieurs appels. Utilisez un filtre qui cesse de correspondre à un contact une fois qu'il a été déplacé — par exemple `filter: { "agentId": "agent_abc123" }` lors de l'assignation à `agent_xyz789` — et répétez exactement le même appel jusqu'à ce que `remaining` renvoie `0`. Lorsque vous passez `contactIds` à la place, `remaining` est toujours `0`.

---

## Assigner un contact à un département

`POST /contacts/{contactId}/department`

"Assigner ce prospect aux ventes" — classe un contact sous un département nommé et, par défaut, le confie à la personne de ce département qui a actuellement le moins de contacts. Ceci est distinct de l'[assignation d'un agent IA](#assign-an-ai-agent-to-a-contact) : un département répond à la question « quelle équipe est responsable de ceci », un agent répond à « quelle IA traite ceci », et définir l'un n'efface jamais l'autre.

| Champ | Requis | Description |
|---|---|---|
| `department_id` | Oui | Le département sous lequel classer le contact. Passez `null` pour effacer cette valeur. |
| `hand_to_member` | Non | Confier également le contact à la personne la moins chargée de ce département. La valeur par défaut est `true`. Ne réassigne jamais un contact déjà possédé par quelqu'un. |

**cURL**

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/department?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "department_id": "dept_sales" }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/department", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ department_id: "dept_sales" }),
});
const data = await res.json();
console.log(data.assigned_to);
```

**Python**

```python
import requests

res = requests.post(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/department",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"department_id": "dept_sales"},
)
print(res.json()["assigned_to"])
```

**Réponse**

```json
{
  "success": true,
  "department_id": "dept_sales",
  "assigned_to": "member_uid_123"
}
```

`assigned_to` est `null` lorsque le contact était déjà possédé par quelqu'un, ou si vous avez passé `hand_to_member: false`.

---

## Lier un contact entre les canaux

"Continuer sur WhatsApp" (ou SMS) trouve ou crée le contact de cette personne sur un autre canal basé sur le téléphone et lie les deux ensemble, afin que le reste de l'application les reconnaisse comme étant la même personne.

### Lier à un autre canal

`POST /contacts/{contactId}/link-channel`

| Champ | Requis | Description |
|---|---|---|
| `channel` | Oui | Le canal auquel se lier. L'un des éléments `whatsapp`, `whatsapp_web`, `sms`. |
| `phoneNumber` | Non | Numéro de téléphone à utiliser sur le nouveau canal. Utilise par défaut le numéro du contact source. |

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/link-channel?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "channel": "sms" }'
```

**Réponse**

```json
{
  "success": true,
  "data": {
    "contact_id": "contact_def456",
    "person_id": "person_xyz789",
    "created": true
  }
}
```

`created` vous indique si un nouveau contact a été créé pour le canal cible ou si un contact existant a été trouvé et lié. Appeler cette fonction une seconde fois est sans risque : elle renvoie le même `contact_id` avec `created: false` au lieu de créer un doublon.

Une erreur `422` signifie que le compte ne peut pas effectuer cette liaison pour le moment : le contact appartient déjà à cette famille de canaux, il n'a pas de numéro de téléphone à utiliser, ou aucun expéditeur n'est connecté pour le canal cible. Une erreur `409` signifie que les deux contacts sont déjà liés à deux personnes différentes ; dissociez-en un d'abord.

### Lister les conversations liées d'un contact

`GET /contacts/{contactId}/linked`

Renvoie les autres conversations qui correspondent à la même personne que ce contact. Un contact non lié renvoie un tableau vide, et non une erreur `404` — « cette personne n'a pas d'autres canaux » est un état normal.

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/linked?apiKey=YOUR_API_KEY"
```

**Réponse**

```json
{
  "success": true,
  "data": [
    {
      "contact_id": "contact_def456",
      "channel": "sms",
      "custom_channel": null,
      "first_name": "Jane",
      "last_name": "Smith",
      "phone_number": "+15551234567",
      "last_message": "Sounds good, thanks!",
      "last_message_timestamp": "2026-06-09T10:21:00.000Z",
      "linked_from": {
        "contact_id": "contact_abc123",
        "channel": "whatsapp",
        "linked_at": "2026-06-01T09:00:00.000Z",
        "reason": "continue_on_channel"
      }
    }
  ]
}
```

### Dissocier un contact

`DELETE /contacts/{contactId}/link`

Supprime ce contact de sa personne, de manière unilatérale — tout autre contact toujours lié à cette personne conserve son lien, donc dissocier un contact sur trois ne dissout pas le groupe.

```bash
curl -X DELETE "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/link?apiKey=YOUR_API_KEY"
```

**Réponse**

```json
{ "success": true }
```

---

## Récupérer la photo de profil d'un contact

`POST /contacts/{contactId}/profile-pic`

Récupère (et met en cache) la photo de profil WhatsApp ou Meta du contact à la demande — la même photo que celle renvoyée par `avatarUrl` dans [Obtenir un contact](#get-a-contact-by-phone-or-email), actualisée.

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123/profile-pic?apiKey=YOUR_API_KEY"
```

**Réponse**

```json
{
  "success": true,
  "avatar_url": "https://example.com/photo.jpg",
  "cached": false
}
```

`cached: true` signifie que l'URL provient d'une récupération récente plutôt que d'une recherche directe auprès du fournisseur — les photos sont mises en cache pendant 7 jours, et un contact pour lequel le fournisseur indique qu'aucune photo n'est accessible est mis en cache comme indisponible pendant 24 heures. Lorsqu'il n'y a pas de photo à récupérer, `avatar_url` est omis et `message` explique pourquoi.

---

## Étiquetage automatique des contacts avec l'IA

Exécute les règles d'étiquetage de votre compte sur l'historique complet des conversations d'un ou plusieurs contacts et applique (ou supprime) les étiquettes exactement comme le fait l'étiquetage en temps réel lors d'un chat en direct — mêmes règles, même coût en crédits par étiquette.

### Démarrer une exécution

`POST /contacts/auto-tag`

| Champ | Requis | Description |
|---|---|---|
| `scope` | Oui | `"contacts"` pour étiqueter des contacts spécifiques, ou `"agent"` pour étiqueter chaque conversation actuellement gérée par un agent IA. |
| `contact_ids` | Requis quand `scope` est `"contacts"` | Tableau d'identifiants de contact, de 1 à 500. |
| `agent_id` | Requis quand `scope` est `"agent"` | L'agent IA dont les conversations doivent être étiquetées. Quand `scope` est `"contacts"`, ceci est facultatif et permet simplement de restreindre les règles d'étiquetage de l'agent qui sont exécutées. |

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/auto-tag?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "scope": "contacts", "contact_ids": ["contact_abc123", "contact_def456"] }'
```

Un **seul** contact s'exécute en ligne et renvoie le résultat immédiatement :

```json
{ "success": true, "result": { "tags_applied": 2, "tags_removed": 0 } }
```

**Deux contacts ou plus** (ou `scope: "agent"`) s'exécutent en tant que tâche de fond et renvoient `202` immédiatement :

```json
{ "success": true, "run_id": "m1x2y3-a1b2c3d4", "total": 214 }
```

### Interroger une exécution

`GET /contacts/auto-tag/run`

Renvoie l'exécution actuelle (ou la plus récente) du compte, afin que vous puissiez suivre la progression sans avoir à gérer `run_id` vous-même.

```bash
curl "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/auto-tag/run?apiKey=YOUR_API_KEY"
```

**Réponse**

```json
{
  "success": true,
  "run": {
    "run_id": "m1x2y3-a1b2c3d4",
    "status": "running",
    "total": 214,
    "processed": 58,
    "tagged_contacts": 12,
    "tags_applied": 15,
    "tags_removed": 2,
    "credits_charged": 15
  }
}
```

`run` est `null` lorsque le compte n'en a jamais démarré. `status` passe de `"running"` à `"completed"` ou `"failed"`.

Une seule exécution en masse peut être en cours par compte à la fois — démarrer une seconde exécution alors qu'une autre est en cours renvoie `409` avec `error_code: "auto_tag_run_in_progress"`. L'épuisement des crédits lors d'une exécution sur un seul contact renvoie `402` avec `error_code: "insufficient_credits"` ; une exécution en masse s'arrête d'elle-même prématurément et indique sa progression dans `run`.

---

## Supprimer un contact

`DELETE /contacts/{contactId}`

Supprime définitivement un contact par son identifiant, ainsi que son historique de messages. **Cette action est irréversible.** Pour supprimer plusieurs contacts en un seul appel, utilisez [Supprimer des contacts](#delete-contacts) ci-dessous.

**cURL**

```bash
curl -X DELETE "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123", {
  method: "DELETE",
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.success);
```

**Python**

```python
import requests

res = requests.delete(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/contact_abc123",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
print(res.json()["success"])
```

**Réponse**

```json
{
  "success": true
}
```

Un ID de contact qui n'existe pas sur votre compte, ou qui appartient à un compte différent, renvoie une `404`.

---

## Supprimer des contacts

`DELETE /contacts`

Supprime définitivement un ou plusieurs contacts par identifiant en un seul appel (jusqu'à 500 identifiants). Les identifiants qui n'existent pas sur votre compte sont ignorés et comptabilisés dans `skipped`. **Cette action est irréversible.**

| Champ | Description |
|---|---|
| `contactIds` | Tableau des identifiants de contact à supprimer (max 500). |

**cURL**

```bash
curl -X DELETE "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "contactIds": ["contactId1", "contactId2"] }'
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts", {
  method: "DELETE",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ contactIds: ["contactId1", "contactId2"] }),
});
const data = await res.json();
console.log(`Deleted ${data.deleted}, skipped ${data.skipped}`);
```

**Python**

```python
import requests

res = requests.delete(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"contactIds": ["contactId1", "contactId2"]},
)
data = res.json()
print(f"Deleted {data['deleted']}, skipped {data['skipped']}")
```

**Réponse**

```json
{
  "success": true,
  "deleted": 2,
  "skipped": 0
}
```

---

## Supprimer un champ personnalisé

`DELETE /contacts/custom-fields/{fieldKey}`

Supprime une clé de champ personnalisé de **chaque** contact de votre compte. Utilisez cette fonction pour faire le ménage après avoir renommé ou retiré un champ personnalisé. La clé ne peut contenir que des lettres, des chiffres, des traits de soulignement et des traits d'union. Renvoie le nombre de contacts mis à jour. **Cette action est irréversible.**

**cURL**

```bash
curl -X DELETE "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/custom-fields/webinar_date_nh?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/custom-fields/webinar_date_nh", {
  method: "DELETE",
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(`Removed from ${data.updated} contacts`);
```

**Python**

```python
import requests

res = requests.delete(
    "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/contacts/custom-fields/webinar_date_nh",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
print(f"Removed from {res.json()['updated']} contacts")
```

**Réponse**

```json
{
  "success": true,
  "updated": 42
}
```

::: note
**Remarque :** Une clé de champ contenant des caractères non pris en charge renvoie une `400`.
:::


---

## Listes

Les listes regroupent des contacts. Une liste est soit **statique** (vous décidez qui en fait partie), soit **intelligente** (l'appartenance est calculée à partir de règles et mise à jour automatiquement — voir [Organisation des listes et des contacts](../get-started/list-and-contact-management.md#smart-lists-auto-updating)).

| Champ | Description |
|---|---|
| `name` | Requis lors de la création. Jusqu'à 100 caractères. |
| `status` | `live` (par défaut) ou `draft`. En minuscules. |
| `contact_ids` | Tableau d'identifiants de contacts à ajouter à la liste. **Listes statiques uniquement.** |
| `type` | `static` (par défaut) ou `smart`. |
| `smart_rules` | L'ensemble de règles — requis lorsque `type` est `smart`. Voir ci-dessous. |

### Créer une liste

`POST /lists`

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/lists?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Hot leads (active)",
        "type": "smart",
        "smart_rules": {
          "match": "all",
          "conditions": [
            { "field": "tags", "op": "has_any", "value": ["tagHotLead"] },
            { "field": "last_activity_at", "op": "within_last", "value": { "amount": 90, "unit": "days" } }
          ]
        }
      }'
```

**Réponse**

```json
{
  "success": true,
  "list_id": "list_abc123",
  "evaluation": { "added": 3, "removed": 0, "total": 3 }
}
```

Une liste intelligente est évaluée **en ligne**, dans la même requête, donc `evaluation` vous indique exactement qui s'y trouve. Sur une liste statique, `evaluation` est `null`.

### Mettre à jour une liste

`PUT /lists/{listId}`

Envoyez uniquement les champs que vous modifiez. La modification de `smart_rules` réévalue immédiatement la liste et renvoie le même objet `evaluation`.

```bash
curl -X PUT "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/lists/list_abc123?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "smart_rules": { "match": "any", "conditions": [ { "field": "tags", "op": "has_any", "value": ["tagHotLead", "tagWebinar"] } ] } }'
```

Vous pouvez faire passer une liste d'un type à l'autre :

- **Statique → intelligente** : envoyez `{ "type": "smart", "smart_rules": { … } }`. Les règles prennent le relais immédiatement.
- **Intelligente → statique** : envoyez `{ "type": "static" }`. Les règles sont supprimées et les personnes présentes sur la liste y restent.

### La structure de `smart_rules`

```json
{
  "match": "all",
  "conditions": [
    { "field": "tags", "op": "has_any", "value": ["tagHotLead"] },
    { "field": "channel", "op": "is_any", "value": ["whatsapp", "sms"] },
    { "field": "last_incoming_message_at", "op": "not_within_last", "value": { "amount": 7, "unit": "days" } },
    { "field": "created_at", "op": "after", "value": "2026-01-01" },
    { "field": "is_bot_active", "op": "is", "value": true },
    { "field": "email", "op": "is_set" },
    { "field": "custom_field", "key": "Plan", "op": "eq", "value": "pro" }
  ]
}
```

- `match` — `all` (chaque condition doit être vraie) ou `any` (au moins une).
- `conditions` — 1 à 20 conditions, chacune avec au plus 100 valeurs, chaînes de caractères jusqu'à 200 caractères.

| `field` | `op` | `value` |
|---|---|---|
| `tags` | `has_any`, `has_all`, `has_none` | tableau d'identifiants de tags |
| `lists` | `in_any`, `not_in_any` | tableau d'identifiants de listes (**listes statiques uniquement** — une liste intelligente ne peut pas être créée à partir d'une autre liste intelligente) |
| `channel` | `is_any`, `is_none` | tableau de canaux |
| `status` | `is_any`, `is_none` | tableau de statuts de contact |
| `created_at`, `last_activity_at`, `last_incoming_message_at`, `last_outgoing_message_at`, `first_ai_interaction_at`, `last_ai_interaction_at` | `within_last`, `not_within_last` | `{ "amount": 1–3650, "unit": "hours" \| "days" }` |
| mêmes champs de date | `before`, `after` | date ISO (`"2026-01-01"`, comparée par jours entiers) ou date-heure ISO complète (`"2026-01-01T14:30:00Z"`, comparée à l'instant précis) |
| mêmes champs de date | `is_set`, `not_set` | — |
| `has_interacted_with_ai` | `is` | `true` / `false` — `true` correspond aux contacts auxquels l'IA a envoyé au moins un message (à tout moment) |
| `is_bot_active`, `do_not_disturb`, `is_private`, `has_ever_responded` | `is` | `true` / `false` |
| `email`, `phone_number`, `first_name`, `last_name` | `is_set`, `not_set`, `contains`, `not_contains` | chaîne pour les formulaires `contains` |
| `current_campaign_id`, `assigned_agent` | `is_any`, `is_none`, `is_set`, `not_set` | tableau d'identifiants pour les formulaires `is_any` / `is_none` |
| `custom_field` (plus un `key`) | `eq`, `neq`, `contains`, `not_contains`, `is_set`, `not_set` | chaîne pour les formulaires de valeur |

`not_within_last` correspond également aux contacts pour lesquels la date n'a jamais été définie ("il y a plus de N, **ou jamais**"), et les comparaisons de texte ignorent la casse.

**Engagement de l'IA.** `has_interacted_with_ai` est l'indicateur de durée de vie : `true` pour chaque contact auquel votre IA a envoyé au moins un message, `false` pour tous les autres (y compris les contacts auxquels seule votre équipe a répondu). Il est apposé lors du premier message de l'IA à un contact et n'est jamais effacé ; ainsi, désactiver les réponses de l'IA pour le contact ou le déplacer vers une autre campagne ne le réinitialise pas. Pour une *période* — « les contacts que mon IA a gérés ce mois-ci », la question de facturation habituelle — utilisez plutôt `last_ai_interaction_at` :

```json
{ "field": "last_ai_interaction_at", "op": "within_last", "value": { "amount": 30, "unit": "days" } }
```

Ne confondez pas l'un ou l'autre avec `is_bot_active` (l'IA est *autorisée* à répondre, ce qui ne signifie pas qu'elle l'a fait) ou `has_ever_responded` (le *contact* a répondu, à qui que ce soit). Les deux mêmes indicateurs sont renvoyés pour chaque contact sous la forme `first_ai_interaction_at` / `last_ai_interaction_at`, et l'ensemble des règles fonctionne également sur `GET /contacts?rules=`, vous pouvez donc compter les correspondances sans créer de liste.

### Prévisualiser un ensemble de règles

`POST /lists/preview`

Compte et échantillonne les contacts qu'un ensemble de règles correspondrait, sans rien créer ni modifier. Utilisez cette fonction pour vérifier la cohérence des règles avant de les enregistrer.

```bash
curl -X POST "<span data-t="apiBaseUrl">https://api.youraiconnector.com</span>/v1/lists/preview?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "smart_rules": { "match": "all", "conditions": [ { "field": "tags", "op": "has_any", "value": ["tagHotLead"] } ] } }'
```

**Réponse**

```json
{
  "success": true,
  "count": 3,
  "sample": [
    {
      "id": "contact_abc123",
      "first_name": "Sofia",
      "last_name": "Martinez",
      "phone_number": "+31600000000",
      "email": "sofia@example.com",
      "channel": "whatsapp"
    }
  ]
}
```

`sample` contient jusqu'à 10 contacts, classés du plus récemment actif au plus ancien.

### Réexécuter une liste intelligente maintenant

`POST /lists/{listId}/evaluate`

Force une réévaluation immédiate (la même action que **Actualiser maintenant** dans le tableau de bord). Les listes intelligentes se mettent déjà à jour lorsqu'un contact change, et toutes les 15 minutes pour les règles basées sur le temps ; cette fonction n'est donc nécessaire que lorsque vous souhaitez obtenir le résultat *immédiatement*.

**Réponse**

```json
{
  "success": true,
  "list_id": "list_abc123",
  "evaluation": { "added": 2, "removed": 1, "total": 4 }
}
```

`evaluation.skipped: true` signifie qu'une autre évaluation de la même liste était déjà en cours et que cet appel n'a rien fait.

### Les listes intelligentes refusent les membres ajoutés manuellement

Les points de terminaison d'appartenance renvoient **`409`** avec `"This is a smart list — its members are computed from its rules. Edit the rules instead."` lorsque la liste cible est intelligente. Cela couvre `POST /contacts/lists`, `DELETE /contacts/lists`, `POST /contacts/lists/batch`, `contact_ids` sur `POST /lists` et `PUT /lists/{listId}`, ainsi que le choix d'une liste intelligente comme cible d'importation CSV. Modifiez plutôt les règles.

Appeler `POST /lists/{listId}/evaluate` sur une liste **statique** est également une `409` — elle ne contient aucune règle à exécuter.

---

## Erreurs de l'API Contacts

Les points de terminaison (endpoints) de contact renvoient l'enveloppe d'erreur standard :

```json
{
  "success": false,
  "error": "Contact not found"
}
```

Certains points de terminaison incluent également `error_code`, qui correspond généralement au statut HTTP — la seule exception est le cas de doublon de contact ci-dessous, où le statut HTTP est `200` et où seul `error_code` contient le `409`. Les codes spécifiques aux points de terminaison de contact :

| Code | Quand cela se produit sur un point de terminaison de contact |
|---|---|
| `400` | Mauvaise requête — un champ manquant/invalide, un corps vide, un curseur incorrect ou plus de 500 ID dans un lot. |
| `402` | Crédits insuffisants pour effectuer une exécution d'étiquetage IA sur un contact (`error_code: "insufficient_credits"`). |
| `404` | Le contact, la liste ou l'étiquette n'a pas été trouvé(e) sur votre compte. |
| `409` | Un contact avec ce numéro de téléphone existe déjà (lors de la création). Renvoyé sous la forme `error_code` dans le corps avec un statut HTTP de `200`, donc effectuez une bifurcation sur `error_code` ici. Également renvoyé lorsqu'une exécution d'étiquetage automatique en masse est déjà en cours (`error_code: "auto_tag_run_in_progress"`), ou lorsque la liaison d'un contact à un autre canal joindrait deux contacts déjà liés à deux personnes différentes. |
| `422` | Le contact ne peut pas recevoir de message pour le moment (ne pas déranger, privé ou canal non pris en charge). Sur le point de terminaison de liaison de canal, couvre également l'absence de numéro de téléphone, un couplage de canal non pris en charge ou l'absence d'expéditeur connecté pour le canal cible. |

Un `403` sur un point de terminaison de contact peut également signifier un problème de limite de contacts ou d'autorisation de liste plutôt qu'un problème d'accès au forfait. Les codes partagés que chaque point de terminaison peut renvoyer — `401`, `403` (votre forfait n'inclut pas l'accès à l'API), `429` (limite de débit) et `500` — sont listés avec des conseils de nouvelle tentative dans [Erreurs et pagination](errors-and-pagination.md).

---

## Étapes suivantes

- [API Messages](messages.md) — envoyez des messages par identité de canal et gérez les conversations.
- [Référence API](reference.md) — liste complète des points de terminaison, y compris les tags et les listes.
- [Accès API](../integrations/api-access.md) — authentification, limites de débit et gestion des erreurs.
