Импорт документов
Это руководство показывает, как поместить ваши документы в Sursa через API карточек: несколько документов из скрипта или все документы системы через регулярное задание. Каждый документ становится карточкой в коллекции. Люди находят её в поиске, ассистент отвечает по ней, и у неё, как у любой карточки, есть владелец, теги, верификация и права доступа.
Если вы хотите, чтобы Sursa сама определяла, какие документы удалены в вашей системе, или отправляете документы с платформы интеграций, используйте вместо этого свой источник. Он рассчитан на полные и инкрементальные синхронизации. Это руководство описывает прямую запись в коллекцию.
Нужен ключ API с областью cards:write и роль, которой разрешено писать в нужную коллекцию (editor или admin).
Примеры используют вспомогательную функцию из руководства Быстрый старт.
Какую операцию выбрать
| Что у вас есть | Что использовать |
|---|---|
| Запись с постоянным идентификатором в вашей системе | PUT /v1/cards-by-external-id/{external_id}: создать или заменить по вашему идентификатору. |
| Текст, который нужно добавить один раз, без своего идентификатора | POST /v1/cards/text |
| Файл, который нужно добавить один раз | POST /v1/cards |
| ZIP-архив со множеством файлов | POST /v1/cards/archive |
| Файл больше лимита одного запроса | Загрузка по частям, затем любая из операций выше. |
| Новое содержимое для уже существующей карточки | PUT /v1/cards/{card_id}/content |
Для всего, что импортируется больше одного раза, используйте external_id. Тогда любой импорт можно повторить:
повторный запуск обновит созданные им карточки, а не добавит копии.
Создание или замена карточки по вашему идентификатору
PUT /v1/cards-by-external-id/{external_id} принимает идентификатор записи в вашей системе. Если ни у одной
карточки нет такого external_id, он создаёт карточку (201). Если есть, он заменяет содержимое и сведения этой
карточки (200).
curl -X PUT https://api.sursa.com/v1/cards-by-external-id/kb-article-1042 \
-H "Authorization: Bearer $SURSA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"collection_id": 3,
"folder_id": "0f8e2d7c-4b6a-4e1f-9c3d-2a5b8e7f1c40",
"title": "Returning a damaged item",
"content_type": "html",
"body": "<h2>Damaged items</h2><p>Send a photo within 7 days and we ship a replacement.</p>",
"tags": ["returns", "shipping"],
"metadata": {"source": "helpdesk", "article_id": "1042"}
}'Что происходит при повторе:
- Содержимое не изменилось: новая версия не создаётся. Ответ —
200с"status": "skipped"и"reason": "unchanged". Повторять запрос всегда безопасно, даже безIdempotency-Key. - Содержимое изменилось: карточка получает новую версию и индексируется заново. Её заголовок, теги,
владелец, категория и
metadataпринимают переданные значения;metadataзаменяется целиком. - Коллекция и папка учитываются только при создании карточки. Чтобы позже перенести карточку, измените её через
PATCH /v1/cards/{card_id}.
Новая версия снимает с карточки верификацию, как и любое изменение содержимого: верификатор подтверждает её заново.
Форматы текста
content_type | Тело запроса |
|---|---|
markdown (по умолчанию) | Markdown. |
html | HTML. Он очищается до простой разметки: заголовков, абзацев, списков, таблиц и ссылок http, https и mailto. Скрипты, стили, фреймы, формы и изображения удаляются вместе с содержимым. |
text | Обычный текст. Абзацы разделяются пустыми строками. |
Ждать карточку или нет
По умолчанию запрос возвращается, когда карточку уже можно найти, с "status": "ready" и карточкой в card. Это
занимает от доли секунды для короткого текста до минуты и дольше для длинного файла.
Для массового импорта передавайте "async": true. Ответ приходит сразу с "status": "queued" и заданием индексации
в job (с его job_id). Проверяйте задание через GET /v1/jobs/{job_id} или следите за
всеми заданиями в GET /v1/jobs/stream. Задание завершается со status done,
error или canceled.
curl https://api.sursa.com/v1/jobs/48213 \
-H "Authorization: Bearer $SURSA_API_KEY"Загрузка файла
Файлы передаются в запросе multipart/form-data, в поле file. Sursa читает PDF, Word, Excel, PowerPoint, таблицы
OpenDocument, CSV, Markdown, HTML, AsciiDoc, электронные письма (.eml), обычный текст и распространённые форматы
изображений. Она проверяет, что имя файла, заявленный тип и содержимое согласуются, и отвергает несоответствие с
415.
Чтобы создать или заменить карточку с файлом по вашему идентификатору, отправьте файл в тот же
PUT /v1/cards-by-external-id/{external_id}:
curl -X PUT https://api.sursa.com/v1/cards-by-external-id/handbook-2026 \
-H "Authorization: Bearer $SURSA_API_KEY" \
-F "file=@employee-handbook.pdf" \
-F "collection_id=3" \
-F "tags=hr,policies" \
-F 'metadata={"owner_team":"people"}'В форме tags — строка со значениями через запятую, а metadata — JSON-объект, записанный строкой. Новая карточка
без title получает в качестве заголовка имя файла.
Чтобы добавить файл один раз, без своего идентификатора, используйте POST /v1/cards
с той же формой и Idempotency-Key. Загрузка файла, который в коллекции уже есть, возвращает "status": "skipped" с
"duplicate": true и card_id существующей карточки, если вы не передали external_id.
Размер одного файла ограничен настройками установки (по умолчанию 200 МБ). Файлы больше этого или при ненадёжной сети сначала загружайте по частям.
Замена содержимого карточки
Если идентификатор карточки в Sursa у вас уже есть, PUT /v1/cards/{card_id}/content
заменяет её текст или файл и сохраняет всё остальное: владельца, теги, верификаторов, интервал верификации и
категорию, заданную вручную.
curl -X PUT https://api.sursa.com/v1/cards/3f2c9a7e-5b1d-4c8e-9f0a-1b2c3d4e5f60/content \
-H "Authorization: Bearer $SURSA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content_type": "markdown", "body": "# Refund policy\n\nRefunds are processed within 10 days."}'- То же содержимое ещё раз возвращает
"status": "skipped"с"reason": "unchanged". - Карточка, которая прямо сейчас индексируется, возвращает
409с кодомcards.indexing_in_progress. Повторите через несколько секунд. - Карточка, пришедшая из источника, например из коннектора или своего источника, меняется только в своём источнике:
cards.managed_by_source. - Ключу должна быть видна вся карточка; карточка, видимая частично, возвращает
cards.content_whole_access.
Импорт множества документов
Цикл по вашим записям с PUT /v1/cards-by-external-id/… — самый простой массовый импорт, и его безопасно
остановить и запустить снова в любой момент. Чтобы он был быстрым и бережным к лимиту частоты:
- Передавайте
"async": true, чтобы каждый запрос возвращался, как только карточка встала в очередь. - Выполняйте несколько запросов параллельно (от 4 до 8) и замедляйтесь, когда заголовок
RateLimitпоказывает, что запросов осталось мало. См. Лимиты частоты и квоты. - При
429ждитеRetry-After; при сетевой ошибке или5xxповторяйте тот же запрос. Повторы ничему не вредят. - Записывайте
external_idкаждой записи, которая завершилась ошибкой, и повторите их в конце.
import { api } from "./sursa.mjs";
async function importAll(records, concurrency = 6) {
const failed = [];
const queue = [...records];
async function worker() {
for (let record = queue.shift(); record; record = queue.shift()) {
try {
await api("PUT", `/v1/cards-by-external-id/${encodeURIComponent(record.id)}`, {
collection_id: 3,
title: record.title,
content_type: "html",
body: record.html,
metadata: { source: "helpdesk" },
async: true,
});
} catch (error) {
failed.push({ id: record.id, error: String(error) });
}
}
}
await Promise.all(Array.from({ length: concurrency }, worker));
return failed;
}Для ZIP-архива с файлами POST /v1/cards/archive одним запросом превращает
каждый поддерживаемый файл внутри в карточку. Он сразу отвечает планом (какие файлы станут карточками, какие будут
пропущены и почему) и заданием; результат сообщает GET /v1/archives/{job_id}.
Поиск и удаление импортированных карточек
- Читайте карточку по вашему идентификатору через
GET /v1/cards-by-external-id/{external_id}. - Получайте карточки одного импорта фильтром по метаданным:
GET /v1/cards?metadata[source]=helpdesk. См. Метаданные. - Удаляйте карточку через
DELETE /v1/cards/{card_id}.
Частые ошибки
| Код | Причина | Что делать |
|---|---|---|
collections.not_found | Ключу не видна коллекция с таким идентификатором или именем. | Проверьте collection_id через GET /v1/collections. |
collections.no_write_access | Ключу нельзя писать в эту коллекцию. | Используйте ключ с ролью editor и доступом к коллекции. |
cards.external_id_taken | POST с external_id, который уже есть у другой карточки. | Используйте PUT /v1/cards-by-external-id/…, чтобы заменить её. |
cards.managed_by_source | Карточка принадлежит источнику. | Измените документ в его источнике. |
files.type_mismatch | Имя, тип и содержимое файла не согласуются. | Отправьте файл с его настоящим расширением. |
files.too_large | Файл больше лимита загрузки или свободного места в хранилище организации. | Используйте загрузку по частям или освободите место. |
limits.cards | Организация достигла лимита карточек своего тарифа. | Удалите ненужные карточки или смените тариф. |
api.metadata_invalid | metadata нарушает ограничение. | См. Метаданные. |