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
- 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.
- 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.
- 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.
# 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 instellen2. 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.
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
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:
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.
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
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
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
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
- Koppel je MCP-client en kies de juiste werkruimte. Controleer of hij kb:read en kb:write heeft, met toegang tot je kanaal.
- 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.
- 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.
- 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
{
"name": "upsert_kb_category",
"arguments": {
"sourceId": "SOURCE_ID",
"externalId": "billing",
"name": "Billing",
"position": 0
}
}2. upsert_kb_article
{
"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
{
"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
{
"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
{
"name": "update_kb_article",
"arguments": {
"sourceId": "SOURCE_ID",
"articleId": "ARTICLE_ID",
"updates": {
"audience": "verified",
"audienceId": "AUDIENCE_ID",
"status": "published"
}
}
}6. list_kb_articles
{
"name": "list_kb_articles",
"arguments": {
"sourceId": "SOURCE_ID",
"page": 1,
"limit": 100,
"updatedSince": "2026-09-01T00:00:00Z"
}
}7. reorder_kb_articles
{
"name": "reorder_kb_articles",
"arguments": {
"sourceId": "SOURCE_ID",
"ids": [
"ARTICLE_ID_1",
"ARTICLE_ID_2"
]
}
}8. update_kb_audience
{
"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.
| Taak | REST | MCP |
|---|---|---|
| Artikelen opsommen / ophalen | GET /articles · GET /articles/{articleId} | list_kb_articles · get_kb_article |
| Categorieën opsommen / ophalen | GET /categories · GET /categories/{categoryId} | list_kb_categories · get_kb_category |
| Aanmaken zonder extern ID | POST /articles · POST /categories | create_kb_article · create_kb_category |
| Bestaande content bewerken | PATCH /articles/{articleId} · PATCH /categories/{categoryId} | update_kb_article · update_kb_category |
| Categorieën ordenen | PUT /categories/reorder | reorder_kb_categories |
| Instellingen van het helpcentrum lezen / bewerken | GET /help-center · PATCH /help-center | get_help_center · update_help_center |
| Doelgroepen opsommen / bewerken | GET /audiences · PATCH /audiences/{audienceId} | list_kb_audiences · update_kb_audience |
| Verwijderen | DELETE /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
- Helpcentrum
Publiceer antwoorden, orden categorieën, kies wie mag lezen en houd je helpcentrum actueel.
- Doelgroepen van het helpcentrum
Maak klantgroepen aan, maak je helpcentrum privé en test wie elk antwoord kan lezen.
- Openbare APIBeta
Sorteer en synchroniseer gesprekken, deel klantcontext, lees rapportages en stuur bestanden met API-sleutels met beperkte rechten.
- Sonny MCPBeta
Koppel AI-assistenten aan inboxworkflows, klantherinneringen, rapportages en bestanden met OAuth of sleutels met beperkte rechten.