# Conversations

> Lister, compter, lire et supprimer les conversations de l'utilisateur de la clé API.

Une conversation est l'historique enregistré d'un chat : les messages échangés avec un modèle ou
un [assistant](assistants.md), son nom et quelques liens (assistant, organisation, tâche,
exécution de workflow). Les conversations sont créées par l'endpoint [chat](chat.md) ; il n'existe pas
d'endpoint pour en créer ou en modifier une directement. Pour poursuivre une conversation, envoyez son `id` comme
`conversation_id` à [chat](chat.md).

Les quatre endpoints ne voient que les conversations **de l'utilisateur de la clé API** :

- les conversations que l'utilisateur a démarrées dans l'application ou via l'API, à titre personnel et dans
  toute organisation à laquelle il appartient ;
- les conversations démarrées pour le compte de l'utilisateur par des tâches planifiées et des exécutions de workflows.

Les conversations des autres utilisateurs ne sont jamais renvoyées, même lorsqu'elles appartiennent à la même
organisation ou qu'elles sont partagées publiquement par un lien. Une conversation qui existe mais qui ne vous
appartient pas est signalée comme introuvable.

Le coût d'une conversation est disponible via l'[endpoint de coût d'utilisation](account.md).

## Lister les conversations

| | |
|---|---|
| Endpoint | `POST /api/v3/conversation/find` |
| Portée | `ayeto.conversation` |
| Limite de débit | [par défaut](conventions.md#limites-de-debit) |

Renvoie les conversations de l'utilisateur qui correspondent aux filtres, les plus récentes en premier, sauf si vous
triez autrement. Chaque conversation est renvoyée avec l'intégralité de son historique de messages : paginez donc toujours
les résultats.

### Requête

Le corps est un objet de requête avec les champs habituels de [filtres, tri et pagination](conventions.md#filtres).
Envoyez `{}` pour tout obtenir ; le corps lui-même est obligatoire.

| Champ | Type | Obligatoire | Description |
|---|---|---|---|
| `filters` | tableau | Non | Conditions de filtre, voir [filtres](conventions.md#filtres) et les [exemples](#filtres-utiles) ci-dessous. Plusieurs conditions sont combinées avec AND. |
| `sort_key` | chaîne | Non | Champ de tri, par exemple `created.timestamp`, `updated.timestamp`, `accessed.timestamp` ou `name`. Par défaut : `created.timestamp`, décroissant. |
| `sort_order` | entier | Non | `0` croissant, `1` décroissant. Par défaut : `0` lorsque `sort_key` est défini. |
| `limit_from` | entier | Non | Index du premier résultat (à partir de zéro). Sans lui, toutes les conversations correspondantes sont renvoyées. |
| `limit_to` | entier | Non | Index suivant le dernier résultat (exclusif), et non une taille de page : `limit_from: 20, limit_to: 40` renvoie les résultats aux positions 20 à 39 (en comptant à partir de 0). |

Pour tout lire, demandez des pages jusqu'à ce qu'une page revienne plus courte que demandé. Le
total pour une pagination s'obtient avec [compter les conversations](#compter-les-conversations).

### Filtres utiles

Les filtres peuvent porter sur n'importe quel champ de la [conversation](#conversation), y compris les champs des
messages (`messages.content`, `messages.role`, `messages.model`). Les conditions sur les messages
sont vérifiées sur chaque message enregistré, y compris les résultats d'outils et les autres messages
qui ne sont pas renvoyés dans `messages`.

| Objectif | Filtre |
|---|---|
| Conversations avec un assistant donné | `["assistant_id", "==", "3f6c1a2e-8b4d-4e7a-9c1f-2d5e6a7b8c90"]` |
| Conversations personnelles uniquement (sans organisation) | `["organization_id", "==", null]` |
| Conversations dans une organisation donnée | `["organization_id", "==", "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"]` |
| Conversations que vous avez menées vous-même, sans les exécutions de tâches et de workflows | `["task_id", "==", null]`, `["task_execution_id", "==", null]`, `["workflow_run_id", "==", null]` |
| Conversations créées par un workflow donné | `["workflow_id", "==", "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a"]` |
| Modifiées depuis un instant donné (ms depuis l'epoch) | `["updated.timestamp", ">=", 1759363200000]` |
| Le nom contient un mot (insensible à la casse) | `["name", "regex", "invoice"]` |
| Un message contient une expression (insensible à la casse) | `["messages.content", "regex", "delivery date"]` |
| Démarrées avec l'un de plusieurs modèles | `["model", "in", ["gpt-5", "claude-sonnet-4-5"]]` |
| Une réponse rédigée par un modèle donné | `["messages.model", "==", "gpt-5"]` |

Les filtres textuels sont comparés au texte enregistré après que l'API a échappé `<`, `>`, `"`
et `'` : une valeur de filtre contenant ces caractères correspond donc rarement ; voir
[filtres](conventions.md#filtres).

### Réponse

`200 OK` avec un tableau de [conversations](#conversation).

### Erreurs

| Statut | `detail` | Cause |
|---|---|---|
| `403` | `permission denied` | Le compte de l'utilisateur n'est pas autorisé à lire les conversations. |
| `422` | erreur de validation | Le corps est absent, ou un filtre ou `sort_key` est invalide. |

Les erreurs d'authentification, de limite de débit et de serveur sont décrites dans les
[conventions](conventions.md#erreurs).

### Exemple

Les 20 conversations avec un assistant donné mises à jour le plus récemment :

```bash
curl -X POST "https://ayeto.ai/api/v3/conversation/find" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": [["assistant_id", "==", "3f6c1a2e-8b4d-4e7a-9c1f-2d5e6a7b8c90"]],
    "sort_key": "updated.timestamp",
    "sort_order": 1,
    "limit_from": 0,
    "limit_to": 20
  }'
```

```json
[
  {
    "id": "8e2f4a6c-1b3d-4e5f-8a7b-9c0d1e2f3a4b",
    "name": "Delivery terms for order 1042",
    "model": "gpt-5",
    "summary": "",
    "organization_id": null,
    "assistant_id": "3f6c1a2e-8b4d-4e7a-9c1f-2d5e6a7b8c90",
    "task_id": null,
    "task_execution_id": null,
    "workflow_id": null,
    "workflow_run_id": null,
    "public_sharing": false,
    "messages": [
      {
        "id": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
        "timestamp": 1759395601000,
        "role": "user",
        "content": "What delivery date did we agree for order 1042?",
        "reasoning_content": null,
        "model": null,
        "attachments": [],
        "tool_runs": {}
      },
      {
        "id": "1b2c3d4e-5f6a-4b7c-9d8e-0f1a2b3c4d5e",
        "timestamp": 1759395606000,
        "role": "assistant",
        "content": "The agreed delivery date for order 1042 is 14 October 2026.",
        "reasoning_content": null,
        "model": "gpt-5",
        "attachments": [],
        "tool_runs": {}
      }
    ],
    "created": {"timestamp": 1759395600000, "user_id": "6c5b4a39-2817-4f6e-9d5c-4b3a29180f7e"},
    "updated": {"timestamp": 1759395606000, "user_id": "6c5b4a39-2817-4f6e-9d5c-4b3a29180f7e"},
    "accessed": {"timestamp": 1759395606000, "user_id": "6c5b4a39-2817-4f6e-9d5c-4b3a29180f7e"}
  }
]
```

## Compter les conversations

| | |
|---|---|
| Endpoint | `POST /api/v3/conversation/count` |
| Portée | `ayeto.conversation` |
| Limite de débit | [par défaut](conventions.md#limites-de-debit) |

Renvoie le nombre de conversations de l'utilisateur qui correspondent aux filtres, voir
[comptage](conventions.md#comptage).

### Requête

Le même objet de requête que pour [lister les conversations](#lister-les-conversations) ; envoyez `{}` pour les compter
toutes. Seul `filters` est utilisé : `limit_from`, `limit_to` et les champs de tri sont
ignorés, vous pouvez donc envoyer le même corps que pour la page que vous affichez.

### Réponse

`200 OK` avec le nombre de conversations correspondantes sous forme d'entier JSON.

```json
42
```

### Erreurs

| Statut | `detail` | Cause |
|---|---|---|
| `403` | `permission denied` | Le compte de l'utilisateur n'est pas autorisé à lire les conversations. |
| `422` | erreur de validation | Le corps est absent, ou un filtre ou `sort_key` est invalide. |

Les erreurs d'authentification, de limite de débit et de serveur sont décrites dans les
[conventions](conventions.md#erreurs).

### Exemple

Conversations avec un assistant donné modifiées depuis un instant donné :

```bash
curl -X POST "https://ayeto.ai/api/v3/conversation/count" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": [
      ["assistant_id", "==", "3f6c1a2e-8b4d-4e7a-9c1f-2d5e6a7b8c90"],
      ["updated.timestamp", ">=", 1759363200000]
    ]
  }'
```

```json
7
```

## Obtenir une conversation

| | |
|---|---|
| Endpoint | `POST /api/v3/conversation/get` |
| Portée | `ayeto.conversation` |
| Limite de débit | [par défaut](conventions.md#limites-de-debit) |

Renvoie une conversation avec l'intégralité de son historique de messages.

### Requête

| Champ | Type | Obligatoire | Description |
|---|---|---|---|
| `id` | UUID | Oui | Id de la conversation. |

L'id peut aussi être envoyé à la place dans le paramètre de requête `entity_id`, sans corps ou avec
`{}` ; voir [paramètres](conventions.md#parametres).

Lire une conversation compte comme l'ouvrir : son horodatage `accessed` est mis à jour (au
plus une fois par minute).

### Réponse

`200 OK` avec une [conversation](#conversation).

### Erreurs

| Statut | `detail` | Cause |
|---|---|---|
| `403` | `permission denied` | Le compte de l'utilisateur n'est pas autorisé à lire les conversations. |
| `404` | `entity not found, id: <id>` | Aucune conversation avec cet id n'existe. |
| `404` | `entity not found` | La conversation existe mais appartient à un autre utilisateur. |
| `422` | erreur de validation | L'id est absent ou n'est pas un UUID, ou `id` et `entity_id` diffèrent. |

### Exemple

```bash
curl -X POST "https://ayeto.ai/api/v3/conversation/get" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id": "8e2f4a6c-1b3d-4e5f-8a7b-9c0d1e2f3a4b"}'
```

La réponse est un objet unique de même forme qu'un élément de
l'[exemple de liste](#exemple).

## Supprimer une conversation

| | |
|---|---|
| Endpoint | `POST /api/v3/conversation/delete` |
| Portée | `ayeto.conversation` |
| Limite de débit | [par défaut](conventions.md#limites-de-debit) |

Supprime définitivement une conversation, ainsi que les fichiers qui lui appartiennent
(pièces jointes qui y ont été envoyées et images ou autres fichiers qui y ont été générés). Cette opération est
irréversible.

### Requête

| Champ | Type | Obligatoire | Description |
|---|---|---|---|
| `id` | UUID | Oui | Id de la conversation. |

L'id peut aussi être envoyé à la place dans le paramètre de requête `entity_id`, sans corps ou avec
`{}` ; voir [paramètres](conventions.md#parametres).

### Réponse

`200 OK` avec l'id de la conversation supprimée sous forme de chaîne JSON.

```json
"8e2f4a6c-1b3d-4e5f-8a7b-9c0d1e2f3a4b"
```

### Erreurs

| Statut | `detail` | Cause |
|---|---|---|
| `403` | `permission denied` | Le compte de l'utilisateur n'est pas autorisé à supprimer les conversations. |
| `404` | `entity not found, id: <id>` | Aucune conversation avec cet id n'existe. |
| `404` | `entity not found` | La conversation existe mais appartient à un autre utilisateur. |
| `422` | erreur de validation | L'id est absent ou n'est pas un UUID, ou `id` et `entity_id` diffèrent. |
| `500` | `can not delete entity` | La conversation n'a pas pu être supprimée ; réessayez plus tard. |

### Exemple

```bash
curl -X POST "https://ayeto.ai/api/v3/conversation/delete" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id": "8e2f4a6c-1b3d-4e5f-8a7b-9c0d1e2f3a4b"}'
```

## Conversation

L'objet renvoyé par tous les endpoints de conversation.

| Champ | Type | Obligatoire | Description |
|---|---|---|---|
| `id` | UUID | Oui | Id de la conversation. La même valeur est le `conversation_id` de [chat](chat.md). |
| `name` | chaîne | Oui | Nom d'affichage. `New conversation` jusqu'à ce qu'un nom soit suggéré après la première réponse ou défini dans l'application. |
| `model` | chaîne | Oui | Id du modèle avec lequel la conversation a été démarrée (voir [modèles](models-and-tools.md)). Certaines réponses peuvent provenir d'un autre modèle ; voir le `model` du message. |
| `summary` | chaîne | Oui | Résumé généré dans l'application à la demande ; chaîne vide si aucun résumé n'a été généré. |
| `organization_id` | UUID ou null | Non | Organisation à laquelle appartient la conversation ; `null` pour un usage personnel. |
| `assistant_id` | UUID ou null | Non | [Assistant](assistants.md) avec lequel la conversation est menée ; `null` lors d'un chat direct avec un modèle. |
| `task_id` | UUID ou null | Non | Tâche planifiée qui a créé la conversation. |
| `task_execution_id` | UUID ou null | Non | Exécution de cette tâche. |
| `workflow_id` | UUID ou null | Non | [Workflow](workflows.md) dont l'étape assistant a créé la conversation. |
| `workflow_run_id` | UUID ou null | Non | Exécution de ce workflow. |
| `public_sharing` | booléen | Oui | `true` lorsque l'utilisateur a partagé la conversation publiquement par un lien dans l'application. |
| `messages` | tableau de [Message](#message) | Oui | Messages de l'utilisateur et de l'assistant dans l'ordre chronologique. Les messages système, d'outils et les autres messages internes sont omis. |
| `created`, `updated`, `accessed` | objet | Oui | Métadonnées `{timestamp, user_id}`, voir [champs communs](conventions.md#champs-communs). `updated` change à chaque nouveau message ; `accessed` indique la dernière ouverture de la conversation. |

### Message

| Champ | Type | Obligatoire | Description |
|---|---|---|---|
| `id` | UUID | Oui | Id du message. |
| `timestamp` | horodatage | Oui | Date de création du message (ms depuis l'epoch). |
| `role` | chaîne | Oui | `user` ou `assistant`. |
| `content` | chaîne ou null | Non | Texte du message en Markdown. Dans les messages de l'assistant, il contient aussi les [appels d'outils](#appels-doutils-dans-le-contenu-des-messages) effectués pendant la réponse. |
| `reasoning_content` | chaîne ou null | Non | Le texte de raisonnement du modèle, lorsque le modèle le renvoie. |
| `model` | chaîne ou null | Non | Modèle qui a rédigé un message de l'assistant ; `null` pour les messages de l'utilisateur. |
| `attachments` | tableau de [Pièce jointe](#piece-jointe) | Oui | Fichiers joints au message. |
| `tool_runs` | objet | Oui | Réponses des sous-assistants appelés pendant ce message, voir [appels d'outils](#appels-doutils-dans-le-contenu-des-messages). Généralement vide. |

### Pièce jointe

| Champ | Type | Obligatoire | Description |
|---|---|---|---|
| `filename` | chaîne | Oui | Nom du fichier. |
| `content_type` | chaîne | Oui | Type MIME, par exemple `image/png` ou `application/pdf`. |
| `file_url` | chaîne | Oui | URL publique du fichier. Les fichiers envoyés ou générés dans la conversation sont supprimés avec elle. |
| `name` | chaîne ou null | Non | Nom d'affichage, lorsqu'il diffère de `filename`. |
| `description` | chaîne ou null | Non | Brève description du fichier. |

### Appels d'outils dans le contenu des messages

Chaque outil appelé par le modèle pendant la rédaction d'un message de l'assistant apparaît dans `content` sous la forme
d'un bloc qui commence par `"\n\n### calling AI tool ...\n"` et se termine par `"\n***\n"`.
Le texte entre les deux marqueurs est la sortie de l'outil affichée à l'utilisateur :

```json
{
  "role": "assistant",
  "content": "Let me look that up.\n\n### calling AI tool ...\nSearching the web for \"order 1042 delivery\"...\n***\nThe agreed delivery date is 14 October 2026."
}
```

Retirez ces blocs si vous n'avez besoin que du texte de la réponse. (L'endpoint [chat](chat.md) peut
les omettre de sa propre réponse avec `remove_tool_calls` ; la conversation enregistrée les conserve toujours.)

Lorsqu'un assistant appelle un autre assistant comme outil, la réponse de cet assistant est enregistrée
dans `tool_runs`, avec pour clé la position de l'appel d'outil dans `content`, sous forme de chaîne (`"0"`
pour le premier appel d'outil, `"1"` pour le deuxième, et ainsi de suite). Chaque valeur contient une chaîne `content`
au même format et ses propres `tool_runs` imbriqués.
