Toute la documentation
/// RÉFÉRENCE

L’API et le serveur MCP.

Tout est en lecture. Cinq points d’entrée REST, neuf événements de webhook, quatre outils MCP — et la façon exacte de s’authentifier auprès des trois.

Adresse de base https://convosia.app

S’authentifier

Un seul mécanisme pour l’API, les webhooks sortants et le serveur MCP : un jeton porteur. Le jeton se crée dans vos réglages, onglet API, et n’est affiché qu’une fois — nous n’en gardons qu’une empreinte SHA-256, et personne chez nous ne peut le relire.

Un jeton commence toujours par « cvk_ », suivi de quarante caractères tirés au sort. Le préfixe n’est pas décoratif : GitHub et consorts balaient les dépôts publics à la recherche de motifs connus, et un secret publié par mégarde avec un préfixe reconnaissable se fait repérer et signaler.

Chaque appel porte l’en-tête :

Authorization: Bearer cvk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json

curl :

curl -s https://convosia.app/api/v1/me \
  -H "Authorization: Bearer $CONVOSIA_TOKEN"
{
  "organization": { "id": "01K5...", "name": "Boutique Awa" },
  "token": {
    "name": "Intégration Zapier",
    "prefix": "cvk_a4f2c1",
    "scopes": ["conversations:read", "knowledge:read"],
    "expires_at": null
  }
}

Les portées

Un jeton n’ouvre que ce que vous cochez. La portée est déclarée sur la route elle-même, pas enfouie dans un contrôleur : on ouvre le fichier de routes et on voit d’un coup d’œil ce qu’un jeton ouvre.

conversations:read Lister les conversations, lire un fil complet avec ses messages.
contacts:read Lister les contacts et leurs identités de canal.
knowledge:read Interroger la base de connaissances du marchand.

`/v1/me` n’exige aucune portée : « ce jeton marche-t-il ? » doit avoir une réponse, même pour un jeton qui n’ouvre rien.

Les points d’entrée

Cinq, tous en lecture, tous préfixés par la version. Le numéro de version coûte quatre caractères aujourd’hui et évite une impasse plus tard : une API publiée sans lui ne peut plus jamais changer de forme sans casser quelqu’un.

Appel
Portée
Ce qu’il rend
GET /api/v1/me —
L’organisation du jeton, son nom, ses portées et sa date d’expiration.
GET /api/v1/conversations conversations:read
Les conversations, de la plus récente à la plus ancienne.
GET /api/v1/conversations/{id} conversations:read
Le fil complet : tous les messages, dans l’ordre.
GET /api/v1/contacts contacts:read
Les contacts avec leurs identités — numéro WhatsApp, adresse, visiteur du widget.
GET · POST /api/v1/knowledge/search knowledge:read
Les extraits de documents qui répondent le mieux à une question.

La recherche accepte GET et POST : une question de trois cents caractères dans une URL se fait tronquer par les intermédiaires.

curl -s "https://convosia.app/api/v1/conversations?status=open&limit=10" \
  -H "Authorization: Bearer $CONVOSIA_TOKEN"
{
  "data": [
    {
      "id": "01K5...",
      "contact_id": "01K5...",
      "status": "open",
      "awaits_human": true,
      "last_inbound_at": "2026-08-23T21:14:07+00:00",
      "last_outbound_at": "2026-08-23T21:14:31+00:00"
    }
  ],
  "next_cursor": null
}
curl -s https://convosia.app/api/v1/knowledge/search \
  -H "Authorization: Bearer $CONVOSIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"q":"quels sont vos délais de livraison ?","limit":3}'
{
  "data": [
    {
      "source_id": "01K5...",
      "source_title": "Conditions de vente.pdf",
      "ordinal": 4,
      "content": "Livraison à Abidjan sous 24 h…",
      "score": 0.8123,
      "corroborated": true
    }
  ]
}

Filtres et pagination

La pagination se fait au curseur, jamais par numéro de page : une page 3 change de contenu dès qu’une conversation arrive, et l’on saute alors une ligne sans le voir.

