Tutoriel développeur · REST et MCP

Synchroniser votre base de connaissances

Continuez à rédiger dans votre système actuel et envoyez les changements à Sonny. Ce tutoriel crée une catégorie Facturation, synchronise un article en brouillon, puis le publie pour une audience de clients vérifiés.

1. Choisir un canal et créer une clé

  1. Ouvrez Canaux → votre canal. Copiez l'identifiant qui suit /app/sources/ dans l'URL de son tableau de bord. L'API appelle cet identifiant de canal sourceId ; il est différent du siteId du widget et du slug public du centre d'aide.
  2. Dans Paramètres → Développeur → Clés API → Créer une clé, accordez kb:read et kb:write. Limitez l'accès aux canaux au canal que vous comptez synchroniser. Enregistrez la clé, affichée une seule fois, dans le gestionnaire de secrets de votre serveur.
  3. Définissez les variables ci-dessous dans l'environnement de votre backend. Ces exemples utilisent curl ; remplacez les identifiants en majuscules par les valeurs renvoyées par Sonny. Exécutez-les sur un canal de test avant de synchroniser un centre d'aide en ligne.
bash
# Load SONNY_API_KEY from your server's secret manager first.
# SOURCE_ID is the ID in the channel dashboard URL: /app/sources/SOURCE_ID
export SOURCE_ID="YOUR_SOURCE_ID"
export BASE="https://www.usesonny.com/api/v1/sources/$SOURCE_ID"

Découverte facultative : GET /api/v1/sources et l'outil MCP list_sources renvoient les canaux accessibles et nécessitent conversations:read. Vous n'avez pas besoin de cette portée si vous avez déjà l'identifiant du canal. La connexion d'un domaine reste une tâche du tableau de bord.

Configuration complète des clés API

2. Créer ou mettre à jour la catégorie

Utilisez dans l'URL un identifiant stable issu de votre système source. Renvoyer le même identifiant externe met à jour la catégorie existante au lieu de créer un doublon. La première création initialise automatiquement le centre d'aide de ce canal.

bash
curl --fail-with-body -X PUT "$BASE/categories/by-external-id/billing" \
  -H "Authorization: Bearer $SONNY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Billing","position":0}'

Attendez-vous à un 201 à la création ou un 200 à la mise à jour, avec la catégorie sous data. Conservez data.id si vous avez besoin de l'identifiant interne Sonny de la catégorie. Encodez les identifiants externes pour l'URL ; gardez-les stables même quand les titres changent.

3. Synchroniser un article en brouillon

bash
curl --fail-with-body -X PUT "$BASE/articles/by-external-id/billing-guide" \
  -H "Authorization: Bearer $SONNY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Manage billing","body":"## Find your invoices\n\nOpen **Settings → Billing** in your account.","bodyFormat":"markdown","categoryExternalId":"billing","status":"draft"}'

La réponse contient data.id et le HTML nettoyé dans data.body. Ouvrez l'article dans Canaux → votre canal → Centre d'aide pour vérifier sa mise en forme. Le Markdown est converti en HTML ; le HTML brut est aussi accepté avec bodyFormat: "html" (la valeur par défaut). Le titre de l'article est distinct des titres du corps.

Utilisez categoryExternalId pour faire référence à la catégorie de l'étape 2, ou categoryId pour son identifiant Sonny. N'en fournissez qu'un seul. Définissez l'un ou l'autre sur null pour retirer un article de sa catégorie. Chaque upsert nécessite un title et un body ; PATCH est disponible pour les modifications partielles. Les nouveaux articles sont des brouillons par défaut lorsque status est omis.

4. Définir l'accès, puis publier

Pour cet exemple de centre privé, connectez d'abord la connexion des clients vérifiés. Créez l'audience et conservez le data.id renvoyé :

bash
curl --fail-with-body -X POST "$BASE/audiences" \
  -H "Authorization: Bearer $SONNY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Pro customers","rules":{"match":"all","conditions":[{"trait":"plan","op":"in","value":["pro","business"]}]}}'

