用自定义数据源同步您的系统
自定义数据源用于连接没有内置连接器的系统:您的 wiki、工单系统、数据库,或任何地方导出的数据。您的代码把文档推送到 Sursa,Sursa 把它们转换为卡片,放入数据源所设置的合集和文件夹中。这些卡片与来自其他数据源的卡片一样,支持搜索、回答、核实和权限控制。
与直接写入卡片不同,自定义数据源会记录您发送过的内容。全量同步结束时,Sursa 会归档所有您未发送的文档,因此您的系统中的删除操作无需额外工作就能同步到 Sursa。
设置数据源
管理员在 Sursa 的 Sources → New source → Custom source(数据源 → 新建数据源 → 自定义数据源)中创建数据源,并选择:
- 卡片所在的合集,以及可选的文件夹;
- 文件夹模式:所有卡片都放在所选文件夹中,或者按每个文档的
path建立对应的子文件夹; - 权限:沿用合集的权限,或者“as in the source”(与数据源一致),即由每个文档列出谁可以查看它。
数据源页面显示数据源的 ID;示例中使用 42。
选择密钥
| 密钥 | 开头 | 能做什么 |
|---|---|---|
| 数据源密钥 | kbsrc_ | 向这一个数据源写入文档,除此之外什么都不能做:不能读取,也不能搜索。在数据源页面的 API keys(API 密钥)区块中创建。 |
具有 import:write 权限范围的组织密钥 | kb_ | 向组织的任何自定义数据源写入,并读取其状态。创建数据源及其密钥需要 admin 权限范围。 |
对于单个集成,尤其是运行在第三方平台上的集成,数据源密钥是最安全的选择。具有 import:write 的组织密钥适合向多个数据源提供数据的服务。
两种密钥的传入方式相同,所有推送接口都位于 /v1/push/sources/{source_id} 下。用 GET /v1/push/sources/{source_id} 检查密钥和数据源的状态:
curl https://api.sursa.com/v1/push/sources/42 \
-H "Authorization: Bearer $SURSA_SOURCE_KEY"{"source_id": 42, "name": "Helpdesk articles", "documents": 1840, "open_sync": null}
文档
| 字段 | 必填 | 说明 |
|---|---|---|
external_id | 是 | 文档在您系统中的稳定 ID,最多 512 个字符。再次发送同一个 ID 会替换该文档。 |
title | 是 | 卡片标题,最多 500 个字符。对于文件,默认为文件名。 |
content | 是,有文件时除外 | 最多 5 MB。 |
content_type | 否 | html(默认)、markdown 或 text。HTML 中的脚本、样式、表单和内嵌对象会被清除。 |
url | 否 | 文档在您系统中的链接,显示在卡片上。 |
path | 否 | 文件夹,可以是数组(["Support", "Billing"])或字符串("Support/Billing"),最多 10 层。 |
permissions | 否 | {"everyone": true},或 {"emails": [...], "groups": [...]}。数据源的权限设置为“as in the source”时使用。 |
tags | 否 | 最多 50 个标签,添加到数据源的标签之外。 |
author_email | 否 | 如果是组织中的用户,该用户成为卡片的负责人。 |
updated_at | 否 | 文档在您系统中的更改时间(RFC 3339)。 |
metadata | 否 | 卡片上由您定义的键值对。参见元数据。 |
category | 否 | 卡片的分类:组织中某个分类的键。 |
verifiers、verify_interval_days | 否 | 谁来核实卡片(按电子邮件指定的用户和用户组),以及核实周期(天)。 |
upload_id | 否 | 通过分段上传得到的文档文件,用于代替 content。 |
attachments | 否 | 文档的其他文件,最多 20 个,是 upload_id 的列表。每个文件都会成为该数据源中单独的一张卡片。 |
单个文档可以不传 content,改为在 multipart/form-data 请求中附带一个最大 100 MB 的文件:文件放在 file 部分,上述字段以 JSON 形式放在 document 部分。upload_id 和 attachments 所用的分段上传须使用具有 import:write 权限范围的组织密钥创建,用途为 import_attachment;数据源密钥不能创建这些上传。
两种推送方式
直接更新适合会通知每次变更的系统,例如服务台的 Webhook 或 Zapier 中的一个步骤。文档创建或更改时发送它,被删除时删除它:
| 请求 | 作用 |
|---|---|
POST /v1/push/sources/{source_id}/documents | 创建或替换一个文档。 |
POST /v1/push/sources/{source_id}/documents/batch | 一次创建或替换多个文档。 |
DELETE /v1/push/sources/{source_id}/documents/{external_id} | 删除一个文档。请对 ID 做 URL 编码。 |
变更在大约一分钟内转换为卡片;连续的多次变更会合并处理。
同步适合定期导出:发送全部内容或所有变更的内容,由 Sursa 进行比对。本指南的其余部分介绍同步。
运行全量同步
同步分三步:打开、推送、关闭。
1. 打开同步
POST /v1/push/sources/{source_id}/syncs 打开一次同步并返回其编号:
curl -X POST https://api.sursa.com/v1/push/sources/42/syncs \
-H "Authorization: Bearer $SURSA_SOURCE_KEY" \
-H "Idempotency-Key: sync-2026-10-24" \
-H "Content-Type: application/json" \
-d '{"type": "initial", "max_minutes": 180}'{"sync_number": 7, "type": "initial", "status": "open", "opened_at": "2026-10-24T02:00:00Z",
"expires_at": "2026-10-24T05:00:00Z", "pushed": 0, "removed": 0}
- 数据源的第一次同步,
type为initial;完成过一次同步后为incremental。在完成任何同步之前发起incremental同步,会返回sources.push_initial_required。 - 同一时间只能打开一个同步:再打开一个会返回
409sources.push_sync_open。 - 同步保持打开
max_minutes分钟(默认 60,最多 1440)。之后同步过期:不会归档任何内容,其编号也不再被接受。请选择一个比导出所需时间宽裕得多的时长。
2. 推送文档
按批次发送文档,每批最多 500 个,并附上同步编号。批次的请求体最大 50 MiB;批次中的文件以 upload_id 传入。
curl -X POST https://api.sursa.com/v1/push/sources/42/documents/batch \
-H "Authorization: Bearer $SURSA_SOURCE_KEY" \
-H "Idempotency-Key: sync-7-batch-1" \
-H "Content-Type: application/json" \
-d '{
"sync_number": 7,
"documents": [
{"external_id": "faq-shipping", "title": "Shipping", "content_type": "markdown",
"content": "# Shipping\n\nWe ship worldwide within 5 days.", "path": ["Support"]},
{"external_id": "faq-refunds", "title": "Refunds", "content_type": "html",
"content": "<p>Refunds take 14 days.</p>", "path": ["Support", "Billing"],
"permissions": {"groups": ["Support"]}, "metadata": {"article_id": "1042"}}
]
}'响应按发送顺序为每个文档给出一个结果:
{
"results": [
{"external_id": "faq-shipping", "result": "unchanged", "status": "indexed", "card_id": "8c1d…", "version": "5e0f…"},
{"external_id": "faq-refunds", "result": "updated", "status": "pending", "card_id": "3f2c…", "version": "a91b…"}
]
}
| 字段 | 含义 |
|---|---|
result | 本次对文档做了什么:created、updated、unchanged、deleted、not_found、error 或 skipped(在未应用的原子批次中)。 |
status | 文档转换为卡片的进度:pending、processing(卡片已写入,正在索引)、indexed、failed 或 removed。 |
card_id | 文档对应的卡片(卡片生成后才有)。 |
version | 内容和字段的哈希值。再次发送的同一个文档版本相同,结果为 unchanged。 |
error、reason | 文档失败的原因。reason 是稳定的代码:content_invalid、file_rejected、too_large、limit_reached、index_failed 或 batch_rejected(原子批次中的另一个文档失败)。 |
默认情况下,批次中的每个文档单独应用:一个有问题的文档不会影响其他文档。
自上次同步以来没有变化的文档不必重新发送。用 POST /v1/push/sources/{source_id}/syncs/{sync_number}/touch 按 ID 确认这些文档(每个请求最多 1000 个 ID),这样全量比对时会保留它们:
curl -X POST https://api.sursa.com/v1/push/sources/42/syncs/7/touch \
-H "Authorization: Bearer $SURSA_SOURCE_KEY" \
-H "Content-Type: application/json" \
-d '{"external_ids": ["faq-contact", "faq-hours"]}'3. 关闭同步
POST /v1/push/sources/{source_id}/syncs/{sync_number}/close 结束同步:
curl -X POST https://api.sursa.com/v1/push/sources/42/syncs/7/close \
-H "Authorization: Bearer $SURSA_SOURCE_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "complete"}'status | 效果 |
|---|---|
complete | 全量比对。数据源中本次同步既未推送也未确认的所有文档都会被归档。只有在发送了全部内容时才使用。 |
complete_incremental | 应用已发送的内容,不归档任何文档。 |
failed | 不归档任何文档,保留已推送的内容。可以加上 reason(例如 API_ERROR)和 error_details,写入日志。 |
导出中途停止时,一定要以 failed 关闭:部分导出之后用 complete 关闭,会归档所有还没来得及发送的文档。重复同样的关闭请求会返回同样的结果。
关闭后,Sursa 在大约一分钟内把文档转换为卡片。数据源页面显示每次同步及其推送和归档的文档数量,GET /v1/push/sources/{source_id}/syncs/{sync_number} 返回同样的计数。
增量同步
在两次全量同步之间,只发送变更的内容:
- 以
"type": "incremental"打开同步。 - 推送变更的文档,并把已删除文档的 ID 放进
deletes(见下文)。 - 以
complete_incremental关闭同步。
请定期(例如每周)运行一次全量同步,补上增量同步可能遗漏的内容。
在批次中删除文档
批次可以在写入文档的同时删除文档。在 deletes 中列出要删除的 ID:
{
"sync_number": 8,
"documents": [{"external_id": "faq-shipping", "title": "Shipping", "content": "…"}],
"deletes": ["faq-old-returns", "faq-fax"]
}
每个被删除的 ID 在 results 中都有单独的一项,result 为 deleted;如果数据源中没有该文档,则为 not_found。再次删除同一个 ID 不会造成影响。
原子批次
有些变更只有一起应用才有意义,例如一个文档和替换它的文档。传入 "atomic": true 后,批次要么全部应用,要么完全不应用:只要有一个文档未通过检查,整个批次都不会应用,响应为 422,错误码为 sources.push_batch_rejected,每个文档的结果在 error.details.results 中:失败的文档带有 error,其他文档为 skipped。
{"sync_number": 8, "atomic": true, "documents": […], "deletes": ["faq-old-returns"]}
不带 atomic 时,批次逐个文档应用。相关联的变更请使用原子批次;批量加载请使用普通批次,以免一个有问题的文档拖累其余文档。
检查每个文档的结果
批次的响应说明每个文档是如何写入的。转换为卡片是之后进行的,因此 status 可能仍为 pending。要找出某次同步中未能转换为卡片的文档:
curl "https://api.sursa.com/v1/push/sources/42/syncs/7/documents?status=failed" \
-H "Authorization: Bearer $SURSA_SOURCE_KEY"每一项包含 external_id、title、status、card_id、version 和 reason,列表用 cursor 分页。status 还可以取 pending、processing、indexed 和 removed。
也可以不主动查询,而是等待通知:事件 import.sync_completed(同步已关闭并处理完毕,附带总数)和 import.document_failed(文档未能转换为卡片,附带 reason)会通过 Webhook 投递,也会出现在事件记录中。
重试
- 每个
POST请求都传入Idempotency-Key。使用同一个键重复发送的批次返回第一次的结果。 - 即使不带该键,
external_id相同的文档也绝不会生成第二张卡片:结果为unchanged或updated。 - 如果进程在同步中途停止,请在旧同步过期后打开新同步;如果还记得旧同步的编号,也可以以
failed关闭它。然后重新运行同步。
限制
| 限制项 | 值 |
|---|---|
| 单个文档的内容 | 5 MB |
| 单文档请求中的文件 | 100 MB |
| 每批文档数 | 500 |
| 批次请求体 | 50 MiB |
每个 touch 请求的 ID 数 | 1000 |
| 每个数据源的文档数 | 取决于套餐:100,000 起 |
| 打开的同步 | 默认 60 分钟,最长 24 小时 |
在批次中,超出数据源文档数限制的文档会失败,原因为 limit_reached;单个文档的请求则返回 409,错误码为 sources.push_too_many_documents。速率限制与其他请求一样适用;参见速率限制与配额。
常见错误
| 响应 | 原因 | 处理方法 |
|---|---|---|
401 sources.push_key_invalid | 密钥有误、已撤销或属于其他数据源。 | 检查密钥和路径中的数据源 ID。 |
400 sources.push_document_invalid | 缺少必填字段或超出限制。 | 修正文档;在普通批次中,只有该文档会失败。 |
409 sources.push_sync_open | 另一个同步仍处于打开状态。 | 关闭它,或等待它过期。 |
409 sources.push_sync_closed | 同步已关闭或已过期。 | 打开新的同步;导出耗时较长时调大 max_minutes。 |
403 sources.feature_disabled | 组织的套餐不包含数据源功能。 | 联系管理员。 |
429 | 请求过多。 | 等待 Retry-After;减少批次数量,增大每批的规模。 |