本文へスキップ
Sursa Developers

ドキュメントをインポートする

このガイドでは、カードのAPIを使ってドキュメントをSursaに取り込む方法を説明します。スクリプトから数件のドキュメントを取り込むことも、定期的なジョブでシステムのすべてのドキュメントを取り込むこともできます。各ドキュメントは、コレクションの中のカードになります。ユーザーは検索でそれを見つけ、アシスタントはそれをもとに回答します。他のカードと同じく、担当者、タグ、検証、権限を持ちます。

自社のシステムで削除されたドキュメントをSursaに判別させたい場合や、連携プラットフォームからドキュメントを送信する場合は、代わりにカスタムソースを使ってください。カスタムソースは完全同期と増分同期のために作られています。このガイドでは、コレクションへの直接書き込みを扱います。

cards:writeスコープと、対象のコレクションに書き込めるロール(editorまたはadmin)を持つAPIキーが必要です。例ではクイックスタートのヘルパーを使います。

適切なエンドポイントを選ぶ

手元にあるもの使うもの
自社のシステムで安定したIDを持つレコードPUT /v1/cards-by-external-id/{external_id}:自社のIDで作成または置換します。
自社のIDがなく、一度だけ追加するテキストPOST /v1/cards/text
一度だけ追加するファイルPOST /v1/cards
多数のファイルをまとめたZIPのアーカイブファイルPOST /v1/cards/archive
1回のリクエストの上限より大きいファイルパートに分けたアップロードの後、上のいずれかのエンドポイント。
既存のカードの新しい内容PUT /v1/cards/{card_id}/content

二度以上インポートするものには、external_idを使ってください。どのインポートも繰り返せるようになり、もう一度実行すると、コピーを追加するのではなく、作成済みのカードが更新されます。

自社のIDでカードを作成または置換する

PUT /v1/cards-by-external-id/{external_id}は、自社のシステムでのレコードの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。
htmlHTML。見出し、段落、リスト、表、http・https・mailtoのリンクだけのシンプルなマークアップに整理されます。スクリプト、スタイル、フレーム、フォーム、画像は、その内容ごと削除されます。
textプレーンテキスト。段落は空行で区切ります。

カードを待つかどうか

デフォルトでは、リクエストはカードが検索できる状態になってから、"status": "ready"とcardのカードとともに応答します。短いテキストなら1秒もかかりませんが、長いファイルでは1分以上かかることがあります。

大量のインポートでは、"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で拒否します。

自社のIDでファイルのカードを作成または置換するには、同じ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のない新しいカードには、ファイル名がタイトルとして付きます。

自社のIDなしでファイルを一度だけ追加するには、同じフォームとIdempotency-Keyを付けてPOST /v1/cardsを使います。コレクションにすでにあるファイルをアップロードすると、external_idを渡さない限り、"status": "skipped"と"duplicate": true、既存のcard_idが返ります。

1つのファイルのサイズはインストール環境で制限されています(デフォルトは200MB)。それより大きいファイルや不安定なネットワークでは、先にファイルをパートに分けてアップロードしてください。

カードの内容を置き換える

カードのSursaのIDがすでにわかっている場合、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"が返ります。
  • インデックス作成中のカードでは、コードcards.indexing_in_progressとともに409が返ります。数秒後に再試行してください。
  • コネクターやカスタムソースなど、ソースから来たカードは、そのソースでしか変更できません:cards.managed_by_source。
  • キーでカード全体を参照できる必要があります。一部だけ参照できるカードでは、cards.content_whole_accessが返ります。

多数のドキュメントをインポートする

PUT /v1/cards-by-external-id/…でレコードをループするのが最も簡単な一括インポートで、どの時点で止めて再実行しても安全です。速く、レート制限にも優しくするには:

  1. "async": trueを送信し、各リクエストがカードのキュー登録後すぐに応答するようにします。
  2. いくつかのリクエスト(4〜8)を並行して実行し、RateLimitヘッダーで残りのリクエストが少なくなったら速度を落とします。レート制限とクォータを参照してください。
  3. 429ではRetry-Afterの時間だけ待ち、ネットワークエラーや5xxでは同じリクエストを再試行します。繰り返しても害はありません。
  4. エラーで終わったレコードの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が、中にあるサポート対象の各ファイルを1回のリクエストでカードにします。すぐに計画(どのファイルがカードになり、どれがなぜスキップされるか)とジョブを返します。結果はGET /v1/archives/{job_id}でわかります。

インポートしたカードを探して削除する

よくあるエラー

コード原因対処
collections.not_foundこのIDまたは名前のコレクションが、キーで参照できない。GET /v1/collectionsでcollection_idを確認してください。
collections.no_write_accessキーがこのコレクションに書き込めない。editorロールとコレクションへのアクセス権を持つキーを使ってください。
cards.external_id_taken別のカードがすでに持っているexternal_idでPOSTした。置き換えるにはPUT /v1/cards-by-external-id/…を使ってください。
cards.managed_by_sourceカードがソースに属している。ソースでドキュメントを変更してください。
files.type_mismatchファイルの名前、種類、内容が一致しない。本来の拡張子でファイルを送信してください。
files.too_largeファイルがアップロードの上限または組織のストレージを超えている。パートに分けたアップロードを使うか、ストレージを空けてください。
limits.cards組織がプランのカードの上限に達した。不要になったカードを削除するか、プランを変更してください。
api.metadata_invalidmetadataが上限を超えている。メタデータを参照してください。