开发者教程 · REST 与 MCP

同步你的知识库

继续在你现有的系统中写作,并把变更发送到 Sonny。本教程会创建一个“账单”分类,同步一篇草稿文章,然后将其发布给一个已验证的客户受众。

1. 选择渠道并创建密钥

  1. 打开渠道 → 你的渠道。复制其后台 URL 中 /app/sources/ 之后的 ID。API 把这个渠道 ID 称为 sourceId;它不同于聊天组件的 siteId,也不同于帮助中心的公开 slug。
  2. 在设置 → 开发者 → API 密钥 → 创建密钥中,授予 kb:read 和 kb:write。将渠道访问权限限制为你要同步的渠道。把只显示一次的密钥保存到服务器的密钥管理工具中。
  3. 在你的后端环境中设置以下变量。这些示例使用 curl;请将大写的 ID 替换为 Sonny 返回的值。同步正式的帮助中心之前,请先在测试渠道上运行。
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"

可选的来源查询:GET /api/v1/sources 和 MCP 工具 list_sources 会返回可访问的渠道,需要 conversations:read。如果你已经有渠道 ID,就不需要这个权限范围。连接域名仍需在后台完成。

完整的 API 密钥设置

2. 创建或更新分类

在 URL 中使用来源系统中稳定的 ID。再次发送相同的外部 ID 会更新已有分类,而不是创建重复项。首次创建会自动初始化此渠道的帮助中心。

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

创建时返回 201,更新时返回 200,分类位于 data 下。需要 Sonny 内部的分类 ID 时,请保存 data.id。对外部 ID 进行 URL 编码;即使标题变化,也要保持它们稳定。

3. 将文章同步为草稿

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

响应包含 data.id,以及 data.body 中经过清理的 HTML。在“渠道 → 你的渠道 → 帮助中心”中打开文章检查格式。Markdown 会被转换为 HTML;也可以通过 bodyFormat: "html"(默认值)直接提交原始 HTML。文章标题与正文中的标题是分开的。

使用 categoryExternalId 引用第 2 步中的分类,或使用 categoryId 引用其 Sonny ID。只能提供其中一个。将任一字段设为 null 即可取消文章的分类。每次 upsert 都需要 title 和 body;部分修改可使用 PATCH。省略 status 时,新文章默认为草稿。

4. 设置访问权限,然后发布

在这个私密帮助中心的示例中,请先连接已验证客户登录。创建受众并保存返回的 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"]}]}}'

替换下面的 AUDIENCE_ID、ARTICLE_ID 和登录 URL。第一个请求会让帮助中心上线并仅限 Pro 客户访问;第二个请求发布文章。你的应用登录流程必须与帮助中心交换已签名的客户 JWT;仅设置 signInUrl 并不会让任何人登录。

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

使用 GET /help-center 返回的 URL,分别测试已登录读者视图和未登录的浏览器。API 密钥和 MCP 以员工身份操作,可以在其权限范围内读取内容;它们的请求成功并不能证明客户能看到这篇文章。

对于公开帮助中心,请在帮助中心和文章上使用 audience: "everyone" 和 audienceId: null,并检查分类的访问权限。若只想移除某个指定群组,请发送 audienceId: null;除非你显式更改 audience,否则“仅限已验证”的访问限制仍会保留。省略 audienceId 会保留之前的设置。帮助中心、分类和文章的所有限制都必须满足。

命名规则使用 match: "all" 或 "any",最多 10 个条件,值的类型严格校验。运算符:eq、neq、in、not_in、exists、not_exists、gt、gte、lt、lte。数值比较使用数字;in/not_in 使用包含 1–100 个标量值的数组;exists/not_exists 省略 value。缺少特征时比较不成立。查看规则行为、继承和访问权限排查。

5. 保持同步最新

重复写入而不产生重复内容

每当来源内容变化时,使用相同的外部 ID 重复执行 PUT。外部 ID 在一个帮助中心内唯一。更新时省略的字段会保留当前值。之后的 upsert 中省略 status 可保留发布状态;显式发送 draft 会撤回已发布的文章。publishedAt 在首次发布时设置,之后保持不变。它是只读字段,因此无法导入历史发布日期。内容未变化或仅修改元数据时,不会触发不必要的重新向量化。

读取变更和分析数据

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"

文章和分类列表返回 data 以及 pagination:page、limit、total。页码从 1 开始;limit 默认 50,最大 100。保持相同的筛选条件逐页请求,直到 page × limit 达到 total。两种列表都接受 externalId 和 updatedSince;文章还接受 categoryId 和 status(draft 或 published)。updatedSince 请使用带时区的 ISO 时间戳;它包含在该时间更新的记录。

