{
  "openapi": "3.1.0",
  "info": {
    "title": "BECLEVER API",
    "version": "1.0.0",
    "description": "API serveur à serveur par organisation. Clés et webhooks se configurent dans le Studio. Aucun paiement API. Capacités du moteur et disponibilité retournées par /channels/{channelId}/operations. Un résultat accepté ne prouve pas la livraison WhatsApp. Texte et mediaId sont exclusifs. Idempotence par requestId stable. Les mutations destructives ne sont pas exposées."
  },
  "servers": [
    {
      "url": "https://beclever.fr/api/v1"
    }
  ],
  "paths": {
    "/capabilities": {
      "get": {
        "summary": "Capacités et limites pour cette clé",
        "operationId": "get_capabilities",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Résultat",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Entrée invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente, expirée ou révoquée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Permission refusée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Ressource introuvable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "État incompatible ou conflit idempotent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Quota ou débit dépassé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service indisponible ou résultat à rapprocher",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": []
      }
    },
    "/channels": {
      "get": {
        "summary": "Lister les numéros de votre organisation",
        "operationId": "get_channels",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Résultat",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Entrée invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente, expirée ou révoquée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Permission refusée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Ressource introuvable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "État incompatible ou conflit idempotent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Quota ou débit dépassé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service indisponible ou résultat à rapprocher",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [],
        "x-required-scope": "channels:read",
        "description": " Inclut les canaux Telegram autorisés : id telegram_<32hex>, provider telegram, label, username/botUsername, status WORKING/STOPPED/FAILED, connectionMode bot ou account et capabilities détaillées. Pour un canal dont le serveur confirme le support : media:true, mediaTypes contenant les types déclarés parmi [image,document,audio,video] ; sinon media:false et mediaTypes:[]. Les comptes personnels annoncent groups:true, history:true, automation:false ; les bots restent privés, groups:false, history:false, automation:true. Aucun token de bot dans la réponse. Association via session cliente dans le Studio, hors API par clé. Réception déclarée séparément par canal : capabilities.incomingMediaDownload:true et incomingMediaTypes contenant les types négociés parmi [image,document,audio,video]. Sinon false et liste vide. Les capacités editText et editCaption sont vraies uniquement pour un bot dont l’adaptateur confirme l’édition ; vérifier aussi editKind du message. Les capacités reactions:true et reactionEmojis sont exposées uniquement pour un bot dont l’adaptateur les confirme ; vérifier aussi reactionAllowed et reactionRevision du message. Aucun support implicite pour les comptes personnels."
      }
    },
    "/conversations": {
      "get": {
        "summary": "Lister les conversations conservées, avec pagination facultative",
        "operationId": "get_conversations",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Liste historique sans paramètres, ou page bornée si un paramètre de pagination est présent.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ConversationList"
                    },
                    {
                      "$ref": "#/components/schemas/ConversationPage"
                    }
                  ]
                },
                "examples": {
                  "legacy": {
                    "summary": "Sans query : forme historique",
                    "value": {
                      "conversations": [
                        {
                          "id": "22222222-2222-4222-8222-222222222222",
                          "tenantId": "organisation-exemple",
                          "channelId": "clever_00000000000000000000000000000000",
                          "chatId": "33600000000@c.us",
                          "publicationId": "",
                          "nodeId": "",
                          "status": "human",
                          "reason": "",
                          "updatedAt": "2026-10-17T08:00:00.000Z"
                        }
                      ]
                    }
                  },
                  "page": {
                    "summary": "Dernière page du relevé",
                    "value": {
                      "conversations": [
                        {
                          "id": "22222222-2222-4222-8222-222222222222",
                          "tenantId": "organisation-exemple",
                          "channelId": "clever_00000000000000000000000000000000",
                          "chatId": "33600000000@c.us",
                          "publicationId": "",
                          "nodeId": "",
                          "status": "human",
                          "reason": "",
                          "updatedAt": "2026-10-17T08:00:00.000Z"
                        }
                      ],
                      "hasMore": false,
                      "nextCursor": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "runtime-conversations-invalid-query : paramètre invalide, inconnu, répété ou vide ; runtime-conversations-cursor-invalid : curseur altéré, expiré ou lié à une autre organisation ou un autre filtre.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente, expirée ou révoquée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Permission refusée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "runtime-conversation-not-found : filtre de compte inconnu ou inaccessible.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "État incompatible ou conflit idempotent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Quota ou débit dépassé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service indisponible ou résultat à rapprocher",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "channelId",
            "in": "query",
            "required": false,
            "description": "Compte WhatsApp ou Telegram accessible ; filtre combinable aux autres critères. Conserver les mêmes filtres pour les pages suivantes.",
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Sa présence active la pagination. Défaut 50 dans le mode paginé ; sans aucun paramètre, la liste historique reste limitée à 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "nextCursor reçu ; omettre au premier appel. Ne jamais le construire ou le modifier.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 2048
            }
          },
          {
            "name": "provider",
            "in": "query",
            "required": false,
            "description": "Fournisseur du compte.",
            "schema": {
              "type": "string",
              "enum": [
                "whatsapp",
                "telegram"
              ]
            }
          },
          {
            "name": "tag",
            "in": "query",
            "required": false,
            "description": "Étiquette exacte normalisée (NFKC, espaces regroupés, minuscules), présente sur le contact ou la conversation.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 40
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Recherche par titre de conversation, numéro ou identifiant de contact (normalisation NFKC, sans distinction de casse), jamais dans le contenu des messages.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            }
          }
        ],
        "x-required-scope": "conversations:read",
        "description": "Scope conversations:read. Sans aucun paramètre, le contrat historique reste {conversations} : 100 résultats maximum, updatedAt décroissant. La présence de limit, cursor, channelId, provider, tag ou q active le mode paginé {conversations, hasMore, nextCursor}. Paramètres inconnus, répétés ou vides refusés. Mode paginé : ordre d’indexation interne immuable décroissant, distinct de updatedAt et de la date de création historique WhatsApp. La première page fixe une borne haute excluant les conversations ajoutées ensuite. Les statuts, updatedAt et étiquettes restent courants : un changement de marquage peut modifier les résultats restants. Actualiser sans curseur pour une vue complète à jour. Curseur opaque lié à l’organisation et aux filtres normalisés ; durée de 15 minutes depuis la première page, sans prolongation. Chaîne de requête limitée à 4096 caractères ; curseur limité à 2048 caractères. Conserver le filtre, limit peut changer. Journal BECLEVER uniquement, sans appel fournisseur ni import du passé WhatsApp ou Telegram."
      }
    },
    "/conversations/{conversationId}/messages": {
      "get": {
        "summary": "Lire l’historique de la conversation",
        "operationId": "get_conversations_conversationId_messages",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Résultat",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Entrée invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente, expirée ou révoquée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Permission refusée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Ressource introuvable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "État incompatible ou conflit idempotent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Quota ou débit dépassé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service indisponible ou résultat à rapprocher",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "x-required-scope": "conversations:read"
      }
    },
    "/conversations/{conversationId}/reply": {
      "post": {
        "summary": "Répondre par texte ou média privé",
        "operationId": "post_conversations_conversationId_reply",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "202": {
            "description": "Résultat accepté",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Entrée invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente, expirée ou révoquée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Permission refusée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Ressource introuvable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "État incompatible ou conflit idempotent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Quota ou débit dépassé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service indisponible ou résultat à rapprocher",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "x-required-scope": "messages:send",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReplyInput"
              }
            }
          }
        },
        "description": " Note vidéo ronde : mediaId privé et mediaType=videoNote, sans texte ni légende non vide. MP4 H.264 carré, une piste vidéo, ≤8Mio et ≤60s, audio AAC-LC facultatif (mp4a.40.2, mono/stéréo, 8–48 kHz) uniquement. Erreur media-video-note-incompatible si fichier non compatible, runtime-video-note-caption-unsupported pour une légende, runtime-operation-unavailable si transport non autorisé. Aucun fallback vers une vidéo classique. Telegram : canal provider telegram connecté et conversation déjà connue. Bot : conversation privée uniquement. Compte personnel : réponse manuelle uniquement. Texte (4 096 caractères maximum), image JPEG/PNG (mediaType=image), document PDF (mediaType=document), vocal OGG/Opus mono (mediaType=audio, ≤20 minutes, sans légende) ou vidéo MP4/H.264 (mediaType=video, ≤60 minutes, une piste vidéo et au plus une piste AAC-LC mono/stéréo 8–48 kHz, aucune autre piste), au plus 8 Mio via mediaId privé ; vérifier capabilities.mediaTypes du canal. Légende image/document/vidéo limitée à 1 024 unités UTF-16, sans texte séparé simultané. Photos des bots : largeur + hauteur ≤10 000 pixels, rapport entre les côtés ≤20. Pas de transcodage automatique ; les formats incompatibles sont refusés. Stickers, notes vidéo rondes et linkPreview refusés. Aucun aperçu automatique de lien ni automatisation sur compte personnel. runtime-telegram-recipient-unknown si destinataire non connu. Une acceptation dans la file ne prouve pas la livraison ; une issue inconnue ne doit pas être renvoyée automatiquement."
      }
    },
    "/messages": {
      "post": {
        "summary": "Envoyer à un destinataire direct depuis un numéro associé",
        "operationId": "post_messages",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "202": {
            "description": "Résultat accepté",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Entrée invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente, expirée ou révoquée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Permission refusée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Ressource introuvable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "État incompatible ou conflit idempotent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Quota ou débit dépassé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service indisponible ou résultat à rapprocher",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [],
        "x-required-scope": "messages:send",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageInput"
              }
            }
          }
        },
        "description": " Note vidéo ronde : mediaId privé et mediaType=videoNote, sans texte ni légende non vide. MP4 H.264 carré, une piste vidéo, ≤8Mio et ≤60s, audio AAC-LC facultatif (mp4a.40.2, mono/stéréo, 8–48 kHz) uniquement. Erreur media-video-note-incompatible si fichier non compatible, runtime-video-note-caption-unsupported pour une légende, runtime-operation-unavailable si transport non autorisé. Aucun fallback vers une vidéo classique. Telegram : canal provider telegram connecté et conversation déjà connue. Bot : conversation privée uniquement. Compte personnel : réponse manuelle uniquement. Texte (4 096 caractères maximum), image JPEG/PNG (mediaType=image), document PDF (mediaType=document), vocal OGG/Opus mono (mediaType=audio, ≤20 minutes, sans légende) ou vidéo MP4/H.264 (mediaType=video, ≤60 minutes, une piste vidéo et au plus une piste AAC-LC mono/stéréo 8–48 kHz, aucune autre piste), au plus 8 Mio via mediaId privé ; vérifier capabilities.mediaTypes du canal. Légende image/document/vidéo limitée à 1 024 unités UTF-16, sans texte séparé simultané. Photos des bots : largeur + hauteur ≤10 000 pixels, rapport entre les côtés ≤20. Pas de transcodage automatique ; les formats incompatibles sont refusés. Stickers, notes vidéo rondes et linkPreview refusés. Aucun aperçu automatique de lien ni automatisation sur compte personnel. runtime-telegram-recipient-unknown si destinataire non connu. Une acceptation dans la file ne prouve pas la livraison ; une issue inconnue ne doit pas être renvoyée automatiquement."
      },
      "get": {
        "summary": "Lire l’historique global conservé par BECLEVER",
        "operationId": "get_messages",
        "description": "Lecture seule du journal de messages reçu et conservé par BECLEVER dans votre organisation. Ne synchronise ni n’importe l’historique antérieur de WhatsApp et n’appelle pas WAHA. Scope messages:read distinct, jamais ajouté automatiquement aux clés existantes. Filtres channelId et conversationId facultatifs et combinables. Les deux filtres doivent désigner la même conversation et son canal. Ordre créé décroissant, puis séquence interne stable décroissante. Le premier appel fixe une borne haute : les nouvelles insertions sont exclues des pages suivantes, même si leur date est antérieure. Le curseur opaque chiffré est lié à l’organisation, aux filtres et à cette borne ; il expire 15 minutes après la première page, sans prolongation aux pages suivantes. Reprendre avec les mêmes filtres ; limit peut changer. Le curseur n’accorde aucun accès et les droits sont revérifiés. Pour un nouveau relevé ou après expiration, recommencer sans curseur et dédupliquer par messages[].id. Paramètres inconnus, répétés, vides ou malformés refusés. Chaîne de requête limitée à 4 096 caractères, curseur à 2 048 caractères. Aucun contenu de fichier ni URL fournisseur dans cette réponse.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-required-scope": "messages:read",
        "parameters": [
          {
            "name": "channelId",
            "in": "query",
            "required": false,
            "description": "Identifiant d’un compte WhatsApp accessible dans cette organisation. Conserver ce filtre pour les pages suivantes.",
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "conversationId",
            "in": "query",
            "required": false,
            "description": "Identifiant interne d’une conversation accessible. Si channelId est fourni, la conversation doit lui appartenir.",
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Taille maximale de la page. Peut être modifiée lors de la reprise du curseur.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Valeur nextCursor retournée précédemment, opaque et chiffrée ; valable 15 minutes depuis la première page, sans renouvellement, liée à l’organisation, aux filtres et à la borne haute. La transmettre inchangée ; ne pas construire ni analyser ce jeton. Omettre à la première page.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 2048
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page du journal local ; nextCursor est null et hasMore est false à la fin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageHistoryPage"
                },
                "example": {
                  "messages": [
                    {
                      "id": "11111111-1111-4111-8111-111111111111",
                      "conversationId": "22222222-2222-4222-8222-222222222222",
                      "channelId": "clever_00000000000000000000000000000000",
                      "chatId": "33600000000@c.us",
                      "direction": "in",
                      "kind": "text",
                      "body": "Bonjour, message de démonstration.",
                      "status": "received",
                      "providerId": "fictitious-provider-id",
                      "created": 1792180800000,
                      "createdAt": "2026-10-16T20:00:00.000Z"
                    }
                  ],
                  "nextCursor": null,
                  "hasMore": false
                }
              }
            }
          },
          "400": {
            "description": "runtime-history-invalid-query : paramètres invalides, inconnus ou répétés. runtime-history-cursor-invalid : curseur altéré, expiré, d’une autre organisation ou associé à d’autres filtres.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente, expirée ou révoquée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Scope messages:read absent ou droits actuels du créateur insuffisants.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "runtime-history-filter-not-found : canal ou conversation inaccessible, introuvable ou combinaison de filtres incohérente.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Limite de requêtes de la clé ou de l’organisation atteinte.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Journal de messages indisponible.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/media": {
      "get": {
        "summary": "Lister les fichiers privés et quotas",
        "operationId": "get_media",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Résultat",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Entrée invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente, expirée ou révoquée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Permission refusée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Ressource introuvable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "État incompatible ou conflit idempotent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Quota ou débit dépassé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service indisponible ou résultat à rapprocher",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [],
        "x-required-scope": "media:read"
      },
      "post": {
        "summary": "Téléverser un fichier privé validé",
        "operationId": "post_media",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Résultat",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Entrée invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente, expirée ou révoquée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Permission refusée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Ressource introuvable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "État incompatible ou conflit idempotent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Quota ou débit dépassé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service indisponible ou résultat à rapprocher",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [],
        "x-required-scope": "media:write",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MediaInput"
              }
            }
          }
        }
      }
    },
    "/media/{mediaId}": {
      "get": {
        "summary": "Télécharger un fichier privé",
        "operationId": "get_media_mediaId",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Octets du fichier",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Entrée invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente, expirée ou révoquée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Permission refusée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Ressource introuvable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "État incompatible ou conflit idempotent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Quota ou débit dépassé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service indisponible ou résultat à rapprocher",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "mediaId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "x-required-scope": "media:read"
      }
    },
    "/channels/{channelId}/operations": {
      "get": {
        "summary": "Catalogue et schémas des opérations de ce numéro",
        "operationId": "get_channels_channelId_operations",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Résultat",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Entrée invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente, expirée ou révoquée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Permission refusée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Ressource introuvable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "État incompatible ou conflit idempotent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Quota ou débit dépassé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service indisponible ou résultat à rapprocher",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "channelId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Clé valide de l’organisation requise. Le catalogue ne déclenche aucune opération. Chaque disponibilité tient compte des scopes de cette clé, du numéro connecté et des fonctions approuvées. access.type=dedicated : envoi via access.sendPath avec messages:send ; access.type=operation : route générique access.path avec whatsapp:read ou whatsapp:write ; unavailable : non exposée."
      }
    },
    "/channels/{channelId}/operations/{operationId}": {
      "get": {
        "summary": "Exécuter une lecture autorisée de WAHA",
        "operationId": "get_channels_channelId_operations_operationId",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Résultat",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Entrée invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente, expirée ou révoquée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Permission refusée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Ressource introuvable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "État incompatible ou conflit idempotent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Quota ou débit dépassé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service indisponible ou résultat à rapprocher",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "channelId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "operationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "params",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Objet JSON encodé dans la query string ; voir inputSchema de l’opération."
          },
          {
            "name": "query",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Objet JSON encodé dans la query string ; voir inputSchema de l’opération."
          }
        ],
        "x-required-scope": "whatsapp:read"
      },
      "post": {
        "summary": "Exécuter une recherche en lecture ou mettre une mutation autorisée en file",
        "operationId": "post_channels_channelId_operations_operationId",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "202": {
            "description": "Résultat accepté",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "400": {
            "description": "Entrée invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente, expirée ou révoquée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Permission refusée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Ressource introuvable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "État incompatible ou conflit idempotent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Quota ou débit dépassé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service indisponible ou résultat à rapprocher",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Résultat d’une recherche en lecture",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "channelId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "operationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "x-required-scope": "whatsapp:write",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OperationInput"
              }
            }
          }
        },
        "description": "Recherche scope read : HTTP 200, permission whatsapp:read, sans requestId. Mutation : HTTP 202, permission whatsapp:write et requestId requis. Consulter le scope et inputSchema dans le catalogue du numéro. Statuts privés : statusImage {body:{mediaId,contacts,caption?}}, statusVideo idem (≤30s), statusVoice {body:{mediaId,contacts,backgroundColor?}} (≤30s, sans légende). Audience explicite 1–25 identifiants PN @c.us ou LID @lid. Photo de groupe : setGroupPicture {params:{id:\"…@g.us\"},body:{mediaId}} JPEG uniquement. Ajouter requestId stable. Aucun file/base64/url dans ces commandes. Les fichiers sont résolus côté serveur après contrôle du tenant et revérifiés avant effet. Scopes whatsapp:write ; disponibilité effective dans le catalogue."
      }
    },
    "/operations": {
      "get": {
        "summary": "Consulter les 100 opérations récentes",
        "operationId": "get_operations",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Résultat",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Entrée invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente, expirée ou révoquée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Permission refusée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Ressource introuvable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "État incompatible ou conflit idempotent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Quota ou débit dépassé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service indisponible ou résultat à rapprocher",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [],
        "x-required-scope": "whatsapp:write"
      }
    },
    "/operations/{jobId}": {
      "get": {
        "summary": "Consulter le résultat d’une opération",
        "operationId": "get_operations_jobId",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Résultat",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "400": {
            "description": "Entrée invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente, expirée ou révoquée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Permission refusée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Ressource introuvable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "État incompatible ou conflit idempotent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Quota ou débit dépassé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service indisponible ou résultat à rapprocher",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "x-required-scope": "whatsapp:write"
      }
    },
    "/conversations/{conversationId}/messages/{messageId}/media": {
      "post": {
        "summary": "Importer une pièce jointe reçue dans la bibliothèque privée",
        "operationId": "post_conversations_conversationId_messages_messageId_media",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Fichier importé ou déjà importé",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "media"
                  ],
                  "properties": {
                    "media": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Entrée invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente, expirée ou révoquée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Permission refusée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Ressource introuvable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "État incompatible ou conflit idempotent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Quota ou débit dépassé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service indisponible ou résultat à rapprocher",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "messageId",
            "in": "path",
            "required": true,
            "description": "Identifiant interne messages[].id, et non identifiant fournisseur WhatsApp ou Telegram.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Import explicite depuis le message interne d’une conversation de votre organisation. Contrôle du type, du contenu et des quotas identique au téléversement. Aucun lien fournisseur accepté ni retourné. Un réimport renvoie le même fichier sans doubler le quota. La fonction dépend de la disponibilité déclarée du moteur et du fichier reçu. Taille fournisseur explicite de 1 octet à 8 Mio requise avant récupération. Les messages citant une autre pièce jointe et les enveloppes non reconnues sont refusés. La taille et le MIME sont revérifiés après récupération. Telegram : photos JPEG/PNG, documents PDF, vocaux OGG/Opus mono ≤20 minutes et vidéos MP4/H.264 ≤60 minutes (audio AAC-LC mono/stéréo 8–48 kHz facultatif, au plus une piste audio et aucune autre piste), dans une conversation déjà connue, même en lecture seule. Les vidéos rondes, animations et fichiers musicaux Telegram ne sont pas importables ; aucune conversion automatique. Les capacités entrantes audio/video doivent avoir été confirmées pour ce compte. Vérifier media.downloadAvailable sur le message ; aucun téléchargement automatique ni marquage lu Telegram par cet import. Bots : messages privés reçus et enregistrés depuis la connexion uniquement. Les contenus protégés, éphémères et conversations secrètes sont exclus. Le compte doit rester actif et le message inchangé pendant la récupération ; une suppression ou modification connue de BECLEVER interrompt l’association. La Bot API ne permet pas de relire arbitrairement un message ni de détecter toutes les suppressions : le contrôle des bots porte sur les mises à jour effectivement reçues. Les identifiants de fichier internes et chemins du serveur ne sont jamais acceptés ni retournés. Si la dernière vérification échoue après stockage, le fichier peut rester privé dans la bibliothèque sans être associé au message.",
        "x-required-scopes": [
          "conversations:read",
          "media:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/conversations/{conversationId}": {
      "get": {
        "summary": "Lire une conversation conservée",
        "operationId": "get_conversation",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-required-scope": "conversations:read",
        "parameters": [
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "description": "Identifiant interne BECLEVER.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Lecture unitaire du journal de votre organisation, même si le compte WhatsApp est déconnecté. Retourne uniquement {conversation}, sans messages ni variables internes. Aucun appel WAHA. Une ressource inconnue ou inaccessible reçoit le même refus 404. La route /conversations/{conversationId}/messages conserve son contrat propre. Aucun paramètre de requête accepté.",
        "responses": {
          "400": {
            "description": "runtime-conversations-invalid-query : aucun paramètre de requête accepté sur le détail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente, expirée ou révoquée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Permission refusée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "runtime-conversation-not-found : conversation inconnue ou inaccessible.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Quota ou débit dépassé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service indisponible ou résultat à rapprocher",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Conversation locale accessible.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationDetail"
                },
                "example": {
                  "conversation": {
                    "id": "22222222-2222-4222-8222-222222222222",
                    "tenantId": "organisation-exemple",
                    "channelId": "clever_00000000000000000000000000000000",
                    "chatId": "33600000000@c.us",
                    "publicationId": "",
                    "nodeId": "",
                    "status": "human",
                    "reason": "",
                    "updatedAt": "2026-10-17T08:00:00.000Z"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/channels/{channelId}/contacts/resolve": {
      "post": {
        "summary": "Rapprocher explicitement des identifiants PN et LID",
        "operationId": "resolve_channel_contacts",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-required-scope": "whatsapp:read",
        "description": "Lecture du moteur pour 1 à 25 identifiants canoniques explicitement fournis. Ordre et doublons conservés. Aucune écriture des alias ou des préférences STOP, aucun import implicite du carnet. Les deux opérations phoneByLid et lidByPhone doivent déjà être approuvées et le compte doit être connecté. Délai du lot 15 secondes ; au maximum 2 lots globaux et 1 par organisation. Un résultat fournisseur incohérent fait échouer le lot.",
        "parameters": [
          {
            "name": "channelId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "contacts"
                ],
                "properties": {
                  "contacts": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 25,
                    "items": {
                      "type": "string",
                      "pattern": "^[0-9]+@(c\\.us|lid)$"
                    }
                  }
                }
              },
              "example": {
                "contacts": [
                  "33600000000@c.us",
                  "123456789@lid"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Résolutions dans l’ordre demandé, doublons conservés",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "results"
                  ],
                  "properties": {
                    "results": {
                      "type": "array",
                      "maxItems": 25,
                      "items": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                          "input",
                          "status",
                          "phoneChatId",
                          "lidChatId"
                        ],
                        "properties": {
                          "input": {
                            "type": "string"
                          },
                          "status": {
                            "enum": [
                              "resolved",
                              "unknown"
                            ]
                          },
                          "phoneChatId": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "lidChatId": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "results": [
                    {
                      "input": "33600000000@c.us",
                      "status": "unknown",
                      "phoneChatId": null,
                      "lidChatId": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "waha-contact-batch-invalid ou waha-invalid-request : corps invalide.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente, expirée ou révoquée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "developer-scope-required ou waha-operation-forbidden : permission refusée.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "waha-channel-not-found : compte inaccessible.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "waha-alias-resolution-unavailable : approbations manquantes ; waha-runtime-mismatch ou session-not-ready : compte ou moteur indisponible.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "waha-contact-batch-busy : nombre de lots simultanés atteint, ou quota API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "waha-invalid-alias-response, alias-resolution-failed, contact-batch-timeout ou provider-rejected : lot non résolu.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/conversations/{conversationId}/messages/{messageId}/edit": {
      "post": {
        "summary": "Modifier le texte ou la légende d’un message envoyé par un bot Telegram",
        "operationId": "post_conversations_conversationId_messages_messageId_edit",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Résultat journalisé ; vérifier edit.state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageEditResult"
                }
              }
            }
          },
          "400": {
            "description": "Entrée invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente, expirée ou révoquée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Permission refusée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Ressource introuvable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "État incompatible ou conflit idempotent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Quota ou débit dépassé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service indisponible ou résultat à rapprocher",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "messageId",
            "in": "path",
            "required": true,
            "description": "Identifiant interne messages[].id, et non identifiant fournisseur WhatsApp ou Telegram.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Modification explicite d’un message sortant du bot, enregistré et confirmé par BECLEVER depuis l’activation de cette capacité. Les anciens messages sans preuve durable, messages reçus, comptes personnels Telegram, WhatsApp et vocaux sont exclus. Utiliser editKind et editRevision de la dernière lecture ; les identifiants fournisseur ne sont jamais acceptés en cible. Texte brut ou légende image/PDF/vidéo uniquement, aucun remplacement de fichier. Droits cumulés conversations:read et messages:send contrôlés avant exécution. Une seule modification par message à la fois. Même requestId et même contenu : retour du journal sans nouvel appel fournisseur ; réutilisation avec un contenu différent : conflit. Après un résultat incertain ou un redémarrage en cours d’appel, aucune relance automatique ni nouvelle modification du message : état unknown à rapprocher. La réponse contient le message courant. Telegram peut refuser une modification ; une suppression externe peut être inconnue de BECLEVER. La confirmation d’édition ne prouve ni livraison ni lecture.",
        "x-required-scopes": [
          "conversations:read",
          "messages:send"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageEditInput"
              }
            }
          }
        }
      }
    },
    "/conversations/{conversationId}/messages/{messageId}/reaction": {
      "post": {
        "summary": "Ajouter ou retirer la réaction du bot Telegram sur un message privé connu",
        "operationId": "post_conversations_conversationId_messages_messageId_reaction",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Résultat journalisé ; vérifier reaction.state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageReactionResult"
                }
              }
            }
          },
          "400": {
            "description": "Entrée invalide",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Clé absente, expirée ou révoquée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Permission refusée",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Ressource introuvable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "État incompatible ou conflit idempotent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Quota ou débit dépassé",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service indisponible ou résultat à rapprocher",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "messageId",
            "in": "path",
            "required": true,
            "description": "Identifiant interne messages[].id, et non identifiant fournisseur WhatsApp ou Telegram.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Réaction explicite du bot sur un message privé entrant ou sortant disposant d’une preuve durable actuelle. Albums, messages de service, protégés, éphémères, payants, anciens messages sans preuve, comptes personnels et WhatsApp exclus. Utiliser reactionAllowed et reactionRevision de la dernière lecture. Une chaîne emoji vide retire la réaction du bot ; les six émojis proposés sont les seuls acceptés. Aucune réaction personnalisée, payante ou animée. Droits cumulés conversations:read et messages:send. Une seule opération de réaction ou d’édition par message à la fois. Même requestId et même contenu : retour du journal sans nouvel appel fournisseur ; contenu différent : conflit. Après une issue incertaine ou un redémarrage pendant l’appel, état unknown durable, sans relance automatique ni nouvelle opération sur ce message. La réponse contient le message courant. reactionEmoji décrit uniquement la dernière réaction du bot confirmée par BECLEVER, jamais toutes les réactions des participants. Une confirmation ne prouve ni livraison ni lecture.",
        "x-required-scopes": [
          "conversations:read",
          "messages:send"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageReactionInput"
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "BECLEVER API key"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false
      },
      "MessageInput": {
        "type": "object",
        "properties": {
          "channelId": {
            "type": "string"
          },
          "to": {
            "type": "string",
            "description": "WhatsApp : identifiant direct @c.us ou @lid. Telegram : tg:<identifiant numérique> d’une conversation déjà connue du compte personnel associé à channelId, ou identifiant positif d’un correspondant privé ayant déjà écrit au bot."
          },
          "text": {
            "type": "string",
            "maxLength": 8000,
            "description": "Texte Telegram : 4 096 caractères maximum. Selon les capacités du canal Telegram, joindre un mediaId JPEG/PNG, PDF ou vidéo MP4/H.264 avec une légende (1 024 unités UTF-16 maximum), ou un vocal OGG/Opus mono sans légende ; aucun texte simultané. Aucun aperçu de lien. Les limites WhatsApp restent inchangées."
          },
          "mediaId": {
            "type": "string",
            "description": "Identifiant privé de cette organisation. audio/ogg produit un vocal et image/webp un sticker statique, sans légende."
          },
          "caption": {
            "type": "string",
            "maxLength": 2000,
            "description": "Légende images, vidéos et documents uniquement. Telegram : JPEG/PNG/PDF et vidéo MP4/H.264, 1 024 unités UTF-16 maximum. Interdite pour un vocal ou sticker ; envoyer le texte séparément. Interdite également avec mediaType=videoNote."
          },
          "requestId": {
            "type": "string",
            "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]{0,79}$"
          },
          "linkPreview": {
            "$ref": "#/components/schemas/LinkPreview"
          },
          "mediaType": {
            "type": "string",
            "enum": [
              "image",
              "video",
              "audio",
              "document",
              "sticker",
              "videoNote"
            ],
            "description": "Type d’envoi explicite, uniquement avec mediaId. Les types classiques doivent correspondre au kind du fichier ; videoNote requiert kind video et compatibilité attestée. Sans ce champ, type dérivé du fichier (comportement historique). Note vidéo ronde : sendVideoNote disponible et aucune légende non vide."
          }
        },
        "required": [
          "channelId",
          "to",
          "requestId"
        ],
        "additionalProperties": false,
        "oneOf": [
          {
            "required": [
              "text"
            ],
            "not": {
              "anyOf": [
                {
                  "required": [
                    "mediaId"
                  ]
                },
                {
                  "required": [
                    "linkPreview"
                  ]
                },
                {
                  "required": [
                    "caption"
                  ]
                },
                {
                  "required": [
                    "mediaType"
                  ]
                }
              ]
            }
          },
          {
            "required": [
              "mediaId"
            ],
            "not": {
              "anyOf": [
                {
                  "required": [
                    "text"
                  ]
                },
                {
                  "required": [
                    "linkPreview"
                  ]
                }
              ]
            }
          },
          {
            "required": [
              "linkPreview"
            ],
            "not": {
              "anyOf": [
                {
                  "required": [
                    "text"
                  ]
                },
                {
                  "required": [
                    "mediaId"
                  ]
                },
                {
                  "required": [
                    "caption"
                  ]
                },
                {
                  "required": [
                    "mediaType"
                  ]
                }
              ]
            }
          }
        ],
        "allOf": [
          {
            "if": {
              "required": [
                "mediaType"
              ],
              "properties": {
                "mediaType": {
                  "const": "videoNote"
                }
              }
            },
            "then": {
              "required": [
                "mediaId"
              ],
              "properties": {
                "caption": {
                  "type": "string",
                  "pattern": "^\\s*$"
                }
              }
            }
          }
        ]
      },
      "ReplyInput": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string",
            "maxLength": 8000,
            "description": "Texte Telegram : 4 096 caractères maximum. Selon les capacités du canal Telegram, joindre un mediaId JPEG/PNG, PDF ou vidéo MP4/H.264 avec une légende (1 024 unités UTF-16 maximum), ou un vocal OGG/Opus mono sans légende ; aucun texte simultané. Aucun aperçu de lien. Les limites WhatsApp restent inchangées."
          },
          "mediaId": {
            "type": "string",
            "description": "Identifiant privé de cette organisation. audio/ogg produit un vocal et image/webp un sticker statique, sans légende."
          },
          "caption": {
            "type": "string",
            "maxLength": 2000,
            "description": "Légende images, vidéos et documents uniquement. Telegram : JPEG/PNG/PDF et vidéo MP4/H.264, 1 024 unités UTF-16 maximum. Interdite pour un vocal ou sticker ; envoyer le texte séparément. Interdite également avec mediaType=videoNote."
          },
          "requestId": {
            "type": "string",
            "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]{0,79}$"
          },
          "linkPreview": {
            "$ref": "#/components/schemas/LinkPreview"
          },
          "mediaType": {
            "type": "string",
            "enum": [
              "image",
              "video",
              "audio",
              "document",
              "sticker",
              "videoNote"
            ],
            "description": "Type d’envoi explicite, uniquement avec mediaId. Les types classiques doivent correspondre au kind du fichier ; videoNote requiert kind video et compatibilité attestée. Sans ce champ, type dérivé du fichier (comportement historique). Note vidéo ronde : sendVideoNote disponible et aucune légende non vide."
          }
        },
        "required": [
          "requestId"
        ],
        "additionalProperties": false,
        "oneOf": [
          {
            "required": [
              "text"
            ],
            "not": {
              "anyOf": [
                {
                  "required": [
                    "mediaId"
                  ]
                },
                {
                  "required": [
                    "linkPreview"
                  ]
                },
                {
                  "required": [
                    "caption"
                  ]
                },
                {
                  "required": [
                    "mediaType"
                  ]
                }
              ]
            }
          },
          {
            "required": [
              "mediaId"
            ],
            "not": {
              "anyOf": [
                {
                  "required": [
                    "text"
                  ]
                },
                {
                  "required": [
                    "linkPreview"
                  ]
                }
              ]
            }
          },
          {
            "required": [
              "linkPreview"
            ],
            "not": {
              "anyOf": [
                {
                  "required": [
                    "text"
                  ]
                },
                {
                  "required": [
                    "mediaId"
                  ]
                },
                {
                  "required": [
                    "caption"
                  ]
                },
                {
                  "required": [
                    "mediaType"
                  ]
                }
              ]
            }
          }
        ],
        "allOf": [
          {
            "if": {
              "required": [
                "mediaType"
              ],
              "properties": {
                "mediaType": {
                  "const": "videoNote"
                }
              }
            },
            "then": {
              "required": [
                "mediaId"
              ],
              "properties": {
                "caption": {
                  "type": "string",
                  "pattern": "^\\s*$"
                }
              }
            }
          }
        ]
      },
      "MediaInput": {
        "type": "object",
        "properties": {
          "filename": {
            "type": "string",
            "maxLength": 120
          },
          "mime": {
            "type": "string",
            "enum": [
              "image/jpeg",
              "image/png",
              "video/mp4",
              "application/pdf",
              "audio/ogg",
              "image/webp"
            ]
          },
          "base64": {
            "type": "string",
            "format": "byte",
            "description": "8 Mio maximum après décodage. JPEG, PNG, MP4, PDF ou Ogg Opus validé : mono/stéréo, 20 minutes maximum. MP3, M4A et Ogg Vorbis refusés."
          }
        },
        "required": [
          "filename",
          "mime",
          "base64"
        ],
        "additionalProperties": false,
        "description": "Fichier privé ≤8 Mio. WebP sticker statique uniquement, 512×512 exact et ≤100 Kio. JPEG/PNG, MP4 H.264, PDF, Ogg/Opus mono ou stéréo ≤20 min. Validation des octets, aucun champ de durée client accepté. Une note vidéo ronde réutilise ce MP4 privé : H.264 carré, une piste vidéo, durée positive ≤60s, audio AAC-LC facultatif (mp4a.40.2, mono/stéréo, 8–48 kHz) et aucune autre piste ; pas de conversion. media.kind reste video. width/height/videoNoteCompatible sont dérivés serveur, jamais acceptés en entrée. Une ancienne vidéo sans mesures doit être réimportée."
      },
      "OperationInput": {
        "type": "object",
        "properties": {
          "params": {
            "type": "object"
          },
          "query": {
            "type": "object"
          },
          "body": {
            "type": "object"
          },
          "requestId": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{8,100}$"
          }
        },
        "required": [],
        "additionalProperties": false
      },
      "Job": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "channelId": {
            "type": "string"
          },
          "operationId": {
            "type": "string"
          },
          "requestId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "sending",
              "completed",
              "failed",
              "unknown"
            ]
          },
          "result": {},
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "status"
        ],
        "additionalProperties": false
      },
      "WebhookEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "message.received",
              "message.sent",
              "message.status",
              "webhook.test"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "type": "object"
          }
        },
        "required": [
          "id",
          "type",
          "createdAt",
          "data"
        ],
        "additionalProperties": false
      },
      "LinkPreview": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "url",
          "title",
          "mediaId"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "URL HTTPS publique sur443, nom DNS uniquement sans identifiants, IP littérale, domaine local/réservé ; toutes les réponses DNS sont vérifiées sans téléchargementHTTP."
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "type": "string",
            "maxLength": 1000
          },
          "mediaId": {
            "type": "string",
            "description": "Miniature privée JPEG ou PNG de cette organisation, ≤1Mio. Aucun URL ou octet client dans cet objet."
          }
        }
      },
      "HistoryMessage": {
        "type": "object",
        "description": "Message du journal BECLEVER. Les données média sont des métadonnées privées ; aucune récupération automatique ni octet de fichier. Les états peuvent évoluer après la lecture.",
        "required": [
          "id",
          "conversationId",
          "channelId",
          "chatId",
          "direction",
          "kind",
          "body",
          "status",
          "created",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Identifiant interne stable à utiliser pour dédupliquer."
          },
          "conversationId": {
            "type": "string"
          },
          "channelId": {
            "type": "string"
          },
          "chatId": {
            "type": "string"
          },
          "direction": {
            "type": "string",
            "enum": [
              "in",
              "out"
            ]
          },
          "kind": {
            "type": "string",
            "description": "Type enregistré, notamment text ou nom de transport sortant ; ne pas déduire la livraison du type."
          },
          "body": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "État enregistré au moment de la lecture ; accepté ne signifie pas livré ni lu."
          },
          "providerId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Identifiant fournisseur lorsqu’il est connu ; distinct de id."
          },
          "created": {
            "type": "integer",
            "description": "Date de création enregistrée en millisecondes Unix."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "caption": {
            "type": "string"
          },
          "media": {
            "type": "object",
            "description": "Métadonnées média existantes : id privé et mime après import, ou mimetype/filename/kind/downloadAvailable pour un reçu non importé. Un fichier supprimé peut porter revoked:true. Les octets se téléchargent séparément avec media:read. Pour un MP4 privé, width, height et videoNoteCompatible sont mesurés côté serveur. Les anciennes métadonnées non mesurées ne prouvent pas la compatibilité.",
            "additionalProperties": true
          },
          "linkPreview": {
            "$ref": "#/components/schemas/LinkPreview"
          },
          "mediaType": {
            "type": "string",
            "description": "Présentation d’envoi du média privé, notamment videoNote pour une note vidéo ronde ; kind vaut alors sendVideoNote, tandis que media.kind reste video."
          },
          "provider": {
            "type": "string",
            "enum": [
              "whatsapp",
              "telegram"
            ],
            "description": "Transport de ce canal. Champ optionnel pour compatibilité des anciens DTO."
          },
          "editKind": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "text",
              "caption",
              null
            ],
            "description": "Type modifiable selon la preuve d’envoi et les capacités ; null interdit la modification."
          },
          "editRevision": {
            "type": [
              "string",
              "null"
            ],
            "description": "Révision opaque à recopier sans la recalculer."
          },
          "editState": {
            "type": "string",
            "enum": [
              "idle",
              "sending",
              "sent",
              "unknown"
            ]
          },
          "editedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "reactionAllowed": {
            "type": "boolean",
            "description": "Vrai seulement si la cible et sa preuve permettent une réaction explicite du bot."
          },
          "reactionRevision": {
            "type": [
              "string",
              "null"
            ],
            "description": "Révision opaque à recopier sans la recalculer."
          },
          "reactionState": {
            "type": "string",
            "enum": [
              "idle",
              "sending",
              "sent",
              "unknown"
            ]
          },
          "reactionEmoji": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              null,
              "",
              "👍",
              "❤",
              "🔥",
              "👏",
              "😁",
              "🎉"
            ],
            "description": "Dernière réaction du bot confirmée par BECLEVER ; null avant toute confirmation, chaîne vide après retrait confirmé. Ne représente pas les réactions des autres participants."
          },
          "reactedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "additionalProperties": true
      },
      "MessageHistoryPage": {
        "type": "object",
        "required": [
          "messages",
          "nextCursor",
          "hasMore"
        ],
        "additionalProperties": false,
        "properties": {
          "messages": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/HistoryMessage"
            }
          },
          "nextCursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Curseur opaque de la page suivante, null lorsqu’il n’y en a plus."
          },
          "hasMore": {
            "type": "boolean",
            "description": "D’autres messages existent dans le relevé borné."
          }
        }
      },
      "Conversation": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "tenantId",
          "channelId",
          "chatId",
          "publicationId",
          "nodeId",
          "status",
          "reason",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Identifiant interne de la conversation BECLEVER."
          },
          "tenantId": {
            "type": "string"
          },
          "channelId": {
            "type": "string"
          },
          "chatId": {
            "type": "string",
            "description": "WhatsApp @c.us/@lid/@g.us ou Telegram privé tg:<id>."
          },
          "publicationId": {
            "type": "string"
          },
          "nodeId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "État courant conservé par BECLEVER, sans preuve de présence ou de livraison WhatsApp."
          },
          "reason": {
            "type": "string"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Dernière mise à jour conservée ; ne détermine pas l’ordre des pages."
          },
          "provider": {
            "type": "string",
            "enum": [
              "whatsapp",
              "telegram"
            ],
            "description": "Transport de ce canal. Champ optionnel pour compatibilité des anciens DTO."
          },
          "conversationTags": {
            "type": "array",
            "maxItems": 20,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "maxLength": 40
            }
          },
          "contactTags": {
            "type": "array",
            "maxItems": 20,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "maxLength": 40
            }
          },
          "tags": {
            "type": "array",
            "maxItems": 40,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "maxLength": 40
            }
          },
          "tagRevisions": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "conversation": {
                "type": "integer",
                "minimum": 0
              },
              "contact": {
                "type": "integer",
                "minimum": 0
              }
            }
          },
          "title": {
            "type": "string",
            "maxLength": 200,
            "description": "Titre conservé de la conversation Telegram ; la présence d’un titre ne prouve pas un historique chargé."
          },
          "chatType": {
            "type": "string",
            "enum": [
              "private",
              "group",
              "channel"
            ]
          },
          "canSend": {
            "type": "boolean",
            "description": "Capacité conservée pour cette conversation. Les droits et l’état du compte sont revérifiés avant tout envoi."
          },
          "manualOnly": {
            "type": "boolean",
            "description": "Un compte personnel Telegram utilise des réponses manuelles, sans agent IA ni scénario automatique."
          },
          "connectionMode": {
            "type": "string",
            "enum": [
              "account",
              "bot"
            ],
            "description": "Mode du canal Telegram : compte personnel ou bot."
          }
        },
        "description": "Conversation locale de l’organisation. Aucun message, variable de parcours ni contenu privé de travail ajouté à ce DTO. conversationTags marque cet échange ; contactTags est partagé pour le même chatId exact et fournisseur entre comptes de cette organisation ; tags réunit les deux. Aucun rapprochement automatique PN/LID ou WhatsApp/Telegram. Écriture des étiquettes uniquement dans le Studio."
      },
      "ConversationList": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "conversations"
        ],
        "properties": {
          "conversations": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/Conversation"
            }
          }
        },
        "description": "Réponse historique sans paramètres : 100 conversations maximum, mises à jour les plus récentes en premier."
      },
      "ConversationPage": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "conversations",
          "hasMore",
          "nextCursor"
        ],
        "properties": {
          "conversations": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/Conversation"
            }
          },
          "hasMore": {
            "type": "boolean"
          },
          "nextCursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Curseur opaque de page suivante ; null en dernière page."
          }
        }
      },
      "ConversationDetail": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "conversation"
        ],
        "properties": {
          "conversation": {
            "$ref": "#/components/schemas/Conversation"
          }
        }
      },
      "MessageEditInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "expectedRevision",
          "requestId"
        ],
        "properties": {
          "expectedRevision": {
            "type": "string",
            "description": "Révision opaque editRevision de la dernière lecture."
          },
          "requestId": {
            "type": "string",
            "minLength": 8,
            "maxLength": 100,
            "pattern": "^[A-Za-z0-9_-]+$"
          },
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 4096,
            "description": "Texte brut ; maximum 4096 unités UTF-16, sans HTML ni Markdown."
          },
          "caption": {
            "type": "string",
            "maxLength": 1024,
            "description": "Légende brute ; chaîne vide pour effacer, maximum 1024 unités UTF-16."
          }
        },
        "oneOf": [
          {
            "required": [
              "text"
            ],
            "not": {
              "required": [
                "caption"
              ]
            }
          },
          {
            "required": [
              "caption"
            ],
            "not": {
              "required": [
                "text"
              ]
            }
          }
        ]
      },
      "MessageEditResult": {
        "type": "object",
        "required": [
          "edit",
          "message"
        ],
        "properties": {
          "edit": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "state": {
                "type": "string",
                "enum": [
                  "sending",
                  "sent",
                  "unknown"
                ]
              },
              "reused": {
                "type": "boolean"
              }
            }
          },
          "message": {
            "$ref": "#/components/schemas/HistoryMessage"
          }
        }
      },
      "MessageReactionInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "emoji",
          "expectedRevision",
          "requestId"
        ],
        "properties": {
          "emoji": {
            "type": "string",
            "enum": [
              "",
              "👍",
              "❤",
              "🔥",
              "👏",
              "😁",
              "🎉"
            ],
            "description": "Chaîne vide pour retirer la réaction du bot."
          },
          "expectedRevision": {
            "type": "string",
            "description": "Révision opaque reactionRevision de la dernière lecture."
          },
          "requestId": {
            "type": "string",
            "minLength": 8,
            "maxLength": 100,
            "pattern": "^[A-Za-z0-9_-]+$"
          }
        }
      },
      "MessageReactionResult": {
        "type": "object",
        "required": [
          "reaction",
          "message"
        ],
        "properties": {
          "message": {
            "$ref": "#/components/schemas/HistoryMessage"
          },
          "reaction": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "state": {
                "type": "string",
                "enum": [
                  "sending",
                  "sent",
                  "unknown"
                ]
              },
              "reused": {
                "type": "boolean"
              }
            }
          }
        }
      }
    }
  }
}