Paramètre
Où
Valeurs
limit
conversations
1 à 100. Par défaut 25.
limit
contacts
1 à 200. Par défaut 50.
limit
knowledge/search
1 à 20. Par défaut 5.
status
conversations
open, pending, closed. Omettre pour toutes.
cursor
conversations, contacts
Le `next_cursor` de la réponse précédente.
q
knowledge/search
La question, en langage naturel. Obligatoire.

`next_cursor` vaut `null` dès que la page n’est pas pleine : une page à moitié remplie est la dernière, et faire faire un appel de plus pour l’apprendre coûte une requête à chaque parcours.

Les erreurs

Toujours la même forme : un code stable qu’un programme teste, un message que lit un humain. Un intégrateur qui doit faire correspondre des phrases françaises pour distinguer deux erreurs écrit du code qui casse à la première reformulation.

{ "error": { "code": "insufficient_scope", "message": "…" } }
Statut
Code
Quand
401 missing_token
Aucun en-tête `Authorization`.
401 invalid_token
Jeton inconnu, expiré ou révoqué. Les trois cas rendent le MÊME message : distinguer « expiré » de « inconnu » dirait à qui essaie des jetons au hasard lequel était le bon.
403 insufficient_scope
Le jeton est valide mais n’a pas la portée. Le message nomme la portée manquante : vous êtes déjà authentifié, vous avez droit à une erreur qui dit quoi faire.
404 not_found
La conversation n’existe pas — ou appartient à quelqu’un d’autre, ce qui donne la même réponse.
422 missing_query
Recherche sans paramètre `q`.
429 —
Limite de débit atteinte.

Limitation de débit

Cent vingt appels par minute et par jeton. Vingt par minute et par adresse IP pour les requêtes sans jeton — elles se font refuser de toute façon, et sans limite on pourrait essayer des jetons au hasard aussi vite que le réseau le permet.

La limite porte sur le JETON, pas sur l’adresse : deux marchands derrière le même réseau d’entreprise ne doivent pas se gêner, et un intégrateur qui change d’adresse ne doit pas contourner sa limite. La clé du compteur est une empreinte du jeton, jamais le jeton lui-même — un secret dans une clé de cache est un secret dans un magasin que personne ne surveille.

Webhooks sortants

Un point de livraison se déclare dans vos réglages. Chaque envoi est signé, horodaté, et rejoué jusqu’à huit fois en cas d’échec.

Les événements

Vous choisissez lesquels vous intéressent, point de livraison par point de livraison.

conversation.opened escalation.opened escalation.resolved order.placed order.confirmed appointment.booked appointment.cancelled contact.created form.completed

La charge utile

{
  "id": "evt_01K5...",
  "type": "escalation.opened",
  "created_at": "2026-08-23T21:14:07+00:00",
  "organization_id": "01K5...",
  "data": { }
}

`id` est là pour que vous puissiez écarter un doublon : une reprise de file peut rejouer une livraison, et un receveur bien écrit garde les identifiants vus et ne traite chacun qu’une fois.

Les en-têtes

Convosia-Signature t={horodatage},v1={HMAC-SHA256}
Convosia-Event Le type de l’événement, pour router sans décoder la charge.
Convosia-Delivery L’identifiant de CETTE tentative de livraison.
User-Agent Convosia-Webhooks/1.0

Vérifier la signature

La signature porte sur « horodatage.corps », et non sur le corps seul : sans l’horodatage dans ce qui est signé, une charge interceptée resterait valable indéfiniment. Comparez en temps constant, et refusez au-delà de cinq minutes.

// Node.js
const crypto = require('crypto');

function verifie(corpsBrut, entete, secret) {
  const parts = Object.fromEntries(
    entete.split(',').map((p) => p.split('='))
  );
  const age = Math.abs(Math.floor(Date.now() / 1000) - Number(parts.t));
  if (!Number.isFinite(age) || age > 300) return false;

  const attendu = crypto
    .createHmac('sha256', secret)
    .update(`${parts.t}.${corpsBrut}`)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(attendu),
    Buffer.from(parts.v1 ?? '')
  );
}
# PHP
$parts = [];
parse_str(str_replace(',', '&', $_SERVER['HTTP_CONVOSIA_SIGNATURE']), $parts);

