Handleiding voor ontwikkelaars · REST en MCP

Je kennisbank synchroniseren

Blijf schrijven in je eigen systeem en stuur wijzigingen naar Sonny. In deze handleiding maak je een categorie Facturatie aan, synchroniseer je een artikel als concept en publiceer je het daarna voor een doelgroep van geverifieerde klanten.

1. Kies een kanaal en maak een sleutel aan

  1. Open Kanalen → je kanaal. Kopieer het ID na /app/sources/ in de URL van het dashboard. De API noemt dit kanaal-ID sourceId; het is iets anders dan de siteId van de widget en de openbare slug van het helpcentrum.
  2. Geef in Instellingen → Ontwikkelaar → API-sleutels → Sleutel aanmaken de rechten kb:read en kb:write. Beperk Toegang tot kanalen tot het kanaal dat je wilt synchroniseren. Bewaar de sleutel, die je maar één keer ziet, in de secret manager van je server.
  3. Stel de variabelen hieronder in de omgeving van je backend in. Deze voorbeelden gebruiken curl; vervang de ID's in hoofdletters door de waarden die Sonny teruggeeft. Probeer ze eerst uit op een testkanaal voordat je een live helpcentrum synchroniseert.
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"

Optioneel opzoeken: GET /api/v1/sources en de MCP-tool list_sources geven de kanalen terug waar je toegang toe hebt en vereisen conversations:read. Heb je het kanaal-ID al, dan heb je die scope niet nodig. Een domein koppelen doe je nog steeds in het dashboard.

API-sleutels volledig instellen

2. Maak de categorie aan of werk hem bij

Gebruik in de URL een vast ID uit je bronsysteem. Stuur je hetzelfde externe ID opnieuw, dan wordt de bestaande categorie bijgewerkt in plaats van dat er een dubbele bijkomt. Bij de eerste keer aanmaken wordt het helpcentrum van dit kanaal automatisch ingericht.

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}'

Je krijgt 201 bij aanmaken of 200 bij bijwerken, met de categorie onder data. Bewaar data.id als je het interne categorie-ID van Sonny nodig hebt. Codeer externe ID's voor gebruik in een URL en houd ze gelijk, ook als titels veranderen.

3. Synchroniseer een artikel als concept

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"}'

Het antwoord bevat data.id en opgeschoonde HTML in data.body. Open het artikel via Kanalen → je kanaal → Helpcentrum om de opmaak te controleren. Markdown wordt omgezet naar HTML; ruwe HTML wordt ook geaccepteerd met bodyFormat: "html" (de standaard). De titel van het artikel staat los van de koppen in de tekst.

Gebruik categoryExternalId om naar de categorie uit stap 2 te verwijzen, of categoryId voor het Sonny-ID. Geef er maar één op. Zet een van beide op null om een artikel uit zijn categorie te halen. Elke upsert heeft een title en body nodig; voor gedeeltelijke wijzigingen is PATCH beschikbaar. Nieuwe artikelen worden standaard een concept als je status weglaat.

4. Stel de toegang in en publiceer

Koppel voor dit voorbeeld van een privé-helpcentrum eerst inloggen voor geverifieerde klanten. Maak de doelgroep aan en bewaar de teruggegeven data.id:

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"]}]}}'

Vervang AUDIENCE_ID, ARTICLE_ID en de inlog-URL hieronder. Het eerste verzoek zet het helpcentrum Live en beperkt het tot Pro-klanten. Het tweede publiceert het artikel. De inlogflow van je app moet een ondertekende klant-JWT uitwisselen met het helpcentrum; alleen signInUrl instellen logt niemand in.

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"}'

Gebruik de URL die GET /help-center teruggeeft om de weergave voor ingelogde lezers te testen, en ook een uitgelogde browser. API-sleutels en MCP werken als teamlid en kunnen content lezen binnen hun scopes; een geslaagd antwoord bewijst dus niet dat een klant het artikel kan zien.

Gebruik voor een openbaar helpcentrum audience: "everyone" en audienceId: null op het helpcentrum en het artikel, en controleer ook de toegang van de categorie. Wil je alleen een benoemde groep weghalen, stuur dan audienceId: null; de beperking tot geverifieerde klanten blijft dan staan, tenzij je audience zelf aanpast. Laat je audienceId weg, dan blijft de vorige toewijzing gelden. Alle beperkingen op helpcentrum, categorie en artikel moeten kloppen.