Remplacez AUDIENCE_ID, ARTICLE_ID et l'URL de connexion ci-dessous. La première requête met le centre d'aide en ligne et le réserve aux clients Pro. La seconde publie l'article. Le processus de connexion de votre application doit échanger un JWT client signé avec le centre d'aide ; définir signInUrl seul ne connecte personne.

bash
curl --fail-with-body -X PATCH "$BASE/help-center" \
  -H "Authorization: Bearer $SONNY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"enabled":true,"title":"Acme Help Center","audience":"verified","audienceId":"AUDIENCE_ID","signInUrl":"https://app.example.com/login"}'

# Replace ARTICLE_ID and AUDIENCE_ID with the returned data.id values.
curl --fail-with-body -X PATCH "$BASE/articles/ARTICLE_ID" \
  -H "Authorization: Bearer $SONNY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"audienceId":"AUDIENCE_ID","status":"published"}'

Utilisez l'URL renvoyée par GET /help-center pour tester la vue lecteur connecté et un navigateur déconnecté. Les clés API et MCP agissent en tant que membres de l'équipe et peuvent lire le contenu dans les limites de leurs portées ; une réponse réussie ne prouve pas qu'un client peut voir l'article.

Pour un centre public, utilisez audience: "everyone" et audienceId: null sur le centre et l'article, et vérifiez aussi l'accès de la catégorie. Pour retirer uniquement un groupe nommé, envoyez audienceId: null ; l'accès réservé aux clients vérifiés reste en place, sauf si vous modifiez explicitement audience. Un audienceId omis conserve l'attribution précédente. Toutes les restrictions du centre d'aide, de la catégorie et de l'article doivent correspondre.

Les règles nommées utilisent match: "all" ou "any", jusqu'à 10 conditions et des valeurs strictement typées. Opérateurs : eq, neq, in, not_in, exists, not_exists, gt, gte, lt, lte. Les comparaisons numériques prennent des nombres ; in/not_in prennent des tableaux de 1 à 100 valeurs scalaires ; exists/not_exists omettent value. Les attributs manquants font échouer les comparaisons. Voir le comportement des règles, l'héritage et le dépannage des accès.

5. Garder la synchronisation à jour

Répéter les écritures sans dupliquer le contenu

Répétez le PUT avec le même identifiant externe chaque fois que votre source change. Les identifiants externes sont uniques au sein d'un centre d'aide. Les champs omis lors d'une mise à jour conservent leur valeur actuelle. Omettez status lors des upserts suivants pour conserver la publication ; envoyer explicitement draft retire un article en ligne. publishedAt est défini lors de la première publication et ne change plus. Il est en lecture seule : les dates de publication historiques ne peuvent pas être importées. Un contenu inchangé et les modifications de métadonnées seules ne déclenchent pas de recalcul inutile des embeddings.

Lire les changements et les statistiques

bash
curl --fail-with-body --get "$BASE/articles" \
  -H "Authorization: Bearer $SONNY_API_KEY" \
  --data-urlencode "page=1" --data-urlencode "limit=100" \
  --data-urlencode "status=published" \
  --data-urlencode "updatedSince=2026-09-01T00:00:00Z"

# Get the full sanitized HTML and read-only analytics for one article.
curl --fail-with-body "$BASE/articles/ARTICLE_ID" \
  -H "Authorization: Bearer $SONNY_API_KEY"

Les listes d'articles et de catégories renvoient data et pagination : page, limit, total. Les pages commencent à 1 ; limit vaut 50 par défaut et accepte jusqu'à 100. Gardez les mêmes filtres et demandez des pages jusqu'à ce que page × limit atteigne total. Les deux listes acceptent externalId et updatedSince ; les articles acceptent aussi categoryId et status (draft ou published). Utilisez un horodatage ISO avec fuseau horaire pour updatedSince ; il inclut les enregistrements mis à jour à cet instant.

Lisez le détail de chaque article pour obtenir son body complet, viewCount, helpfulYes et helpfulNo. Ces statistiques sont en lecture seule. updatedSince liste les enregistrements actuels ; il ne signale pas les suppressions. Suivez les retraits dans votre système source et supprimez explicitement l'enregistrement Sonny correspondant lorsque c'est voulu.