if (abs(time() - (int) $parts['t']) > 300) {
    http_response_code(400);
    exit;
}

$attendu = hash_hmac('sha256', $parts['t'] . '.' . $corpsBrut, $secret);

if (! hash_equals($attendu, $parts['v1'] ?? '')) {
    http_response_code(400);
    exit;
}

Reprises et extinction

Huit tentatives étalées en arrière exponentielle. Une adresse qui échoue vingt fois de suite est désactivée automatiquement — ce n’est pas une punition : un point de livraison mort qu’on continue d’appeler finit par faire prendre notre domaine pour un importun. L’adresse est revérifiée AVANT CHAQUE ENVOI, et pas seulement à l’enregistrement : un nom de domaine peut se mettre à pointer vers un réseau privé entre deux livraisons.

Le serveur MCP

Un seul point d’entrée en POST, qui parle JSON-RPC 2.0. Il permet à un modèle — Claude, ou tout client qui parle le protocole — d’interroger le compte du marchand directement.

POST https://convosia.app/mcp · JSON-RPC 2.0 · 2025-06-18

Hors du préfixe `/v1/`, et ce n’est pas un oubli : sa version est celle du PROTOCOLE, négociée à la poignée de main. Lui coller un numéro de version à nous ferait deux versions à accorder pour une seule surface.

Les méthodes

initialize La poignée de main. Rend la version du protocole, les capacités et le nom du serveur.
ping Rend un résultat vide. Sert à savoir si la connexion tient.
tools/list Le catalogue des outils, FILTRÉ par les portées du jeton.
tools/call Exécute un outil. Attend `name` et `arguments`.
curl -s https://convosia.app/mcp \
  -H "Authorization: Bearer $CONVOSIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize"}'
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": { "tools": { "listChanged": false } },
    "serverInfo": { "name": "convosia", "version": "1.0.0" }
  }
}

Les outils

Un modèle ne voit dans `tools/list` que ce qu’il peut réellement appeler. Un outil visible mais refusé à l’appel apprend au modèle à réessayer, et il réessaie.

lister_conversations conversations:read

Les conversations récentes. Arguments : `statut` (open, pending, closed), `limite` (1 à 100).

lire_conversation conversations:read

Le fil complet d’une conversation. Argument : `id`.

lister_contacts contacts:read

Les contacts et leurs identités de canal. Argument : `limite` (1 à 200).

chercher_connaissances knowledge:read

Cherche dans les documents du marchand. Arguments : `question`, `limite` (1 à 20).

curl -s https://convosia.app/mcp \
  -H "Authorization: Bearer $CONVOSIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "chercher_connaissances",
      "arguments": { "question": "délais de livraison", "limite": 3 }
    }
  }'

Brancher un client

La plupart des clients MCP acceptent un serveur distant en HTTP avec un en-tête. Voici la forme attendue :

{
  "mcpServers": {
    "convosia": {
      "url": "https://convosia.app/mcp",
      "headers": {
        "Authorization": "Bearer cvk_…"
      }
    }
  }
}

Les erreurs JSON-RPC

-32600 Requête JSON-RPC invalide.
-32601 Méthode inconnue.
-32602 Paramètre attendu manquant — `name` sur `tools/call`.
Tout est en lecture

Aucun outil n’écrit dans le compte du marchand, et ce n’est pas une étape qu’on franchira distraitement plus tard : laisser un modèle envoyer des messages à de vrais clients demande des garde-fous que ce serveur n’a pas — confirmation humaine, limitation par contact, journal séparé. Le jour où ce sera fait, ce sera un chantier à soi.

Ce qui vous protège des autres marchands

Le jeton désigne une organisation, et le tenant est posé AVANT que le contrôleur ne s’exécute. Après ce geste, toute requête à la base est bornée : même un contrôleur écrit distraitement ne peut pas lire les conversations d’une autre organisation, parce qu’il n’y a plus d’« autre organisation » atteignable. L’isolation est structurelle, pas vigilante — et douze suites de tests le vérifient.

Une question sur l’API ? Nous écrire