Entwickleranleitung · REST und MCP
Wissensdatenbank synchronisieren
Schreib weiter in deinem bestehenden System und schick die Änderungen an Sonny. In dieser Anleitung legst du eine Kategorie „Abrechnung“ an, synchronisierst einen Artikel als Entwurf und veröffentlichst ihn dann für eine Zielgruppe verifizierter Kunden.
1. Kanal wählen und Schlüssel erstellen
- Öffne Kanäle → dein Kanal. Kopiere die ID nach /app/sources/ aus der Dashboard-URL. Die API nennt diese Kanal-ID sourceId; sie unterscheidet sich von der siteId des Widgets und vom öffentlichen Slug des Hilfecenters.
- Erteile unter Einstellungen → Entwickler → API-Schlüssel → Schlüssel erstellen die Berechtigungen kb:read und kb:write. Beschränke den Kanalzugriff auf den Kanal, den du synchronisieren willst. Speichere den nur einmal angezeigten Schlüssel im Secret Manager deines Servers.
- Setze die folgenden Variablen in deiner Backend-Umgebung. Die Beispiele nutzen curl; ersetze die großgeschriebenen IDs durch die Werte, die Sonny zurückgibt. Teste alles mit einem Testkanal, bevor du ein Live-Hilfecenter synchronisierst.
# 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"Optional zum Nachschlagen: GET /api/v1/sources und das MCP-Tool list_sources liefern die zugänglichen Kanäle und erfordern conversations:read. Diese Berechtigung brauchst du nicht, wenn du die Kanal-ID schon hast. Domains verbindest du weiterhin im Dashboard.
API-Schlüssel ausführlich einrichten2. Kategorie erstellen oder aktualisieren
Verwende in der URL eine stabile ID aus deinem Quellsystem. Sendest du dieselbe externe ID erneut, wird die vorhandene Kategorie aktualisiert statt dupliziert. Beim ersten Erstellen wird das Hilfecenter dieses Kanals automatisch eingerichtet.
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}'Du erhältst 201 beim Erstellen oder 200 beim Aktualisieren, mit der Kategorie unter data. Speichere data.id, wenn du Sonnys interne Kategorie-ID brauchst. Kodiere externe IDs für die URL und halte sie stabil, auch wenn sich Titel ändern.
3. Artikel als Entwurf synchronisieren
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"}'Die Antwort enthält data.id und bereinigtes HTML in data.body. Öffne den Artikel unter Kanäle → dein Kanal → Hilfecenter, um die Formatierung zu prüfen. Markdown wird in HTML umgewandelt; rohes HTML wird mit bodyFormat: "html" (Standard) ebenfalls akzeptiert. Der Artikeltitel ist unabhängig von Überschriften im Text.
Verweise mit categoryExternalId auf die Kategorie aus Schritt 2 oder mit categoryId auf ihre Sonny-ID. Gib nur eins von beiden an. Setze einen der Werte auf null, um die Kategoriezuordnung eines Artikels aufzuheben. Jedes Upsert braucht title und body; für Teiländerungen gibt es PATCH. Neue Artikel werden standardmäßig als Entwurf angelegt, wenn du keinen status angibst.
4. Zugriff festlegen, dann veröffentlichen
Für dieses Beispiel mit privatem Hilfecenter verbindest du zuerst die Anmeldung verifizierter Kunden. Erstelle die Zielgruppe und speichere die zurückgegebene 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"]}]}}'Ersetze unten AUDIENCE_ID, ARTICLE_ID und die Login-URL. Die erste Anfrage schaltet das Hilfecenter live und beschränkt es auf Pro-Kunden. Die zweite veröffentlicht den Artikel. Der Login-Ablauf deiner App muss ein signiertes Kunden-JWT mit dem Hilfecenter austauschen; signInUrl allein meldet niemanden an.
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"}'Teste mit der URL aus GET /help-center die Leseransicht im angemeldeten Zustand und in einem abgemeldeten Browser. API-Schlüssel und MCP handeln als Mitarbeitende und können Inhalte innerhalb ihrer Berechtigungen lesen; eine erfolgreiche Antwort beweist also nicht, dass ein Kunde den Artikel sehen kann.
Für ein öffentliches Hilfecenter verwendest du audience: "everyone" und audienceId: null für Hilfecenter und Artikel – und prüfst auch den Zugriff der Kategorie. Um nur eine benannte Gruppe zu entfernen, sende audienceId: null; der Zugriff nur für verifizierte Kunden bleibt bestehen, solange du audience nicht ausdrücklich änderst. Fehlt audienceId, bleibt die bisherige Zuordnung erhalten. Alle Einschränkungen von Hilfecenter, Kategorie und Artikel müssen zutreffen.
Benannte Regeln nutzen match: "all" oder "any", bis zu 10 Bedingungen und streng typisierte Werte. Operatoren: eq, neq, in, not_in, exists, not_exists, gt, gte, lt, lte. Numerische Vergleiche erwarten Zahlen; in/not_in erwarten Arrays mit 1–100 skalaren Werten; exists/not_exists kommen ohne value aus. Fehlende Merkmale lassen Vergleiche fehlschlagen. Regelverhalten, Vererbung und Fehlerbehebung beim Zugriff.
5. Synchronisierung aktuell halten
Wiederholt schreiben, ohne Inhalte zu duplizieren
Wiederhole PUT mit derselben externen ID, sobald sich deine Quelle ändert. Externe IDs sind innerhalb eines Hilfecenters eindeutig. Weggelassene Felder behalten beim Update ihren aktuellen Wert. Lass status bei späteren Upserts weg, damit die Veröffentlichung erhalten bleibt; sendest du ausdrücklich draft, wird ein Live-Artikel zurückgezogen. publishedAt wird bei der ersten Veröffentlichung gesetzt und bleibt danach unverändert. Das Feld ist schreibgeschützt, historische Veröffentlichungsdaten lassen sich also nicht importieren. Unveränderte Inhalte und reine Metadaten-Änderungen lösen kein unnötiges Re-Embedding aus.
Änderungen und Statistiken abrufen
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"Artikel- und Kategorielisten liefern data plus pagination: page, limit, total. Seiten beginnen bei 1; limit ist standardmäßig 50 und akzeptiert bis zu 100. Behalte dieselben Filter bei und frage Seiten ab, bis page × limit den Wert total erreicht. Beide Listen akzeptieren externalId und updatedSince; Artikel zusätzlich categoryId und status (draft oder published). Verwende für updatedSince einen ISO-Zeitstempel mit Zeitzone; Einträge, die genau zu diesem Zeitpunkt aktualisiert wurden, sind enthalten.
Ruf die Detailansicht jedes Artikels ab, um den vollständigen body sowie viewCount, helpfulYes und helpfulNo zu erhalten. Diese Statistiken sind schreibgeschützt. updatedSince listet aktuelle Einträge auf, meldet aber keine Löschungen. Verfolge Entfernungen in deinem Quellsystem und lösche den entsprechenden Sonny-Eintrag ausdrücklich, wenn das gewollt ist.
Bild hochladen
curl --fail-with-body -X POST "$BASE/media" \
-H "Authorization: Bearer $SONNY_API_KEY" \
-F "file=@billing.png;type=image/png"Verwende die zurückgegebene data.url in Markdown-Bildsyntax oder einem HTML-img-Element und synchronisiere dann den Artikeltext. Uploads akzeptieren JPEG, PNG, GIF und WebP bis 25 MB und setzen ein eingerichtetes Hilfecenter voraus. Hochgeladene URLs sind öffentlich; eine Zielgruppen-Einschränkung des Artikels schützt die Bild-URL nicht. MCP verwendet statt Multipart-Formulardaten upload_kb_media mit sourceId, fileName, contentType und dataBase64 der Datei.
Artikel und Kategorien anordnen
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"]}'Hol zuerst alle Seiten ohne Status-, Kategorie- oder Datumsfilter ab, einschließlich Entwürfen. Ersetze die Beispiel-IDs durch jede aktuelle Artikel-ID – genau einmal und in der gewünschten Reihenfolge. Für die vollständige Kategorienliste verwendest du /categories/reorder. Eine erfolgreiche Neuanordnung liefert 204 ohne Body. Hat sich die Liste geändert oder enthält sie fremde, doppelte oder fehlende IDs, aktualisiere sie vor dem nächsten Versuch. Du kannst auch bei einzelnen Schreibvorgängen eine nicht negative position setzen.
Derselbe Ablauf über MCP
- Verbinde deinen MCP-Client und wähle den richtigen Arbeitsbereich. Prüfe, dass er kb:read und kb:write hat und auf deinen Kanal zugreifen darf.
- Verwende die Kanal-ID aus dem Dashboard oder rufe list_sources auf, wenn deine Zugangsdaten auch conversations:read haben. Verwende nie stattdessen die siteId des Widgets.
- Führe die ersten fünf Beispiele unten der Reihe nach aus. Ersetze SOURCE_ID durch deine Kanal-ID und ARTICLE_ID/AUDIENCE_ID durch die data.id des jeweils vorherigen Ergebnisses. Ersetze die Login-URL und verbinde die Anmeldung, bevor du veröffentlichst.
- Prüfe Änderungen mit list_kb_articles. Ordne erst neu, nachdem du die vollständige, ungefilterte Liste inklusive Entwürfen abgerufen hast. Aktualisiere deine Listen nach erfolgreichen Schreibvorgängen.
Jedes Beispiel zeigt den Tool-Namen und seine Argumente. Upsert-Felder stehen direkt in den Argumenten; Update-Tools erwarten geänderte Felder in updates. Das sind Eingaben für Tool-Aufrufe, keine HTTP-Anfragen an den MCP-Endpunkt.
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"
}
}
}Weitere Operationen
Die REST-Pfade unten sind relativ zu BASE. Alle Felder und Antwortschemata findest du in der API-Referenz.
| Aufgabe | REST | MCP |
|---|---|---|
| Artikel auflisten / abrufen | GET /articles · GET /articles/{articleId} | list_kb_articles · get_kb_article |
| Kategorien auflisten / abrufen | GET /categories · GET /categories/{categoryId} | list_kb_categories · get_kb_category |
| Ohne externe ID erstellen | POST /articles · POST /categories | create_kb_article · create_kb_category |
| Vorhandene Inhalte bearbeiten | PATCH /articles/{articleId} · PATCH /categories/{categoryId} | update_kb_article · update_kb_category |
| Kategorien anordnen | PUT /categories/reorder | reorder_kb_categories |
| Hilfecenter-Einstellungen lesen / bearbeiten | GET /help-center · PATCH /help-center | get_help_center · update_help_center |
| Zielgruppen auflisten / bearbeiten | GET /audiences · PATCH /audiences/{audienceId} | list_kb_audiences · update_kb_audience |
| Löschen | DELETE /articles/{articleId} · /categories/{categoryId} · /audiences/{audienceId} | delete_kb_article · delete_kb_category · delete_kb_audience |
Das Löschen eines Artikels ist endgültig. Wird eine Kategorie gelöscht, verlieren ihre Artikel die Kategoriezuordnung und die geerbte Kategorie-Einschränkung entfällt. Wird eine Zielgruppe gelöscht, entfallen ihre Merkmalsregeln, verknüpfte Inhalte bleiben aber nur für verifizierte Kunden sichtbar. Prüf den betroffenen Zugriff vor dem Löschen; nutze Entwürfe, um Artikel zurückzuziehen, die du behalten willst.
Fehlerbehebung bei der Synchronisierung
- 400 · Ungültige Anfrage
- Prüfe die Pflichtfelder title/body bzw. name, die exakten Feldnamen, gültige Regeltypen und vollständige IDs beim Neuanordnen. Sende categoryId oder categoryExternalId, nicht beide.
- 401 · Nicht autorisiert
- Gib einen gültigen Bearer-Schlüssel an. Prüfe, ob er abgelaufen ist oder widerrufen wurde.
- 403 · Verboten
- Prüfe die Berechtigungen des Schlüssels, die Rechte seines Inhabers und die Abrechnung des Arbeitsbereichs. Verbinde einen MCP-Client mit dem nötigen Zugriff neu, wenn seine Tools nur lesen können.
- 404 · Nicht gefunden
- Prüfe, ob die Quelle zum gewählten Arbeitsbereich gehört und für die Zugangsdaten freigegeben ist. Ein neues Hilfecenter richtest du ein, indem du zuerst Inhalte erstellst; Lesezugriffe legen es nicht an.
- 409 · Konflikt
- Ruf die aktuellen Inhalte erneut ab und gleiche die kollidierende ID oder die veraltete Reihenfolge ab, bevor du es noch einmal versuchst.
- 413 · Zu groß
- Verkleinere das Bild unter das Limit von 25 MB.
- 429 · Rate-Limit erreicht
- Warte die Zeit aus Retry-After ab, bevor du es erneut versuchst. Steuere das Anfragetempo mit RateLimit-Limit, RateLimit-Remaining und RateLimit-Reset.
Verwandte Anleitungen
- Hilfecenter
Veröffentliche Antworten, ordne Kategorien, leg den Lesezugriff fest und halte dein Hilfecenter aktuell.
- Zielgruppen im Hilfecenter
Erstelle Kundengruppen, mach dein Hilfecenter privat und teste, wer welche Antwort lesen darf.
- Öffentliche APIBeta
Sichte und synchronisiere Unterhaltungen, teile Kundenkontext, lies Berichte und sende Dateien – mit API-Schlüsseln mit gezielten Berechtigungen.
- Sonny MCPBeta
Verbinde KI-Assistenten per OAuth oder eingeschränkten Schlüsseln mit Posteingang, Kundengedächtnis, Berichten und Dateien.