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/webhooks | Webhookの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は、変更されたフォルダーのないコレクションを返しません。
同期ループ
- 初回の実行では、
updated_sinceなしでリスト全体を読み込み、実行を開始した時刻を記録します。 - 以降の実行では、記録した時刻から1分ほどの余裕を引いた値を
updated_sinceとして渡し、新しい開始時刻を記録します。 - 各オブジェクトを
idをキーにして自社のストアに書き込み、以前のコピーを置き換えます。余裕のために2回現れたオブジェクトは、単にもう一度書き込まれるだけです。 - 次のページはすべて、カーソルと同じ
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つのジョブにまとめています。