本文へスキップ
Sursa Developers

updated_sinceによる増分同期

Sursaのデータのコピーを自社のシステムに保つために、毎回すべてを読み込む必要はありません。updated_atを持つオブジェクトのリストはupdated_sinceを受け付け、その時点以降に変更されたオブジェクトだけを返します。

curl -G https://api.sursa.com/v1/cards \
  -H "Authorization: Bearer $SURSA_API_KEY" \
  --data-urlencode "updated_since=2026-10-23T00:00:00Z" \
  --data-urlencode "limit=200"

updated_sinceを指定すると、リストはupdated_atの昇順、次にidの順に並び、カーソルもその順序で続きます。そのため変更は発生した順に読み込まれ、ページの境界で変更が抜け落ちることはありません。

updated_sinceは次のリストで使えます。

リスト変更とみなされるもの
GET /v1/cards、GET /v1/cards/countカードのupdated_at。
GET /v1/groupsグループのupdated_at:タイトルまたは設定の変更。
GET /v1/webhooksWebhookのupdated_at:設定とシークレットの変更。配信は含みません。
GET /v1/jobsジョブのupdated_at:ステータスのすべての変更。
GET /v1/drafts下書きのupdated_at。
GET /v1/cards/{card_id}/threads最初のコメントまたはいずれかの返信の、最新の作成または編集。スレッドは全体が返されます。
GET /v1/folders、GET /v1/collections/{name}/foldersフォルダーのupdated_at。一致したフォルダーはフラットなリストで返されます(後述)。

コレクション、ユーザー、お知らせにはupdated_atがないため、それらのリストはこの条件で絞り込めません。いずれも短いリストなので、実行のたびに全体を読み込んでください。時刻にはZや+02:00のようなタイムゾーンのオフセットが必要です。それ以外の値は、GET /v1/cardsと同じく拒否されます。

updated_sinceを指定すると、フォルダーのツリーはツリーの形ではなくなります。各コレクションのfoldersには、変更されたフォルダーが空のchildrenとともにフラットなリストで入り、それぞれの位置はparent_idでわかります。このときGET /v1/foldersは、変更されたフォルダーのないコレクションを返しません。

同期ループ

  1. 初回の実行では、updated_sinceなしでリスト全体を読み込み、実行を開始した時刻を記録します。
  2. 以降の実行では、記録した時刻から1分ほどの余裕を引いた値をupdated_sinceとして渡し、新しい開始時刻を記録します。
  3. 各オブジェクトをidをキーにして自社のストアに書き込み、以前のコピーを置き換えます。余裕のために2回現れたオブジェクトは、単にもう一度書き込まれるだけです。
  4. 次のページはすべて、カーソルと同じupdated_sinceを付けて要求してください。カーソルは最初のページと同じ絞り込み条件でしか使えません。条件がないと、コードapi.cursor_invalidとともに400が返ります。

終了時刻ではなく実行の開始時刻を使うことで、実行中に行われた変更も次の実行で確実に取り込めます。

GET /v1/cardsはJSON配列を返すので、次のページはX-Next-Cursorヘッダーで返されます(配列を返すリストを参照)。

const BASE = "https://api.sursa.com";
const headers = { Authorization: `Bearer ${process.env.SURSA_API_KEY}` };

async function syncCards(since, saveCard) {
  const startedAt = new Date();
  const params = new URLSearchParams({ limit: "200" });
  if (since) params.set("updated_since", new Date(since.getTime() - 60_000).toISOString());
  let query = params.toString();
  while (query) {
    const response = await fetch(`${BASE}/v1/cards?${query}`, { headers });
    if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
    for (const card of await response.json()) await saveCard(card); // write into your store by card.id
    const cursor = response.headers.get("X-Next-Cursor");
    if (cursor) params.set("cursor", cursor); // keep updated_since: the cursor needs the same filters
    query = cursor ? params.toString() : "";
  }
  return startedAt; // pass it as `since` next time
}

削除

リストは存在するオブジェクトだけを返すので、何かが削除されたことはリストからはわかりません。削除はイベントフィードから取り込みます。

curl -G https://api.sursa.com/v1/webhook-events \
  -H "Authorization: Bearer $SURSA_API_KEY" \
  --data-urlencode "type=card.deleted" \
  --data-urlencode "created_since=2026-10-23T00:00:00Z"

フィードを読み取るには、adminロールとwebhooks:manageスコープを持つキーが必要です。イベントは30日間保持されます。同期を30日より長く実行していない場合は、リスト全体をもう一度読み込み、リストにないオブジェクトをすべてストアから削除してください。

データウェアハウスにエクスポートするでは、リスト、updated_since、イベントフィードを1つのジョブにまとめています。