Importer une image

bash
curl --fail-with-body -X POST "$BASE/media" \
  -H "Authorization: Bearer $SONNY_API_KEY" \
  -F "file=@billing.png;type=image/png"

Utilisez le data.url renvoyé dans la syntaxe d'image Markdown ou dans un élément HTML img, puis synchronisez le corps de l'article. Les fichiers importés acceptent JPEG, PNG, GIF et WebP jusqu'à 25 Mo et nécessitent un centre d'aide initialisé. Les URL importées sont publiques ; la restriction d'audience d'un article ne protège pas l'URL de l'image. MCP utilise upload_kb_media avec sourceId, fileName, contentType et le dataBase64 du fichier au lieu d'un formulaire multipart.

Organiser articles et catégories

bash
curl --fail-with-body -X PUT "$BASE/articles/reorder" \
  -H "Authorization: Bearer $SONNY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ids":["ARTICLE_ID_1","ARTICLE_ID_2"]}'

Récupérez d'abord toutes les pages sans filtre de statut, de catégorie ou de date, brouillons compris. Remplacez les identifiants d'exemple par chaque identifiant d'article actuel, une seule fois, dans l'ordre souhaité. Utilisez /categories/reorder pour la liste complète des catégories. Une réorganisation réussie renvoie 204 sans corps. Si la liste a changé ou contient des identifiants étrangers, en double ou manquants, actualisez-la avant de réessayer. Vous pouvez aussi définir une position positive ou nulle lors d'écritures individuelles.

Utiliser le même processus via MCP

  1. Connectez votre client MCP et choisissez le bon espace de travail. Vérifiez qu'il dispose de kb:read et kb:write, avec l'accès à votre canal.
  2. Utilisez l'identifiant de canal du tableau de bord, ou appelez list_sources si votre identifiant dispose aussi de conversations:read. Ne le remplacez jamais par le siteId du widget.
  3. Exécutez dans l'ordre les cinq premiers exemples ci-dessous. Remplacez SOURCE_ID par l'identifiant de votre canal, et ARTICLE_ID/AUDIENCE_ID par le data.id de chaque résultat précédent. Remplacez l'URL de connexion et connectez la connexion client avant de publier.
  4. Utilisez list_kb_articles pour vérifier les changements. Ne lancez une réorganisation qu'après avoir récupéré la liste complète non filtrée, brouillons compris. Actualisez vos listes après chaque écriture réussie.

Chaque exemple montre le nom de l'outil et ses arguments. Les champs d'upsert vont directement dans les arguments ; les outils de mise à jour placent les champs modifiés dans updates. Il s'agit d'entrées d'appel d'outil, pas d'une requête HTTP vers le point de terminaison MCP.

1. upsert_kb_category
Appel d'outil MCP
{
  "name": "upsert_kb_category",
  "arguments": {
    "sourceId": "SOURCE_ID",
    "externalId": "billing",
    "name": "Billing",
    "position": 0
  }
}
2. upsert_kb_article
Appel d'outil MCP
{
  "name": "upsert_kb_article",
  "arguments": {
    "sourceId": "SOURCE_ID",
    "externalId": "billing-guide",
    "title": "Manage billing",
    "body": "## Find your invoices\n\nOpen **Settings → Billing** in your account.",
    "bodyFormat": "markdown",
    "categoryExternalId": "billing",
    "status": "draft"
  }
}
3. create_kb_audience
Appel d'outil MCP
{
  "name": "create_kb_audience",
  "arguments": {
    "sourceId": "SOURCE_ID",
    "name": "Pro customers",
    "rules": {
      "match": "all",
      "conditions": [
        {
          "trait": "plan",
          "op": "in",
          "value": [
            "pro",
            "business"
          ]
        }
      ]
    }
  }
}
4. update_help_center
Appel d'outil MCP
{
  "name": "update_help_center",
  "arguments": {
    "sourceId": "SOURCE_ID",
    "updates": {
      "enabled": true,
      "title": "Acme Help Center",
      "audience": "verified",
      "audienceId": "AUDIENCE_ID",
      "signInUrl": "https://app.example.com/login"
    }
  }
}
5. update_kb_article
Appel d'outil MCP
{
  "name": "update_kb_article",
  "arguments": {
    "sourceId": "SOURCE_ID",
    "articleId": "ARTICLE_ID",
    "updates": {
      "audience": "verified",
      "audienceId": "AUDIENCE_ID",
      "status": "published"
    }
  }
}
6. list_kb_articles
Appel d'outil MCP
{
  "name": "list_kb_articles",
  "arguments": {
    "sourceId": "SOURCE_ID",
    "page": 1,
    "limit": 100,
    "updatedSince": "2026-09-01T00:00:00Z"
  }
}
7. reorder_kb_articles
Appel d'outil MCP
{
  "name": "reorder_kb_articles",
  "arguments": {
    "sourceId": "SOURCE_ID",
    "ids": [
      "ARTICLE_ID_1",
      "ARTICLE_ID_2"
    ]
  }
}
8. update_kb_audience
Appel d'outil MCP
{
  "name": "update_kb_audience",
  "arguments": {
    "sourceId": "SOURCE_ID",
    "audienceId": "AUDIENCE_ID",
    "updates": {
      "name": "Paid customers"
    }
  }
}