Benoemde regels gebruiken match: "all" of "any", maximaal 10 voorwaarden en strikt getypeerde waarden. Operatoren: eq, neq, in, not_in, exists, not_exists, gt, gte, lt, lte. Numerieke vergelijkingen verwachten getallen; in/not_in verwachten arrays met 1–100 enkelvoudige waarden; bij exists/not_exists laat je value weg. Ontbrekende kenmerken laten een vergelijking mislukken. Bekijk hoe regels werken, overerving en problemen met toegang oplossen.

5. Houd de synchronisatie actueel

Herhaald schrijven zonder dubbele content

Herhaal PUT met hetzelfde externe ID zodra je bron verandert. Externe ID's zijn uniek binnen een helpcentrum. Velden die je bij een update weglaat, behouden hun huidige waarde. Laat status weg bij latere upserts om de publicatie te behouden; stuur je expliciet draft, dan wordt een live artikel ingetrokken. publishedAt wordt bij de eerste publicatie gezet en verandert daarna niet. Het is alleen-lezen, dus historische publicatiedatums kun je niet importeren. Ongewijzigde content en wijzigingen aan alleen metadata leiden niet tot onnodig opnieuw embedden.

Wijzigingen en statistieken lezen

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"

Lijsten met artikelen en categorieën geven data terug plus pagination: page, limit, total. Pagina's beginnen bij 1; limit is standaard 50 en kan tot 100. Houd dezelfde filters aan en vraag pagina's op tot page × limit total bereikt. Beide lijsten accepteren externalId en updatedSince; artikelen accepteren ook categoryId en status (draft of published). Gebruik voor updatedSince een ISO-tijdstempel met tijdzone; records die op dat moment zijn bijgewerkt, tellen mee.

Lees de details van elk artikel op om de volledige body, viewCount, helpfulYes en helpfulNo te krijgen. Deze statistieken zijn alleen-lezen. updatedSince toont de huidige records, maar meldt geen verwijderingen. Houd verwijderingen bij in je bronsysteem en verwijder het bijbehorende record in Sonny expliciet als dat de bedoeling is.

Een afbeelding uploaden

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

Gebruik de teruggegeven data.url in Markdown-afbeeldingssyntax of een HTML-img-element en synchroniseer daarna de body van het artikel. Uploads accepteren JPEG, PNG, GIF en WebP tot 25 MB en vereisen een ingericht helpcentrum. Geüploade URL's zijn openbaar; de doelgroepbeperking van een artikel beschermt de URL van de afbeelding niet. MCP gebruikt upload_kb_media met sourceId, fileName, contentType en de dataBase64 van het bestand in plaats van multipart-formuliergegevens.

Artikelen en categorieën ordenen

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"]}'

Haal eerst elke pagina op zonder filters op status, categorie of datum, inclusief concepten. Vervang de voorbeeld-ID's door elk huidig artikel-ID, precies één keer en in de gewenste volgorde. Gebruik /categories/reorder voor de volledige lijst met categorieën. Een geslaagde herschikking geeft 204 terug zonder body. Is de lijst veranderd of bevat die vreemde, dubbele of ontbrekende ID's, vernieuw hem dan voordat je het opnieuw probeert. Je kunt ook een niet-negatieve position meegeven bij afzonderlijke schrijfacties.

Dezelfde werkwijze via MCP

  1. Koppel je MCP-client en kies de juiste werkruimte. Controleer of hij kb:read en kb:write heeft, met toegang tot je kanaal.
  2. Gebruik het kanaal-ID uit het dashboard, of roep list_sources aan als je sleutel ook conversations:read heeft. Gebruik nooit de siteId van de widget in plaats daarvan.
  3. Voer de eerste vijf voorbeelden hieronder op volgorde uit. Vervang SOURCE_ID door je kanaal-ID, en ARTICLE_ID/AUDIENCE_ID door de data.id uit het eerdere resultaat. Vervang de inlog-URL en koppel het inloggen voordat je publiceert.
  4. Gebruik list_kb_articles om wijzigingen te bekijken. Voer een herschikking pas uit nadat je de volledige, ongefilterde lijst hebt opgehaald, inclusief concepten. Vernieuw je lijsten na geslaagde schrijfacties.

Elk voorbeeld toont de naam van de tool en de argumenten. Upsert-velden staan direct in de argumenten; update-tools zetten gewijzigde velden in updates. Dit zijn invoerwaarden voor een tool-aanroep, geen HTTP-verzoek naar het MCP-endpoint.

