Guía para desarrolladores · REST y MCP
Sincroniza tu base de conocimiento
Sigue escribiendo en tu sistema actual y envía los cambios a Sonny. En esta guía se crea una categoría Facturación, se sincroniza un artículo como borrador y después se publica para una audiencia de clientes verificados.
1. Elige un canal y crea una clave
- Abre Canales → tu canal. Copia el ID que aparece después de /app/sources/ en la URL del panel. La API llama a este ID de canal sourceId; es distinto del siteId del widget y del slug público del centro de ayuda.
- En Ajustes → Desarrolladores → Claves de API → Crear clave, concede kb:read y kb:write. Limita el acceso a canales al canal que vas a sincronizar. Guarda la clave, que solo se muestra una vez, en el gestor de secretos de tu servidor.
- Define las variables siguientes en el entorno de tu backend. Estos ejemplos usan curl; sustituye los IDs en mayúsculas por los valores que devuelve Sonny. Pruébalos con un canal de prueba antes de sincronizar un centro de ayuda publicado.
# 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"Descubrimiento opcional: GET /api/v1/sources y la herramienta MCP list_sources devuelven los canales accesibles y requieren conversations:read. No necesitas ese permiso si ya tienes el ID del canal. La conexión del dominio se sigue haciendo desde el panel.
Configuración completa de claves de API2. Crea o actualiza la categoría
Usa en la URL un ID estable de tu sistema de origen. Si vuelves a enviar el mismo ID externo, se actualiza la categoría existente en lugar de crear un duplicado. La primera creación inicializa automáticamente el centro de ayuda de este canal.
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}'Recibirás 201 al crear o 200 al actualizar, con la categoría en data. Guarda data.id si necesitas el ID interno de la categoría en Sonny. Codifica en la URL los IDs externos y mantenlos estables aunque cambien los títulos.
3. Sincroniza un artículo como borrador
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 respuesta contiene data.id y el HTML saneado en data.body. Abre el artículo en Canales → tu canal → Centro de ayuda para comprobar su formato. El Markdown se convierte a HTML; también se acepta HTML sin procesar con bodyFormat: "html" (el valor predeterminado). El título del artículo es independiente de los encabezados del cuerpo.
Usa categoryExternalId para referirte a la categoría del paso 2, o categoryId para su ID en Sonny. Indica solo uno. Pon cualquiera de los dos a null para dejar un artículo sin categoría. Cada upsert necesita title y body; PATCH está disponible para ediciones parciales. Los artículos nuevos son borradores por defecto si se omite status.
4. Define el acceso y publica
Para este ejemplo de centro privado, conecta primero el inicio de sesión de clientes verificados. Crea la audiencia y guarda el data.id que devuelve:
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"]}]}}'Sustituye AUDIENCE_ID, ARTICLE_ID y la URL de inicio de sesión de abajo. La primera petición publica el centro de ayuda y lo restringe a los clientes Pro. La segunda publica el artículo. El flujo de inicio de sesión de tu aplicación debe intercambiar un JWT de cliente firmado con el centro de ayuda; definir signInUrl por sí solo no inicia la sesión de nadie.
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"}'Usa la URL que devuelve GET /help-center para probar la vista del lector con sesión iniciada y un navegador sin sesión. Las claves de API y MCP actúan como personal del equipo y pueden leer el contenido dentro de sus permisos; una respuesta correcta no demuestra que un cliente pueda ver el artículo.
Para un centro público, usa audience: "everyone" y audienceId: null en el centro y en el artículo, y revisa también el acceso de la categoría. Para quitar solo un grupo con nombre, envía audienceId: null; el acceso solo para verificados se mantiene salvo que cambies audience explícitamente. Si omites audienceId, se conserva la asignación anterior. Las restricciones del centro de ayuda, la categoría y el artículo deben coincidir.
Las reglas con nombre usan match: "all" o "any", hasta 10 condiciones y valores de tipo estricto. Operadores: eq, neq, in, not_in, exists, not_exists, gt, gte, lt, lte. Las comparaciones numéricas aceptan números; in/not_in aceptan arrays de 1 a 100 valores escalares; exists/not_exists omiten value. Los atributos ausentes no superan las comparaciones. Consulta el comportamiento de las reglas, la herencia y la resolución de problemas de acceso.
5. Mantén la sincronización al día
Repite escrituras sin duplicar contenido
Repite el PUT con el mismo ID externo cada vez que cambie tu origen. Los IDs externos son únicos dentro de un centro de ayuda. Los campos omitidos en una actualización conservan su valor actual. Omite status en los upserts posteriores para mantener la publicación; enviar draft de forma explícita retira un artículo publicado. publishedAt se fija en la primera publicación y no cambia. Es de solo lectura, así que no se pueden importar fechas de publicación históricas. El contenido sin cambios y las ediciones solo de metadatos no provocan re-embeddings innecesarios.
Lee cambios y analíticas
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"Las listas de artículos y categorías devuelven data más pagination: page, limit, total. Las páginas empiezan en 1; limit vale 50 por defecto y admite hasta 100. Mantén los mismos filtros y pide páginas hasta que page × limit alcance total. Ambas listas aceptan externalId y updatedSince; los artículos también aceptan categoryId y status (draft o published). Usa una marca de tiempo ISO con zona horaria para updatedSince; incluye los registros actualizados en ese momento.
Lee el detalle de cada artículo para obtener su body completo, viewCount, helpfulYes y helpfulNo. Estas analíticas son de solo lectura. updatedSince lista los registros actuales; no informa de eliminaciones. Controla las eliminaciones en tu sistema de origen y elimina explícitamente el registro correspondiente en Sonny cuando corresponda.
Sube una imagen
curl --fail-with-body -X POST "$BASE/media" \
-H "Authorization: Bearer $SONNY_API_KEY" \
-F "file=@billing.png;type=image/png"Usa el data.url devuelto en la sintaxis de imagen de Markdown o en un elemento img de HTML y después sincroniza el cuerpo del artículo. Las subidas admiten JPEG, PNG, GIF y WebP de hasta 25 MB y requieren un centro de ayuda inicializado. Las URL subidas son públicas; la restricción de audiencia de un artículo no protege la URL de la imagen. MCP usa upload_kb_media con sourceId, fileName, contentType y el dataBase64 del archivo en lugar de datos de formulario multipart.
Ordena artículos y categorías
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"]}'Primero obtén todas las páginas sin filtros de status, categoría ni fecha, borradores incluidos. Sustituye los IDs de ejemplo por todos los IDs de artículo actuales, cada uno una sola vez y en el orden deseado. Usa /categories/reorder para la lista completa de categorías. Una reordenación correcta devuelve 204 sin cuerpo. Si la lista ha cambiado o incluye IDs ajenos, duplicados o ausentes, actualízala antes de reintentar. También puedes indicar una position no negativa en escrituras individuales.
Usa el mismo flujo a través de MCP
- Conecta tu cliente MCP y elige el espacio de trabajo correcto. Comprueba que tiene kb:read y kb:write, con acceso a tu canal.
- Usa el ID de canal del panel, o llama a list_sources si tu credencial también tiene conversations:read. Nunca uses en su lugar el siteId del widget.
- Ejecuta en orden los cinco primeros ejemplos de abajo. Sustituye SOURCE_ID por el ID de tu canal, y ARTICLE_ID/AUDIENCE_ID por el data.id de cada resultado anterior. Sustituye la URL de inicio de sesión y conecta el inicio de sesión antes de publicar.
- Usa list_kb_articles para revisar los cambios. Ejecuta una reordenación solo después de reunir la lista completa sin filtros, borradores incluidos. Actualiza tus listas tras cada escritura correcta.
Cada ejemplo muestra el nombre de la herramienta y sus argumentos. Los campos de upsert van directamente en los argumentos; las herramientas de actualización ponen los campos cambiados dentro de updates. Son entradas de llamadas a herramientas, no una petición HTTP al endpoint MCP.
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"
}
}
}Operaciones relacionadas
Las rutas REST de abajo son relativas a BASE. Consulta la referencia de la API para ver todos los campos y esquemas de respuesta.
| Tarea | REST | MCP |
|---|---|---|
| Listar / obtener artículos | GET /articles · GET /articles/{articleId} | list_kb_articles · get_kb_article |
| Listar / obtener categorías | GET /categories · GET /categories/{categoryId} | list_kb_categories · get_kb_category |
| Crear sin ID externo | POST /articles · POST /categories | create_kb_article · create_kb_category |
| Editar contenido existente | PATCH /articles/{articleId} · PATCH /categories/{categoryId} | update_kb_article · update_kb_category |
| Ordenar categorías | PUT /categories/reorder | reorder_kb_categories |
| Leer / editar ajustes del centro de ayuda | GET /help-center · PATCH /help-center | get_help_center · update_help_center |
| Listar / editar audiencias | GET /audiences · PATCH /audiences/{audienceId} | list_kb_audiences · update_kb_audience |
| Eliminar | DELETE /articles/{articleId} · /categories/{categoryId} · /audiences/{audienceId} | delete_kb_article · delete_kb_category · delete_kb_audience |
Eliminar un artículo es permanente. Eliminar una categoría deja sus artículos sin categoría y quita la restricción heredada de esa categoría. Eliminar una audiencia quita sus reglas de atributos y deja el contenido vinculado solo para verificados. Revisa el acceso afectado antes de eliminar; usa borradores para retirar artículos que quieras conservar.
Resolver problemas de sincronización
- 400 · Petición no válida
- Comprueba los campos obligatorios title/body o name, los nombres exactos de los campos, los tipos de regla válidos y que la reordenación incluya todos los IDs. Envía categoryId o categoryExternalId, no ambos.
- 401 · No autorizado
- Envía una clave Bearer válida. Comprueba si ha caducado o se ha revocado.
- 403 · Prohibido
- Comprueba los permisos de la clave, los permisos de su propietario y la facturación del espacio de trabajo. Vuelve a conectar el cliente MCP con el acceso necesario si sus herramientas son de solo lectura.
- 404 · No encontrado
- Confirma que el canal pertenece al espacio de trabajo seleccionado y que la credencial lo permite. Inicializa un centro de ayuda nuevo creando contenido primero; las lecturas no lo crean.
- 409 · Conflicto
- Vuelve a obtener el contenido actual y resuelve el ID en conflicto o el orden desactualizado antes de reintentar.
- 413 · Demasiado grande
- Reduce la imagen por debajo del límite de 25 MB.
- 429 · Límite de peticiones
- Espera el tiempo de Retry-After antes de reintentar. Usa RateLimit-Limit, RateLimit-Remaining y RateLimit-Reset para espaciar las peticiones.
Documentación relacionada
- Centro de ayuda
Publica respuestas, organiza categorías, elige quién puede leerlas y mantén tu centro de ayuda al día.
- Audiencias del centro de ayuda
Crea grupos de clientes, haz privado tu centro de ayuda y comprueba quién puede leer cada respuesta.
- API públicaBeta
Clasifica y sincroniza conversaciones, comparte el contexto del cliente, lee informes y envía archivos con claves de API de permisos acotados.
- Sonny MCPBeta
Conecta asistentes de IA a los flujos de la bandeja de entrada, las memorias de clientes, los informes y los archivos con OAuth o claves acotadas.