# API REST

> Discutez avec vos agents intoCHAT, lisez leurs conversations, leads et sources de connaissances, synchronisez leurs contacts, et abonnez des webhooks depuis votre propre serveur ou un outil d'automatisation : clés API, scopes, limites de débit, erreurs, pagination et chaque point de terminaison avec des exemples.

L'API REST permet à votre propre serveur de communiquer avec vos agents. Envoyez un message et recevez la réponse de l'agent, lisez les conversations et les leads, créez et mettez à jour des contacts, ajoutez ou supprimez des sources de connaissances, et abonnez des webhooks aux événements d'un agent, en JSON sur HTTPS. Les [applications Zapier et Make](/fr/docs/zapier-and-make) reposent sur elle. Une réponse via l'API est rédigée par le même code qu'une réponse dans le widget de chat, à partir des mêmes connaissances et réglages.

L'API est destinée aux serveurs. Elle n'envoie pas d'en-têtes CORS : une page web d'un autre site ne peut donc pas l'appeler, et votre clé API ne doit jamais apparaître dans un navigateur ni dans votre code d'intégration. Pour afficher un chat sur votre site, utilisez la [balise script](/fr/docs/script-tag). Pour recevoir les événements au moment où ils se produisent, utilisez les [webhooks](/fr/docs/webhooks).

## Offres qui incluent l'API

L'API est incluse dans les offres **Starter**, **Pro** et **Enterprise**. Avec l'offre **Free**, vous ne pouvez pas créer de clés.

Si un compte repasse à l'offre Free, ses clés restent dans la liste, mais chaque requête reçoit une erreur `403` avec le code `plan_required`. Elles fonctionnent de nouveau dès que le compte est sur Starter ou une offre supérieure.