读取每篇文章的详情可获得完整的 body、viewCount、helpfulYes 和 helpfulNo。这些分析数据是只读的。updatedSince 列出的是当前记录,不会报告删除。请在来源系统中跟踪删除,并在需要时显式删除对应的 Sonny 记录。

上传图片

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

在 Markdown 图片语法或 HTML img 元素中使用返回的 data.url,然后同步文章正文。上传支持最大 25 MB 的 JPEG、PNG、GIF 和 WebP,且要求帮助中心已初始化。上传后的 URL 是公开的;文章的受众限制不会保护图片 URL。MCP 使用 upload_kb_media,传入 sourceId、fileName、contentType 和文件的 dataBase64,而不是 multipart 表单数据。

排列文章和分类

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

先在不带状态、分类或日期筛选的情况下获取所有页面,包括草稿。将示例 ID 替换为每个当前文章 ID,每个恰好出现一次,并按期望的顺序排列。完整的分类列表请使用 /categories/reorder。重新排序成功会返回 204,且没有响应体。如果列表已变化,或包含外来、重复或缺失的 ID,请刷新列表后再重试。你也可以在单独的写入中设置非负的 position。

通过 MCP 使用同样的流程

  1. 连接你的 MCP 客户端并选择正确的工作区。确认它拥有 kb:read 和 kb:write,并且可以访问你的渠道。
  2. 使用后台中的渠道 ID;如果你的凭据还有 conversations:read,也可以调用 list_sources。切勿用聊天组件的 siteId 代替。
  3. 按顺序运行下面前五个示例。将 SOURCE_ID 替换为你的渠道 ID,将 ARTICLE_ID/AUDIENCE_ID 替换为之前各结果中的 data.id。发布前请替换登录 URL 并连接登录。
  4. 使用 list_kb_articles 查看变更。只有在收集到包括草稿在内的完整、未筛选列表后,才能执行重新排序。写入成功后请刷新列表。

每个示例都显示了工具名称及其参数。upsert 的字段直接放在参数中;update 类工具则把变更的字段放在 updates 中。这些是工具调用的输入,而不是发往 MCP 端点的 HTTP 请求。

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

相关操作

以下 REST 路径均相对于 BASE。每个字段和响应结构请参阅 API 参考。

知识库相关的 REST 与 MCP 操作
任务RESTMCP
列出 / 获取文章GET /articles · GET /articles/{articleId}list_kb_articles · get_kb_article
列出 / 获取分类GET /categories · GET /categories/{categoryId}list_kb_categories · get_kb_category
不带外部 ID 创建POST /articles · POST /categoriescreate_kb_article · create_kb_category
编辑已有内容PATCH /articles/{articleId} · PATCH /categories/{categoryId}update_kb_article · update_kb_category
排列分类PUT /categories/reorderreorder_kb_categories
读取 / 编辑帮助中心设置GET /help-center · PATCH /help-centerget_help_center · update_help_center
列出 / 编辑受众GET /audiences · PATCH /audiences/{audienceId}list_kb_audiences · update_kb_audience
删除DELETE /articles/{articleId} · /categories/{categoryId} · /audiences/{audienceId}delete_kb_article · delete_kb_category · delete_kb_audience

删除文章是永久性的。删除分类会让其中的文章变为未分类,并移除该分类继承下来的限制。删除受众会移除其特征规则,关联内容仍保持“仅限已验证客户”。删除前请检查受影响的访问权限;如果想保留文章,请改用草稿来撤回。

同步故障排查

400 · 请求无效
检查必填的 title/body 或 name、字段名是否完全正确、规则类型是否有效,以及重新排序的 ID 是否完整。categoryId 和 categoryExternalId 只能发送一个。
401 · 未授权
请提供有效的 Bearer 密钥,并检查它是否已过期或被撤销。
403 · 禁止访问
检查密钥的权限范围、密钥所有者的权限和工作区账单状态。如果 MCP 客户端的工具是只读的,请用所需的权限重新连接。
404 · 未找到
确认该来源属于所选工作区,且凭据允许访问。新的帮助中心需要先创建内容才能初始化;读取操作不会创建它。
409 · 冲突
重新获取当前内容,处理冲突的 ID 或过时的排序后再重试。
413 · 内容过大
将图片缩小到 25 MB 限制以下。
429 · 请求过于频繁
等待 Retry-After 指定的时间后再重试。使用 RateLimit-Limit、RateLimit-Remaining 和 RateLimit-Reset 控制请求节奏。

相关文档