Opérations associées

Les chemins REST ci-dessous sont relatifs à BASE. Consultez la référence de l'API pour chaque champ et schéma de réponse.

Opérations REST et MCP associées à la base de connaissances
TâcheRESTMCP
Lister / lire les articlesGET /articles · GET /articles/{articleId}list_kb_articles · get_kb_article
Lister / lire les catégoriesGET /categories · GET /categories/{categoryId}list_kb_categories · get_kb_category
Créer sans identifiant externePOST /articles · POST /categoriescreate_kb_article · create_kb_category
Modifier un contenu existantPATCH /articles/{articleId} · PATCH /categories/{categoryId}update_kb_article · update_kb_category
Ordonner les catégoriesPUT /categories/reorderreorder_kb_categories
Lire / modifier les paramètres du centre d'aideGET /help-center · PATCH /help-centerget_help_center · update_help_center
Lister / modifier les audiencesGET /audiences · PATCH /audiences/{audienceId}list_kb_audiences · update_kb_audience
SupprimerDELETE /articles/{articleId} · /categories/{categoryId} · /audiences/{audienceId}delete_kb_article · delete_kb_category · delete_kb_audience

La suppression d'un article est définitive. Supprimer une catégorie retire ses articles de la catégorie et supprime la restriction héritée de celle-ci. Supprimer une audience retire ses règles d'attributs, le contenu lié restant réservé aux clients vérifiés. Vérifiez les accès concernés avant de supprimer ; passez des articles en brouillon pour les retirer tout en les conservant.

Dépanner une synchronisation

400 · Requête invalide
Vérifiez les champs obligatoires title/body ou name, les noms de champs exacts, les types de règles valides et l'exhaustivité des identifiants de réorganisation. Envoyez categoryId ou categoryExternalId, pas les deux.
401 · Non autorisé
Fournissez une clé Bearer valide. Vérifiez qu'elle n'a pas expiré ou été révoquée.
403 · Interdit
Vérifiez les portées de la clé, les permissions de son propriétaire et la facturation de l'espace de travail. Reconnectez un client MCP avec les accès requis si ses outils sont en lecture seule.
404 · Introuvable
Vérifiez que la source appartient à l'espace de travail sélectionné et qu'elle est autorisée par l'identifiant. Initialisez un nouveau centre d'aide en créant d'abord du contenu ; les lectures ne le créent pas.
409 · Conflit
Récupérez à nouveau le contenu actuel et résolvez l'identifiant en conflit ou l'ordre obsolète avant de réessayer.
413 · Trop volumineux
Réduisez l'image sous la limite de 25 Mo.
429 · Limite de débit atteinte
Attendez le délai Retry-After avant de réessayer. Utilisez RateLimit-Limit, RateLimit-Remaining et RateLimit-Reset pour cadencer vos requêtes.

Documentation associée