Chaque réponse qu'un agent rédige via l'API compte comme un message de votre offre, exactement comme une réponse dans le widget, et compte dans le [plafond mensuel de messages](/fr/docs/limits-and-access#plafond-mensuel-de-messages) de l'agent si vous en avez défini un. Lire des données, et ajouter ou supprimer des sources, ne consomme aucun message. Voir [Ce qui compte comme message](/fr/docs/plans-and-limits#ce-qui-compte-comme-message).

## Créer une clé API

1. Dans la barre latérale, sous **Settings**, cliquez sur **API keys**.
2. Sous **Create a key**, donnez à la clé un **Name** que vous reconnaîtrez plus tard, par exemple `CRM sync`.
3. Sous **Access**, gardez **Every scope, including ones added later**, ou choisissez **Only the scopes I choose** et cochez ceux dont cette clé a besoin. Voir [Scopes](#scopes).
4. Cliquez sur **Create key**.
5. Cliquez sur **Copy**, conservez la clé là où votre serveur peut la lire, par exemple dans une variable d'environnement, puis cliquez sur **I've saved it**.

Une clé se compose de `ic_live_` suivi de 32 lettres et chiffres. Elle ne s'affiche qu'une seule fois : intoCHAT n'en conserve qu'une empreinte, si bien que personne ne peut vous la montrer de nouveau. Si vous la perdez, révoquez-la et créez-en une nouvelle.

Une clé appartient au compte, pas à la personne qui l'a créée. Elle fonctionne pour tous les agents du compte, dans la limite de ses scopes, et continue de fonctionner si cette personne quitte l'équipe. Un compte peut avoir jusqu'à 20 clés actives.

Seuls le propriétaire du compte et les admins peuvent créer et révoquer des clés. Les editors et les viewers voient la liste des clés, avec uniquement leurs premiers caractères. Voir [Membres de l'équipe et rôles](/fr/docs/team).

### La liste des clés

Chaque clé affiche son nom, ses premiers caractères (par exemple `ic_live_3f9A…`), ses scopes, sa date de création et sa dernière utilisation. **Last used** est mis à jour au plus une fois par minute.

### Révoquer une clé

Cliquez sur **Revoke** à côté de la clé et confirmez. La clé cesse de fonctionner dès sa requête suivante, et cette action est irréversible. Révoquez une clé dès que vous pensez que quelqu'un d'autre pourrait la connaître.

Révoquer une clé supprime aussi les webhooks qu'elle a [abonnés](#abonner-un-webhook), par exemple pour Zapier ou Make, avec leur historique d'envois : rien d'autre ne pourrait les retirer. Les webhooks que vous avez ajoutés dans l'onglet **Leads** ne sont pas concernés.

## Authentification

Envoyez la clé dans l'en-tête `Authorization` de chaque requête :

```bash
curl https://www.intochat.ai/api/v1/agents \
  -H "Authorization: Bearer ic_live_YOUR_KEY"
```

Tous les points de terminaison se trouvent sous `https://www.intochat.ai/api/v1`. Les requêtes avec un corps envoient du JSON avec `Content-Type: application/json`, et chaque réponse est en JSON, sauf une réponse de chat en streaming. L'API n'utilise ni cookies ni session de connexion : la clé seule détermine ce qu'une requête peut faire.

Une clé absente, mal formée, inconnue ou révoquée reçoit une erreur `401` avec le code `unauthorized`.

## Scopes

Un scope permet à une clé d'utiliser un groupe de points de terminaison. Une clé créée avec **Every scope, including ones added later** peut utiliser tous les points de terminaison, y compris ceux ajoutés à l'avenir.

| Scope | Autorise |
| --- | --- |
| `agents:read` | [Lister les agents](#lister-les-agents) et [obtenir un agent](#obtenir-un-agent) |
| `chat` | [Envoyer un message](#envoyer-un-message) à un agent. Chaque réponse compte comme un message de votre offre. |
| `conversations:read` | [Lister les conversations](#lister-les-conversations) et [obtenir une conversation](#obtenir-une-conversation) avec sa transcription |
| `leads:read` | [Lister les leads](#lister-les-leads), [lister les contacts](#lister-les-contacts) et [obtenir un contact](#obtenir-un-contact), ainsi que les événements de lead, de formulaire, de réservation et de retour de [Lister les événements](#lister-les-evenements) et [Obtenir des exemples d'événements](#obtenir-des-exemples-d-evenements) |
| `contacts:write` | [Créer ou mettre à jour un contact](#creer-ou-mettre-a-jour-un-contact), [modifier un contact](#modifier-un-contact) et [supprimer un contact](#supprimer-un-contact) |
| `sources:read` | [Lister les sources](#lister-les-sources) |
| `sources:write` | [Ajouter une source](#ajouter-une-source) et [supprimer une source](#supprimer-une-source), ce qui lance aussi l'entraînement |
| `webhooks:write` | [Abonner un webhook](#abonner-un-webhook) et [désabonner un webhook](#desabonner-un-webhook) |

`conversations:read` couvre aussi les événements de transfert, de chat en direct et de nouvelle conversation de [Lister les événements](#lister-les-evenements).

Une requête vers un point de terminaison pour lequel la clé n'a pas de scope reçoit une erreur `403` avec le code `insufficient_scope`. Pour changer les scopes d'une clé, créez une nouvelle clé et révoquez l'ancienne.

## Limites de débit

Chaque clé peut effectuer 60 requêtes par minute, tous points de terminaison confondus. Chaque réponse qui a passé la vérification de la clé porte ces en-têtes :

| En-tête | Valeur |
| --- | --- |
| `X-RateLimit-Limit` | `60` |
| `X-RateLimit-Remaining` | Les requêtes restantes dans la minute en cours |
| `X-RateLimit-Reset` | La fin de la minute, en secondes Unix |

Au-delà de la limite, la requête reçoit une erreur `429` avec le code `rate_limited` et un en-tête `Retry-After` indiquant le nombre de secondes à attendre.

Le chat a deux limites supplémentaires qui ne dépendent pas de la clé : les messages de l'offre, et un agent répond à 1 000 messages par heure au maximum, widget et API confondus. Au-delà, une requête de chat reçoit une erreur `429` avec le code `agent_busy`.

## Erreurs

Toutes les erreurs ont la même forme :

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This API key doesn't have the \"chat\" scope."
  }
}
```

Utilisez `code` dans votre code ; `message` l'explique à une personne et peut changer.

| Statut | Code | Signification |
| --- | --- | --- |
| `400` | `invalid_request` | Il manque quelque chose à la requête ou une valeur est incorrecte. Le message précise laquelle. |
| `401` | `unauthorized` | Aucune clé, ou la clé est mal formée, inconnue ou révoquée. |
| `403` | `insufficient_scope` | La clé n'a pas le scope dont ce point de terminaison a besoin. |
| `403` | `plan_required` | Le compte est sur l'offre Free. L'API est incluse à partir de Starter. |
| `403` | `message_limit_reached` | Le compte a utilisé tous les messages que son offre inclut pour cette période. |
| `403` | `agent_cap_reached` | L'agent a atteint le plafond mensuel de messages défini sous **Share**, **Limits and access**. |
| `403` | `agent_private` | L'agent est privé : il ne répond donc que dans le tableau de bord. Voir [Agents privés](/fr/docs/allowed-domains#agents-prives). |
| `403` | `character_limit_exceeded` | Une nouvelle source ferait dépasser à l'agent les caractères d'entraînement de son offre. |
| `403` | `contact_limit_reached` | Un nouveau contact ferait dépasser à l'agent les contacts par agent de son offre. |
| `404` | `not_found` | Aucun agent, aucune conversation, aucune source, aucun contact ou aucun webhook de ce type dans ce compte. |
| `409` | `conflict` | Un autre contact de l'agent a déjà cet identifiant externe ou cet e-mail. |
| `429` | `rate_limited` | La clé a effectué plus de 60 requêtes au cours de cette minute. |
| `429` | `agent_busy` | L'agent répond à trop de messages en ce moment. |
| `500` | `internal_error` | Un problème est survenu de notre côté. Réessayez. |
| `504` | `timeout` | L'agent a mis trop de temps à répondre. |

Un agent, une conversation ou une source qui appartient à un autre compte reçoit `404`, comme un élément qui n'existe pas.

## Pagination

[Lister les conversations](#lister-les-conversations) et [lister les leads](#lister-les-leads) renvoient une page à la fois, les plus récents en premier. Ces points de terminaison acceptent ces paramètres de requête :

| Paramètre | Contenu |
| --- | --- |
| `limit` | Éléments par page, de 1 à 100. La valeur par défaut est 20. |
| `cursor` | Le `nextCursor` de la page précédente, pour obtenir la suivante. |
| `since` | Une heure ISO 8601, par exemple `2026-10-01T00:00:00Z`. Seuls les éléments ayant une activité à partir de ce moment sont renvoyés : une conversation avec un message depuis, ou un lead enregistré ou envoyé de nouveau depuis. |

Une page se présente ainsi :

```json
{
  "data": [ ],
  "hasMore": true,
  "nextCursor": "MjAyNi0xMC0wNFQwOTowMDowMC4wMDBafGNsdjEyMw"
}
```

Demandez la page suivante avec `?cursor=` et cette valeur, en gardant les mêmes `limit` et `since`, jusqu'à ce que `hasMore` vaille `false` et `nextCursor` vaille `null`. L'ordre est fixé par la date de création : une nouvelle activité pendant que vous parcourez les pages ne fait donc ni apparaître un élément deux fois ni disparaître un élément. Pour synchroniser régulièrement, enregistrez l'heure à laquelle vous avez lancé la dernière synchronisation et transmettez-la comme `since` la fois suivante.

## Lister les agents

`GET /api/v1/agents` · scope `agents:read`

Tous les agents du compte, les plus récents en premier.

```bash
curl https://www.intochat.ai/api/v1/agents \
  -H "Authorization: Bearer ic_live_YOUR_KEY"
```

```json
{
  "data": [
    {
      "id": "AGENT_ID",
      "name": "Acme Assistant",
      "private": false,
      "status": "trained",
      "trainedAt": "2026-10-02T09:00:00.000Z",
      "characters": 48210,
      "counts": { "conversations": 312, "messages": 2240, "leads": 41, "sources": 18 },
      "createdAt": "2026-09-01T09:00:00.000Z",
      "updatedAt": "2026-10-02T09:00:00.000Z"
    }
  ]
}
```

`status` vaut `untrained`, `training`, `trained` ou `error` (le dernier entraînement a échoué). `characters` indique combien de caractères d'entraînement utilisent les sources entraînées de l'agent.

## Obtenir un agent

`GET /api/v1/agents/{agentId}` · scope `agents:read`

Un agent, dans `data`, avec les mêmes champs que dans [Lister les agents](#lister-les-agents).

## Envoyer un message

`POST /api/v1/agents/{agentId}/chat` · scope `chat`

Envoie un message à l'agent et renvoie sa réponse. L'agent répond comme dans le widget : à partir de ses connaissances et de ses instructions, avec vos [actions API](/fr/docs/api-actions), [boutons personnalisés](/fr/docs/custom-buttons), [formulaires de chat](/fr/docs/chat-forms), la [réservation](/fr/docs/booking), le [transfert par e-mail](/fr/docs/handoff) et le [chat en direct](/fr/docs/live-chat), là où vous les avez configurés.

| Champ | Contenu |
| --- | --- |
| `message` | Obligatoire. Le texte, jusqu'à 10 000 caractères. |
| `conversationId` | Facultatif. Poursuit une conversation que vous avez démarrée via l'API, avec le `conversationId` d'une réponse précédente. |
| `sessionId` | Facultatif. Votre propre identifiant pour la personne au nom de laquelle vous discutez, par exemple votre identifiant utilisateur, jusqu'à 100 caractères. La même valeur poursuit toujours la même conversation. Vous pouvez aussi envoyer le `sessionId` d'une réponse précédente. |
| `stream` | Facultatif. `true` envoie le texte de la réponse en streaming ; voir [Streaming](#streaming). La valeur par défaut est `false`. |
| `timeZone` | Facultatif. Le fuseau horaire IANA de la personne, par exemple `Europe/Berlin`, pour les horaires de réservation. Sans lui, les horaires sont en UTC. |

Sans `conversationId` ni `sessionId`, chaque message démarre une nouvelle conversation.

```bash
curl https://www.intochat.ai/api/v1/agents/AGENT_ID/chat \
  -H "Authorization: Bearer ic_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message": "Do you ship to Switzerland?", "sessionId": "customer-4821" }'
```

```json
{
  "reply": "Yes, we ship to Switzerland. Delivery takes 3 to 5 working days.",
  "conversationId": "CONVERSATION_ID",
  "sessionId": "icapi5f0c2b9e4d7a41f8a3c6e1b2d9f07a64",
  "sources": [
    { "title": "Shipping", "url": "https://www.example.com/shipping" }
  ],
  "followUps": ["How much does shipping cost?", "Can I track my order?"],
  "buttons": [],
  "products": [],
  "form": null,
  "leadForm": null,
  "booking": null,
  "liveChat": null
}
```

| Champ | Contenu |
| --- | --- |
| `reply` | La réponse de l'agent en texte brut avec Markdown, sans les blocs de contrôle du widget. Vide tant qu'une personne de votre équipe a la main sur le chat (voir `liveChat`). |
| `conversationId` | La conversation, comme dans [Obtenir une conversation](#obtenir-une-conversation). |
| `sessionId` | La session de la conversation dans intoCHAT. Elle commence toujours par `icapi`. |
| `sources` | Les pages sur lesquelles s'appuie la réponse, avec `title` et `url`, quand **Show sources under replies** est activé. Les pages issues de la [recherche web](/fr/docs/web-search) sont toujours listées, avec `"web": true`. |
| `followUps` | Les questions suivantes suggérées, quand **Suggest follow-up questions** est activé. |
| `buttons` | Les [boutons personnalisés](/fr/docs/custom-buttons) choisis par l'agent, avec `label` et `url`. |
| `products` | Les fiches produits de votre [boutique](/fr/docs/connect-your-store) ou de vos actions API, avec `name`, `url` et, quand ils sont connus, `image`, `price`, `compareAtPrice`, `currency` et `available`. |
| `form` | Un [formulaire de chat](/fr/docs/chat-forms) proposé par l'agent, avec son `formId`, son `name`, ses `fields` et son `submitLabel`, ou `null`. L'API ne peut pas l'envoyer ; affichez-le dans votre propre interface ou ignorez-le. |
| `leadForm` | Le [formulaire de leads](/fr/docs/lead-collection), avec son `message`, ses `fields` de contact et `form`, quand l'agent a demandé des coordonnées, ou `null`. Les coordonnées écrites dans un message sont enregistrées comme lead quand **Save contact details written in the chat** est activé, comme dans le widget. |
| `booking` | Les créneaux de rendez-vous disponibles, avec `provider`, `eventTitle`, `eventLength` en minutes, `timeZone` et `days`, chacun avec sa `date` et ses `slots` en heures UTC, ou `null`. L'API ne peut pas réserver de créneau. |
| `liveChat` | `null`, ou l'état du [chat en direct](/fr/docs/live-chat) : `offered` (l'agent peut demander à votre équipe de rejoindre le chat), `requested` (il le lui a demandé) ou `paused` (un membre de l'équipe a la main sur le chat, l'agent n'a donc pas répondu). |

### Différences entre les chats API et le widget

- La conversation est enregistrée comme un chat du widget et apparaît dans l'onglet **Conversations** de l'agent, avec ses leads, ses notes et ses statistiques. Elle n'a ni pays ni page.
- Les [domaines autorisés](/fr/docs/allowed-domains), les [pays bloqués](/fr/docs/limits-and-access#pays-bloques) et les [messages par visiteur](/fr/docs/limits-and-access#messages-par-visiteur) ne s'appliquent pas : il n'y a ni page ni adresse de visiteur à vérifier. Les messages de l'offre, le plafond mensuel de l'agent et la limite de débit de la clé s'appliquent.
- Un [agent privé](/fr/docs/allowed-domains#agents-prives) refuse les chats API avec `agent_private`.
- Les [actions côté client](/fr/docs/client-actions) s'exécutent dans le navigateur d'un visiteur : elles ne sont donc pas proposées à l'agent dans les chats API. Les actions API, les boutons, les formulaires et la réservation fonctionnent.
- Impossible de joindre des fichiers, et il n'y a pas de chats temporaires.
- Seules les conversations démarrées via l'API peuvent être poursuivies via l'API. L'agent voit les 40 derniers messages de la conversation comme historique.
- Tant qu'une personne de votre équipe a pris la main sur le chat dans la [boîte de réception Live chat](/fr/docs/live-chat#la-boite-de-reception-live-chat), votre message est enregistré, `reply` est vide et `liveChat` vaut `paused`. Les réponses du membre de l'équipe apparaissent dans la conversation : lisez-les avec [Obtenir une conversation](#obtenir-une-conversation), où elles ont un `authorName`. Un message pendant une prise en main ne consomme aucun message de votre offre.
- Une réponse qui prend plus de 55 secondes se termine par une erreur `504` avec le code `timeout`. Elle peut tout de même compter comme un message.

### Streaming

Avec `"stream": true`, la réponse est le texte de la réponse au fur et à mesure de sa rédaction, en `text/plain`, sans les champs JSON. Les en-têtes `X-Conversation-Id` et `X-Session-Id` indiquent la conversation et la session, et `X-Live-Chat` l'état du chat en direct s'il y en a un. Les sources, les boutons et les autres blocs ne sont pas envoyés en streaming ; lisez-les ensuite avec [Obtenir une conversation](#obtenir-une-conversation). Les erreurs sont en JSON, comme sans streaming.

```bash
curl -N https://www.intochat.ai/api/v1/agents/AGENT_ID/chat \
  -H "Authorization: Bearer ic_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message": "What are your opening hours?", "stream": true }'
```

## Lister les conversations

`GET /api/v1/agents/{agentId}/conversations` · scope `conversations:read`

Les conversations de l'agent, depuis le widget et l'API, les plus récentes en premier, sans leurs messages. Accepte `limit`, `cursor` et `since` ; voir [Pagination](#pagination).

```bash
curl "https://www.intochat.ai/api/v1/agents/AGENT_ID/conversations?limit=50&since=2026-10-01T00:00:00Z" \
  -H "Authorization: Bearer ic_live_YOUR_KEY"
```

```json
{
  "data": [
    {
      "id": "CONVERSATION_ID",
      "sessionId": "8f14e45f-ceea-467a-9575-1a2b3c4d5e6f",
      "channel": "widget",
      "createdAt": "2026-10-04T09:00:00.000Z",
      "lastActivityAt": "2026-10-04T09:12:00.000Z",
      "country": "CH",
      "page": "https://www.example.com/pricing",
      "messageCount": 6,
      "topics": ["Pricing"],
      "sentiment": "positive",
      "liveChat": null,
      "csatScore": null
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

`channel` vaut `api` pour une conversation démarrée via l'API, sinon `widget` (la balise script, l'iframe dans la page, le lien direct et le Playground). `topics` et `sentiment` sont définis par l'[analyse des thèmes](/fr/docs/conversations-and-dashboard#themes-et-sentiment) quand elle est activée. `liveChat` vaut `null`, `requested`, `active` ou `ended`. `csatScore` est la note de 1 à 5 donnée par le visiteur après un chat en direct, ou `null`.

## Obtenir une conversation

`GET /api/v1/conversations/{conversationId}` · scope `conversations:read`

Une conversation de n'importe quel agent du compte, avec tous ses messages dans l'ordre.

```json
{
  "data": {
    "id": "CONVERSATION_ID",
    "agentId": "AGENT_ID",
    "sessionId": "icapi5f0c2b9e4d7a41f8a3c6e1b2d9f07a64",
    "channel": "api",
    "createdAt": "2026-10-04T09:00:00.000Z",
    "lastActivityAt": "2026-10-04T09:14:00.000Z",
    "country": null,
    "page": null,
    "topics": [],
    "sentiment": null,
    "handoffRequestedAt": null,
    "liveChat": { "status": "ended", "startedAt": "2026-10-04T09:05:00.000Z", "endedAt": "2026-10-04T09:13:00.000Z" },
    "csat": { "score": 5, "comment": "Quick and friendly.", "ratedAt": "2026-10-04T09:14:00.000Z" },
    "messages": [
      { "id": "MESSAGE_ID", "role": "assistant", "content": "Hello! How can I help you today?", "createdAt": "2026-10-04T09:00:00.000Z", "authorName": null, "sources": [] },
      { "id": "MESSAGE_ID", "role": "user", "content": "Can I change my delivery address?", "createdAt": "2026-10-04T09:01:00.000Z", "authorName": null, "sources": [] },
      { "id": "MESSAGE_ID", "role": "assistant", "content": "Yes, I'm on it. What's your order number?", "createdAt": "2026-10-04T09:06:00.000Z", "authorName": "Anna", "sources": [] }
    ]
  }
}
```

`role` vaut `user` pour le visiteur ou vos messages API, et `assistant` pour l'agent et votre équipe. `authorName` est le prénom du membre de l'équipe qui a rédigé une réponse en [chat en direct](/fr/docs/live-chat), ou `null` quand c'est l'agent qui l'a rédigée. `content` est le texte sans les blocs de contrôle du widget ; les `sources` d'une réponse de l'agent listent les pages qu'elle a affichées. `handoffRequestedAt` est le moment où l'agent a envoyé la conversation à votre équipe par e-mail, ou `null`. `liveChat` et `csat` valent `null` quand il n'y a eu ni chat en direct ni note.

## Lister les leads

`GET /api/v1/agents/{agentId}/leads` · scope `leads:read`

Les leads de l'agent, les plus récents en premier. Accepte `limit`, `cursor` et `since` ; voir [Pagination](#pagination).

```json
{
  "data": [
    {
      "id": "LEAD_ID",
      "email": "jane@example.com",
      "phone": null,
      "name": "Jane",
      "customFields": [{ "id": "FIELD_ID", "label": "Company", "value": "Acme" }],
      "source": "form",
      "conversationId": "CONVERSATION_ID",
      "collectedAt": "2026-10-04T09:03:00.000Z",
      "lastSeenAt": "2026-10-04T09:03:00.000Z",
      "submissionCount": 1
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

`source` vaut `form` pour le formulaire de leads ou `chat` pour des coordonnées écrites dans un message. `customFields` utilise les libellés actuels de votre formulaire. `lastSeenAt` et `submissionCount` changent quand la même personne envoie de nouveau ses coordonnées. `conversationId` vaut `null` quand la conversation a été supprimée.

## Contacts

Les [contacts](/fr/docs/contacts) d'un agent : visiteurs vérifiés, leads, contacts importés et ceux que vous ajoutez ici, chacun avec jusqu'à 50 attributs personnalisés. Les lire nécessite `leads:read` ; les créer, les modifier et les supprimer nécessite `contacts:write`. Les clés créées avec **Every scope** ont les deux.

Un contact se présente ainsi :

```json
{
  "id": "CONTACT_ID",
  "agentId": "AGENT_ID",
  "externalId": "user-4711",
  "email": "jane@example.com",
  "phone": "+41441234567",
  "name": "Jane",
  "attributes": { "plan": "pro", "seats": 5, "vip": true },
  "source": "api",
  "firstSeenAt": "2026-10-04T09:03:00.000Z",
  "lastSeenAt": "2026-10-04T09:03:00.000Z",
  "createdAt": "2026-10-04T09:03:00.000Z",
  "updatedAt": "2026-10-04T09:03:00.000Z"
}
```

`source` vaut `identity`, `lead`, `import` ou `api` : la provenance d'origine du contact. `externalId` est l'identifiant d'utilisateur de la personne sur votre site. Les e-mails sont enregistrés en minuscules.

### Lister les contacts

`GET /api/v1/agents/{agentId}/contacts` · scope `leads:read`

Les contacts de l'agent, les plus récents en premier. Accepte `limit`, `cursor` et `since` (les contacts modifiés à partir de ce moment) ; voir [Pagination](#pagination). Ajoutez `email=` ou `external_id=` pour rechercher un contact.

```bash
curl "https://www.intochat.ai/api/v1/agents/AGENT_ID/contacts?external_id=user-4711" \
  -H "Authorization: Bearer ic_live_YOUR_KEY"
```

```json
{ "data": [ { "id": "CONTACT_ID", "externalId": "user-4711", "email": "jane@example.com", "attributes": { "plan": "pro" } } ], "hasMore": false, "nextCursor": null }
```

`data` contient tous les champs présentés plus haut ; certains sont omis ici.

### Créer ou mettre à jour un contact

`POST /api/v1/agents/{agentId}/contacts` · scope `contacts:write`

```bash
curl https://www.intochat.ai/api/v1/agents/AGENT_ID/contacts \
  -H "Authorization: Bearer ic_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "external_id": "user-4711", "email": "jane@example.com", "name": "Jane", "attributes": { "plan": "pro", "seats": 5 } }'
```

| Champ | Contenu |
| --- | --- |
| `external_id` | Jusqu'à 200 caractères. Envoyez ce champ ou `email`. |
| `email` | Une adresse e-mail valide. |
| `phone` | De 7 à 15 chiffres, éventuellement précédés de `+`. |
| `name` | Jusqu'à 100 caractères. |
| `attributes` | Un objet de noms et de valeurs d'attributs. Les noms commencent par une lettre minuscule et utilisent des lettres minuscules, des chiffres et des tirets bas, jusqu'à 40 caractères ; voir [Attributs](/fr/docs/contacts#attributs). Une valeur est un texte de 1 000 caractères au plus, un nombre ou `true`/`false`, et `null` retire l'attribut. |

Le contact qui a cet `external_id` est mis à jour, sinon celui qui a cet `email`, sinon un nouveau contact est créé. Un contact sans identifiant externe retrouvé ainsi par son e-mail reçoit l'`external_id`. Les champs que vous envoyez remplacent ce qui est enregistré, un champ défini à `null` est vidé, et les champs que vous omettez restent inchangés. Les attributs sont fusionnés : ceux que vous envoyez sont définis, les autres sont conservés.

```json
{ "data": { "id": "CONTACT_ID", "externalId": "user-4711", "email": "jane@example.com", "attributes": { "plan": "pro", "seats": 5 } }, "result": "created" }
```

`result` vaut `created` (statut `201`) ou `updated` (statut `200`). Un e-mail qui appartient à un contact ayant un autre identifiant externe, ou un identifiant externe ou un e-mail qu'un autre contact a déjà, reçoit une erreur `409` avec le code `conflict`. Un nouveau contact au-delà des [contacts par agent](/fr/docs/plans-and-limits#contacts) de votre offre reçoit une erreur `403` avec le code `contact_limit_reached`.

### Obtenir un contact

`GET /api/v1/contacts/{contactId}` · scope `leads:read`

Un contact de n'importe quel agent du compte, sous la forme `{ "data": { … } }`.

### Modifier un contact

`PATCH /api/v1/contacts/{contactId}` · scope `contacts:write`

Accepte les mêmes champs que [Créer ou mettre à jour un contact](#creer-ou-mettre-a-jour-un-contact), tous facultatifs, et répond avec le contact sous la forme `{ "data": { … } }`. Un contact doit garder un identifiant externe, un e-mail ou un numéro de téléphone.

### Supprimer un contact

`DELETE /api/v1/contacts/{contactId}` · scope `contacts:write`

```json
{ "data": { "id": "CONTACT_ID", "deleted": true } }
```

Les leads et les conversations du contact sont conservés.

## Lister les sources

`GET /api/v1/agents/{agentId}/sources` · scope `sources:read`

Les sources de connaissances de l'agent, dans l'ordre de son onglet **Knowledge**, avec la progression de l'entraînement. Le texte d'une source n'est pas inclus, sauf la question et la réponse d'une paire de questions-réponses.

```json
{
  "data": [
    {
      "id": "SOURCE_ID",
      "type": "qa",
      "title": "Do you ship to Switzerland?",
      "url": null,
      "filename": null,
      "question": "Do you ship to Switzerland?",
      "answer": "Yes, in 3 to 5 working days.",
      "characters": 71,
      "status": "trained",
      "error": null,
      "trainedAt": "2026-10-02T09:00:00.000Z",
      "createdAt": "2026-10-02T08:59:00.000Z",
      "updatedAt": "2026-10-02T09:00:00.000Z"
    }
  ],
  "training": { "total": 18, "trained": 18, "pending": 0, "processing": 0, "failed": 0, "done": true },
  "characters": { "used": 48210, "limit": 500000 }
}
```

`type` vaut `website`, `file`, `text`, `qa` ou `notion`. `status` vaut `pending`, `processing`, `trained` ou `failed`, avec la raison dans `error`. `characters.limit` correspond aux caractères d'entraînement par agent de votre offre.

## Ajouter une source

`POST /api/v1/agents/{agentId}/sources` · scope `sources:write`

Ajoute un [extrait de texte ou une paire de questions-réponses](/fr/docs/text-and-qa) et lance l'entraînement, comme le fait l'onglet **Knowledge**. L'agent utilise la nouvelle source une fois qu'elle est entraînée ; suivez son `status` avec [Lister les sources](#lister-les-sources).

Un extrait de texte :

```bash
curl https://www.intochat.ai/api/v1/agents/AGENT_ID/sources \
  -H "Authorization: Bearer ic_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "type": "text", "title": "Opening hours", "content": "We are open Monday to Friday, 9:00 to 17:00." }'
```

Une paire de questions-réponses :

```json
{ "type": "qa", "question": "Do you ship to Switzerland?", "answer": "Yes, in 3 to 5 working days." }
```

```json
{
  "data": { "id": "SOURCE_ID", "type": "text", "title": "Opening hours", "status": "pending", "characters": 44 },
  "result": "created",
  "training": { "total": 19, "trained": 18, "pending": 1, "processing": 0, "failed": 0, "done": false }
}
```

`data` contient tous les champs de [Lister les sources](#lister-les-sources) ; certains sont omis ci-dessus. `result` vaut `created` (statut `201`), `updated` quand une paire de questions-réponses avec la même question, sans tenir compte des majuscules et minuscules, existait déjà et a maintenant la nouvelle réponse, ou `unchanged` quand elle avait exactement cette réponse (tous deux avec le statut `200`). Un extrait de texte est toujours ajouté comme nouvelle source. Un titre et une question peuvent chacun compter jusqu'à 200 caractères.

La source compte dans les caractères d'entraînement de l'agent. Une source qui dépasserait la limite de votre offre reçoit une erreur `403` avec le code `character_limit_exceeded`, et rien n'est ajouté. Voir [Caractères d'entraînement par agent](/fr/docs/plans-and-limits#caracteres-d-entrainement-par-agent).

## Supprimer une source

`DELETE /api/v1/agents/{agentId}/sources/{sourceId}` · scope `sources:write`

Supprime immédiatement la source et ce que l'agent en a appris, comme le fait la suppression dans l'onglet **Knowledge**. Rien d'autre n'a besoin d'être réentraîné.

```json
{ "id": "SOURCE_ID", "deleted": true, "training": { "total": 17, "trained": 17, "pending": 0, "processing": 0, "failed": 0, "done": true } }
```

## Abonner un webhook

`POST /api/v1/agents/{agentId}/webhooks` · scope `webhooks:write`

Abonne un webhook aux événements de l'agent : un REST hook, tel que l'utilisent les déclencheurs instantanés de Zapier et de Make. intoCHAT envoie ensuite chaque événement à l'adresse comme décrit dans [Webhooks](/fr/docs/webhooks#ce-qui-est-envoye), signé et retenté comme un webhook ajouté dans l'onglet **Leads**.

| Champ | Contenu |
| --- | --- |
| `url` | Obligatoire. L'adresse à laquelle envoyer les événements. Comme dans l'onglet **Leads**, elle doit commencer par `https://` et pointer vers un serveur public ; les adresses de réseaux privés ou internes sont refusées. |
| `events` | Obligatoire. Un ou plusieurs événements parmi `lead.created`, `handoff.requested`, `form.submitted`, `booking.created`, `live_chat.requested` et `return.requested`. Voir [Événements](/fr/docs/webhooks#evenements). |
| `source` | Facultatif. `zapier`, `make` ou `api` (la valeur par défaut) : ce que l'onglet **Leads** affiche à côté du webhook, par exemple **via Zapier**. |

```bash
curl https://www.intochat.ai/api/v1/agents/AGENT_ID/webhooks \
  -H "Authorization: Bearer ic_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://hooks.example.com/intochat", "events": ["lead.created"], "source": "api" }'
```

```json
{
  "id": "WEBHOOK_ID",
  "agentId": "AGENT_ID",
  "url": "https://hooks.example.com/intochat",
  "events": ["lead.created"],
  "source": "api",
  "secret": "whsec_…",
  "createdAt": "2026-10-08T09:00:00.000Z"
}
```

La réponse a le statut `201`. Conservez `id` pour vous désabonner plus tard. `secret` est le secret de signature du webhook, renvoyé uniquement ici ; utilisez-le pour [vérifier la signature](/fr/docs/webhooks#verifier-la-signature) si votre récepteur le peut. Les outils qui ne savent pas vérifier les signatures, comme Zapier et Make, peuvent l'ignorer.

Un webhook abonné apparaît dans l'onglet **Leads** de l'agent avec un badge **via Zapier**, **via Make** ou **via API**, où le propriétaire peut le mettre en pause ou le retirer. Il garde en mémoire la clé qui l'a abonné : [révoquer cette clé](#revoquer-une-cle) le supprime. Un agent peut avoir jusqu'à 25 webhooks abonnés, en plus des 5 ajoutés dans l'onglet **Leads** ; au-delà, la requête reçoit une erreur `400`.

## Désabonner un webhook

`DELETE /api/v1/webhooks/{webhookId}` · scope `webhooks:write`

Supprime un webhook abonné via l'API, avec son historique d'envois. N'importe quelle clé du même compte peut le faire. Un webhook ajouté dans l'onglet **Leads**, le webhook d'un autre compte ou un webhook déjà retiré reçoit `404`.

```json
{ "id": "WEBHOOK_ID", "deleted": true }
```

## Lister les événements

`GET /api/v1/agents/{agentId}/events/{event}` · scope `agents:read`, plus `leads:read` ou `conversations:read`

Les derniers événements d'un type pour l'agent, les plus récents en premier, chacun exactement de la même forme que le corps d'un [envoi de webhook](/fr/docs/webhooks#ce-qui-est-envoye). Les outils d'automatisation l'interrogent au lieu d'attendre un webhook, par exemple pour afficher de vrais exemples de données. Accepte `limit`, de 1 à 100 (20 par défaut).

| `event` | Nécessite aussi | Construit à partir de |
| --- | --- | --- |
| `lead.created` | `leads:read` | Les leads, les derniers enregistrés en premier |
| `form.submitted` | `leads:read` | Les formulaires reçus |
| `booking.created` | `leads:read` | Les réservations |
| `return.requested` | `leads:read` | Les demandes de retour |
| `handoff.requested` | `conversations:read` | Les conversations transférées par e-mail |
| `live_chat.requested` | `conversations:read` | Les conversations dans lesquelles l'agent a demandé à votre équipe de rejoindre le chat |
| `conversation.created` | `conversations:read` | Les nouvelles conversations. Cet événement n'a pas de webhook ; il peut seulement être listé. |

```bash
curl "https://www.intochat.ai/api/v1/agents/AGENT_ID/events/lead.created?limit=5" \
  -H "Authorization: Bearer ic_live_YOUR_KEY"
```

```json
{
  "data": [
    {
      "id": "LEAD_ID",
      "event": "lead.created",
      "created_at": "2026-10-07T09:30:00.000Z",
      "agent": { "id": "AGENT_ID", "name": "Acme Assistant" },
      "data": {
        "leadId": "LEAD_ID",
        "agentId": "AGENT_ID",
        "agentName": "Acme Assistant",
        "email": "jane@example.com",
        "phone": null,
        "name": "Jane",
        "customFields": [],
        "source": "form",
        "conversationId": "CONVERSATION_ID",
        "collectedAt": "2026-10-07T09:30:00.000Z"
      }
    }
  ]
}
```

Les différences avec un envoi :

- `id` est l'identifiant de l'enregistrement, pas un identifiant d'envoi : un même événement a donc toujours le même `id`, à savoir l'identifiant du lead, de l'envoi de formulaire, de la réservation, de la demande de retour ou de la conversation. Pour les transferts et les demandes de chat en direct, il s'agit de l'identifiant de la conversation, suivi de deux-points et de l'heure en millisecondes, car une conversation peut redemander plus tard.
- `created_at` est le moment où l'enregistrement a été sauvegardé.
- Les données sont reconstituées à partir de ce qu'intoCHAT conserve. Le `summary` d'un transfert n'est pas conservé : il vaut donc `null`, et `test` vaut `false`. Un transfert qui a ouvert un [ticket helpdesk](/fr/docs/helpdesk-tickets) contient son `ticket`, comme le webhook. Le `providerStatus` d'une réservation vaut `pending` tant qu'elle attend votre confirmation, sinon `accepted`. Une demande de retour a son `status` actuel, `requested` ou `handled`.
- Les données de `conversation.created` contiennent `conversationId`, `channel` (`widget` ou `api`), `page`, `country` et `createdAt`.

## Obtenir des exemples d'événements

`GET /api/v1/agents/{agentId}/events/{event}/samples` · scope `agents:read`

Jusqu'à 3 des derniers événements de l'agent, comme dans [Lister les événements](#lister-les-evenements), pour qu'un outil ait toujours des données à partir desquelles associer les champs. Quand l'agent n'en a pas encore, ou que la clé n'a pas le scope dont cet événement a besoin (`leads:read` ou `conversations:read`), il renvoie à la place un exemple documenté avec les mêmes champs, et `sample` vaut `true`.

```json
{
  "data": [
    {
      "id": "sample_booking.created",
      "event": "booking.created",
      "created_at": "2026-10-07T09:30:00.000Z",
      "agent": { "id": "AGENT_ID", "name": "Acme Assistant" },
      "data": { "bookingId": "sample_booking", "eventTitle": "30 min intro call", "attendeeEmail": "jane@example.com" }
    }
  ],
  "sample": true
}
```

Le `data` de l'exemple contient tous les champs de l'événement ; certains sont omis ci-dessus.

## Limites

- L'API est incluse à partir de Starter. Avec l'offre Free, aucune clé ne peut être créée et les clés existantes reçoivent `plan_required`.
- 60 requêtes par minute et par clé, et 20 clés actives au maximum par compte.
- Chaque réponse de chat compte comme un message de votre offre. Un agent répond à 1 000 messages par heure au maximum, widget et API confondus.
- Une réponse de chat doit se terminer en 55 secondes.
- Sources : extraits de texte et paires de questions-réponses uniquement. Les pages de site, les fichiers et les pages Notion s'ajoutent dans le tableau de bord.
- Jusqu'à 25 webhooks abonnés par agent, en plus des 5 ajoutés dans l'onglet **Leads**.
- Les contacts sont créés et mis à jour un par un. Pour en ajouter beaucoup à la fois, utilisez [Importer un fichier CSV](/fr/docs/contacts#importer-un-fichier-csv) dans le tableau de bord.
- Il n'existe pas encore de points de terminaison pour créer ou modifier des agents, envoyer des formulaires, réserver des rendez-vous ou prendre la main sur des chats en direct.

## Étapes suivantes

- Recevez les leads et les transferts sur votre serveur au moment où ils se produisent : [Webhooks](/fr/docs/webhooks).
- Utilisez intoCHAT dans Zapier ou Make : [Zapier et Make](/fr/docs/zapier-and-make).
- Laissez votre agent appeler votre propre API pendant qu'il répond : [Actions API](/fr/docs/api-actions).
- Rédigez de bons extraits de texte et de bonnes paires de questions-réponses : [Texte et questions-réponses](/fr/docs/text-and-qa).
- Vérifiez ce que comprend votre offre : [Offres et limites](/fr/docs/plans-and-limits).