1. upsert_kb_category
MCP-tool-aanroep
{
  "name": "upsert_kb_category",
  "arguments": {
    "sourceId": "SOURCE_ID",
    "externalId": "billing",
    "name": "Billing",
    "position": 0
  }
}
2. upsert_kb_article
MCP-tool-aanroep
{
  "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
MCP-tool-aanroep
{
  "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
MCP-tool-aanroep
{
  "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
MCP-tool-aanroep
{
  "name": "update_kb_article",
  "arguments": {
    "sourceId": "SOURCE_ID",
    "articleId": "ARTICLE_ID",
    "updates": {
      "audience": "verified",
      "audienceId": "AUDIENCE_ID",
      "status": "published"
    }
  }
}
6. list_kb_articles
MCP-tool-aanroep
{
  "name": "list_kb_articles",
  "arguments": {
    "sourceId": "SOURCE_ID",
    "page": 1,
    "limit": 100,
    "updatedSince": "2026-09-01T00:00:00Z"
  }
}
7. reorder_kb_articles
MCP-tool-aanroep
{
  "name": "reorder_kb_articles",
  "arguments": {
    "sourceId": "SOURCE_ID",
    "ids": [
      "ARTICLE_ID_1",
      "ARTICLE_ID_2"
    ]
  }
}
8. update_kb_audience
MCP-tool-aanroep
{
  "name": "update_kb_audience",
  "arguments": {
    "sourceId": "SOURCE_ID",
    "audienceId": "AUDIENCE_ID",
    "updates": {
      "name": "Paid customers"
    }
  }
}

Gerelateerde bewerkingen

De REST-paden hieronder zijn relatief ten opzichte van BASE. Lees de API-referentie voor elk veld en elk antwoordschema.

Gerelateerde REST- en MCP-bewerkingen voor de kennisbank
TaakRESTMCP
Artikelen opsommen / ophalenGET /articles · GET /articles/{articleId}list_kb_articles · get_kb_article
Categorieën opsommen / ophalenGET /categories · GET /categories/{categoryId}list_kb_categories · get_kb_category
Aanmaken zonder extern IDPOST /articles · POST /categoriescreate_kb_article · create_kb_category
Bestaande content bewerkenPATCH /articles/{articleId} · PATCH /categories/{categoryId}update_kb_article · update_kb_category
Categorieën ordenenPUT /categories/reorderreorder_kb_categories
Instellingen van het helpcentrum lezen / bewerkenGET /help-center · PATCH /help-centerget_help_center · update_help_center
Doelgroepen opsommen / bewerkenGET /audiences · PATCH /audiences/{audienceId}list_kb_audiences · update_kb_audience
VerwijderenDELETE /articles/{articleId} · /categories/{categoryId} · /audiences/{audienceId}delete_kb_article · delete_kb_category · delete_kb_audience

Een artikel verwijderen is definitief. Verwijder je een categorie, dan raken de artikelen hun categorie kwijt en vervalt de overgenomen categoriebeperking. Verwijder je een doelgroep, dan verdwijnen de kenmerkregels, maar blijft gekoppelde content beperkt tot geverifieerde klanten. Controleer de toegang die geraakt wordt voordat je iets verwijdert; gebruik concepten om artikelen in te trekken als je ze wilt bewaren.

Problemen met een synchronisatie oplossen

400 · Ongeldig verzoek
Controleer de verplichte title/body of name, exacte veldnamen, geldige regeltypen en volledige ID's bij herschikken. Stuur categoryId of categoryExternalId, niet allebei.
401 · Niet geautoriseerd
Geef een geldige Bearer-sleutel mee. Controleer of hij is verlopen of ingetrokken.
403 · Geen toegang
Controleer de scopes van de sleutel, de rechten van de eigenaar van de sleutel en de facturatie van de werkruimte. Koppel een MCP-client opnieuw met de vereiste toegang als de tools alleen-lezen zijn.
404 · Niet gevonden
Controleer of de bron bij de gekozen werkruimte hoort en of de sleutel er toegang toe heeft. Een nieuw helpcentrum richt je in door eerst content aan te maken; lezen maakt het niet aan.
409 · Conflict
Haal de huidige content opnieuw op en los het conflicterende ID of de verouderde volgorde op voordat je het opnieuw probeert.
413 · Te groot
Maak de afbeelding kleiner dan de limiet van 25 MB.
429 · Limiet bereikt
Wacht tot Retry-After voorbij is voordat je het opnieuw probeert. Gebruik RateLimit-Limit, RateLimit-Remaining en RateLimit-Reset om je verzoeken te doseren.

Gerelateerde handleidingen