Skip to content

Instantly share code, notes, and snippets.

@tyler-dane
Last active January 24, 2026 02:49
Show Gist options
  • Select an option

  • Save tyler-dane/8344b1c1de05395272a7fed9ac4815aa to your computer and use it in GitHub Desktop.

Select an option

Save tyler-dane/8344b1c1de05395272a7fed9ac4815aa to your computer and use it in GitHub Desktop.
TDD: Google Calendar Sync API (Condensed)

TDD: Google Calendar Sync API (Condensed)

Document Status: Draft
Full TDD: https://gist.github.com/tyler-dane/5d18ecdab29edeabf526ee55cd584b32
Context: For an article on https://newsletter.fullstack.zip


Solution Overview

The Calendar API implements synchronization using cursor pagination (nextPageToken) with incremental sync tokens (nextSyncToken). Clients perform an initial full sync, then efficient incremental sync calls returning only changes since the last sync.

Core Concepts

Sync Tokens

  • Full sync (no syncToken): Returns all resources, paginated with nextPageToken, final page includes nextSyncToken
  • Incremental sync (with syncToken): Returns only changed entries (including deletions) since token was issued
  • Token expiration: Server returns HTTP 410 GONE → client must clear state and full sync again
  • Tokens are opaque strings—never parse or assume structure

Pagination Rules

  • nextPageToken present → more pages exist, nextSyncToken omitted
  • nextSyncToken only appears on the final page (when nextPageToken is absent)
  • Client must exhaust all pages before storing nextSyncToken

Client Implementation

Sync Flow

function syncSettings() {
  token = loadSyncToken("settings")
  pageToken = null
  
  do {
    params = {}
    if (token) params["syncToken"] = token
    if (pageToken) params["pageToken"] = pageToken

    response = GET /users/me/settings with params

    if (response.status == 410) {
      clearSyncToken("settings")
      clearLocalSettings()
      return syncSettings()  // restart full sync
    }

    for (setting of response.items) upsertLocalSetting(setting)

    pageToken = response.nextPageToken
    if (!pageToken) token = response.nextSyncToken
  } while (pageToken)

  if (token) saveSyncToken("settings", token)
}

Token State Machine

[Start] → NoToken → FullSync → HasToken ⟷ IncrementalSync
                                   ↓
                         TokenExpired/Invalidated → FullSync (HTTP 410)

Push Notifications

Register via POST /calendars/{calendarId}/events/watch with callback URL. Notifications contain resourceId only—client must call API with syncToken to fetch actual changes.

Batch Requests

Supports up to 50 requests per batch using multipart/mixed:

POST /batch/calendar/v3
Content-Type: multipart/mixed; boundary=batch_boundary

Conflict Resolution

Use If-Match header with ETag for optimistic locking:

  • Match → Update succeeds
  • No match → 412 Precondition Failed → Fetch latest, merge, retry
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment