Calendar API

差分同期

フル同期を実行し、nextSyncToken を使ってイベントの変更差分や削除を取得します。

Calendarpit は、Google Calendar の events.list におけるフル同期と差分同期のフローを実装しています。流れは次の 2 段階です。

  1. 最初にフル同期を実行して nextSyncToken を取得します。
  2. 以降のリクエストで、そのトークンを syncToken として渡します。その時点以降に作成・更新・削除されたイベントだけを取得できます。

プロトコルの詳細は Google の同期ガイドおよび events.list リファレンスを参照してください。

フル同期

events.list によるフル同期(events.instances は対象外)では、showDeleted=true の有無にかかわらず同期トークンが発行されます。

ページ送り

  • 最終ページまで nextPageToken をたどってください。
  • 各 pageToken のリクエストには、初回と同じクエリパラメータを渡します。パラメータを変更すると HTTP 410(invalidPageToken)が返ります。
  • Calendarpit は最終ページでのみ nextSyncToken を返し、nextPageToken と同時に返すことはありません。全ページを処理した後に nextSyncToken を保存してください。
  • 各ページのレコードと同期用のスナップショットは、最初のページを取得した時点でまとめて固定されます。ページ送りの最中に発生したイベントの書き込みは、次回の差分同期で返されます。

終了条件のない繰り返しの系列

singleEvents=true を指定する場合は、フル同期で必ず timeMax を設定してください。

終了条件のない繰り返しの系列とは、COUNT も UNTIL もない RRULE のことです。このような系列がカレンダーにあると、次のリクエストは失敗します。

  • singleEvents=true: timeMax なしで発行されたトークンを使うと、HTTP 400(unsupportedFeature)が返ります。timeMax を指定してフル同期をやり直してください。
  • singleEvents=false: timeMin のみを指定し timeMax を省略したフル同期の events.list も拒否されます(イベント、繰り返し、時刻の扱いを参照)。singleEvents=false での差分同期はこの影響を受けません。

終了条件のない繰り返しの系列は、削除後も対象になります。リセットや Fixture の読み込みを行うまで、削除済みレコード(status: cancelled)としてカレンダーに残るためです。

// api is the Google SDK client configured with Calendarpit's rootUrl.
const query = {
  calendarId: 'primary',
  singleEvents: true,
  showDeleted: true,
  timeMin: '2026-10-01T00:00:00Z',
  timeMax: '2026-11-01T00:00:00Z',
};
let pageToken: string | undefined;
let syncToken: string | undefined;
do {
  const { data } = await api.events.list({ ...query, pageToken });
  for (const event of data.items ?? []) {
    if (event.status === 'cancelled') await removeLocalEvent(event.id!);
    else await upsertLocalEvent(event);
  }
  pageToken = data.nextPageToken ?? undefined;
  if (!pageToken) syncToken = data.nextSyncToken ?? undefined;
} while (pageToken);

差分同期

watch 通知を受け取った後や定期的なポーリング時に、syncToken を指定して events.list を呼び出します。差分同期の結果も maxResults と pageToken でページ送りできます。

const previousToken = syncToken;
do {
  const { data } = await api.events.list({
    calendarId: 'primary',
    singleEvents: true,
    showDeleted: true,
    syncToken: previousToken,
    pageToken,
  });
  for (const event of data.items ?? []) {
    if (event.status === 'cancelled') await removeLocalEvent(event.id!);
    else await upsertLocalEvent(event);
  }
  pageToken = data.nextPageToken ?? undefined;
  if (!pageToken) syncToken = data.nextSyncToken ?? undefined;
} while (pageToken);

クエリのルール

以下のルールに違反するリクエストは HTTP 400(invalidParameter)で失敗します。

  • 併用できないパラメータ: syncToken は timeMin、timeMax、orderBy、updatedMin、privateExtendedProperty、sharedExtendedProperty、iCalUID と同時に指定できません。また、events.instances は syncToken に対応していません。
  • 削除されたイベント: showDeleted=false は指定しないでください。差分同期には常に削除が含まれます。showDeleted=true を指定するか、showDeleted を省略してください。
  • 展開オプションの一致: singleEvents と適用される timeZone は、初回のフル同期と一致している必要があります。適用される timeZone は、timeZone パラメータの値です。省略時はカレンダーのタイムゾーンです。
  • タイムゾーンの変更: timeZone パラメータを明示せずに発行されたトークンは、管理画面や管理 API でカレンダーのタイムゾーンを変更すると無効になります(HTTP 400)。

差分の内容と挙動

  • リトライ: 同期トークンは一度使っても消費されません。リクエストが失敗した場合は、同じ syncToken でリトライできます。

  • 毎回新しいトークン: 差分同期を実行するたびに、最終ページで新しい nextSyncToken が返されます。

  • 変更がない場合: items が [] になります。この場合も新しい nextSyncToken が返されます。

  • 削除済みレコード: 削除されたイベントは削除済みレコードとして保持され、status: "cancelled" で返されます。

  • フィルタの非適用: 差分には、初回リクエストで指定した updatedMin、拡張プロパティ、iCalUID のフィルタは適用されません。カレンダー内で変更されたすべてのイベントが含まれます。

  • 繰り返しの系列の対象期間: 差分の内容は singleEvents によって変わります。

    • singleEvents=false の場合、差分には変更された繰り返しの系列の親イベント、保存された例外(個別に変更された回)、単発のイベントが含まれます。
    • singleEvents=true の場合、繰り返しの系列は初回のフル同期で指定した timeMin と timeMax の期間内で再展開されます。親イベントを編集すると、その期間内のすべてのインスタンスが返されます。系列の期間短縮やキャンセルで除外されたインスタンスは、status: "cancelled" として返されます。
    • 繰り返しではない単発のイベントは、初回の timeMin と timeMax の期間外でも、変更があれば返されます。

    繰り返しの展開期間を移動する場合は、新しくフル同期を実行してください。

ライフサイクルと制限

同期トークンは、ユーザー、カレンダー、展開オプション、初回の対象期間に紐づく不透明な UUID です。

トークン有効期間と上限永続化無効時のエラー
syncTokenTTL 7 日間、ワークスペースあたり最大 100 件(古いものから削除)SQLite に保存(:memory: モードでは再起動で消失)HTTP 410(fullSyncRequired)
pageTokenTTL 1 時間、ワークスペースあたり最大 1,000 ページ(古いものから削除)インメモリのみ(再起動で消失)HTTP 410(invalidPageToken)
  • HTTP 410(fullSyncRequired): syncToken が次のいずれかに当てはまる場合に返されます。

    • 不明、または期限切れ
    • 上限の 100 件を超えて削除された
    • リセットや Fixture の読み込みで消去された
    • 別のユーザーやカレンダー向けに発行された

    このエラーが返された場合は、そのカレンダーのローカル同期データを破棄してフル同期をやり直してください。

  • トークンの上限: pageToken を指定しないフル同期・差分同期の events.list リクエストごとに、新しいトークンが発行されます。上限の 100 件は、すべてのユーザーとカレンダーで共有されます。管理 API でのイベント一覧取得も含みます。

  • リセットと Fixture: POST /__calendarpit/reset および POST /__calendarpit/fixtures を実行すると、すべての同期トークンとページトークンが消去されます(テスト制御 APIを参照)。同時にすべての OAuth 認可も取り消されるため、クライアントは先に再認可を行う必要があります。

  • ページカーソル: イベントの pageToken カーソルは、クエリパラメータと呼び出し元のユーザーに紐づいています。再起動、リセット、Fixture の読み込みを行うと失われます。HTTP 410(invalidPageToken)が返された場合は、保持している syncToken からページ送りをやり直すか、フル同期を最初からやり直してください。

目次