Document Type: Product Requirements Document (PRD)
TDD: https://gist.github.com/tyler-dane/5d18ecdab29edeabf526ee55cd584b32
Document Status: Draft
Author: fullstack.zip
Last Updated: January 2026
Reviewers: AI
Context: For an article on https://newsletter.fullstack.zip
The Calendar Sync API enables third-party calendar applications to synchronize event data with Google Calendar through a robust, real-time synchronization protocol. This system provides push-based change notifications, incremental synchronization capabilities, and conflict resolution mechanisms that allow API clients to maintain accurate, up-to-date copies of user calendar data without resorting to expensive full-calendar polling.
This capability is critical to Google Calendar's ecosystem strategy: by providing best-in-class sync primitives, we enable a thriving third-party application ecosystem that increases user engagement with Google Calendar as their authoritative calendar source while respecting user privacy and maintaining system reliability at scale.
API clients must receive real-time change notifications without resorting to polling. The system implements a webhook-based push notification architecture.
| Requirement | Specification |
|---|---|
| Notification Delivery | Webhook POST to client-registered HTTPS endpoint |
| Notification Latency | Target: < 5 seconds from change commit to webhook delivery |
| Payload Format | Lightweight notification (change signal only, not full event data) |
| Channel Expiration | Configurable TTL up to 30 days; clients must renew before expiration |
| Delivery Guarantees | At-least-once delivery; clients must handle duplicate notifications |
| Verification | Domain verification required; signature validation on all payloads |
Synchronization must be efficient, transferring only changed data since the last sync operation.
| Requirement | Specification |
|---|---|
| Sync Token | Opaque, server-issued token representing sync state |
| Token Validity | Minimum 7 days; may be invalidated earlier under exceptional circumstances |
| Delta Response | Only events modified since token issuance |
| Deleted Events | Returned with status: "cancelled" in delta response |
| Token Invalidation | HTTP 410 Gone triggers full sync requirement |
| Page Token | Separate from sync token; used for paginating large result sets |
Incremental Sync Request:
GET /calendar/v3/calendars/primary/events?syncToken=CPDAlvWDx4oCEPDAlvWDx4oCGAEggL3tThe API must function consistently across all client platforms.
| Platform | Transport | Authentication | Push Support |
|---|---|---|---|
| Web | HTTPS REST | OAuth 2.0 | Webhook to server |
| iOS | HTTPS REST | OAuth 2.0 | Webhook → APNs relay |
| Android | HTTPS REST | OAuth 2.0 | Webhook → FCM relay |
| Desktop | HTTPS REST | OAuth 2.0 | Webhook to local server or long-poll fallback |
Platform-Specific Considerations:
- Mobile clients should implement a server-side webhook receiver that relays to FCM/APNs, as direct webhook delivery to mobile devices is not reliable
- Desktop clients may implement a local HTTP server for webhook receipt or use Server-Sent Events (SSE) as a fallback (future consideration)
- All platforms must implement exponential backoff for retry logic and respect
Retry-Afterheaders
The API must support all calendar types within a user's account.
| Calendar Type | Identifier Pattern | Sync Support | Write Support |
|---|---|---|---|
| Primary | primary or user email |
Full | Full |
| Sub-calendars | Calendar ID (hash) | Full | Full |
| Shared calendars | Calendar ID (hash) | Full | Per ACL |
| Public calendars | Calendar ID (hash) | Read-only | None |
| Resource calendars | resource.calendar.google.com |
Full | Per ACL |
| Holiday calendars | #[email protected] |
Read-only | None |
Calendar List Endpoint:
GET /calendar/v3/users/me/calendarListReturns all calendars the user has added to their list, with access roles and sync capabilities indicated per calendar.
The system operates under an eventual consistency model with defined propagation bounds.
| Metric | Target | Maximum |
|---|---|---|
| Read-after-write (same region) | < 1 second | 3 seconds |
| Cross-region propagation | < 3 seconds | 10 seconds |
| Push notification delivery | < 5 seconds | 30 seconds |
| Sync token freshness | Real-time | 5 seconds staleness |
Consistency Guarantees:
- Changes are durably committed before API response returns
- Sync tokens are monotonically advancing within a calendar
- Event sequence numbers (
sequencefield) provide per-event ordering updatedtimestamps use server time, not client time
The system must be highly available with graceful degradation under load.
| Requirement | Specification |
|---|---|
| Availability Target | 99.95% monthly uptime |
| Degradation Strategy | Shed push notifications before sync; shed writes before reads |
| Circuit Breaker | Per-client rate limiting with automatic recovery |
| Regional Failover | Automatic failover to secondary region within 60 seconds |
| Data Durability | 99.999999999% (11 nines) for committed event data |
Scenario: A user modifies an event in Google Calendar (web or mobile), and a third-party calendar app needs to reflect this change.
Implementation Considerations:
- Clients should debounce rapid webhook notifications (coalesce within 500ms window)
- The webhook only signals that changes exist; client must fetch actual changes
- Clients must handle the case where multiple changes occurred since last sync
- If sync token is invalid (410), client must perform full sync and rebuild local state
Error Handling:
| Error | Response | Client Action |
|---|---|---|
| 410 Gone | Sync token expired | Clear local syncToken, perform full sync |
| 429 Too Many Requests | Rate limited | Exponential backoff per Retry-After header |
| 503 Service Unavailable | Temporary outage | Retry with exponential backoff |
| 401 Unauthorized | Token expired | Refresh OAuth token, retry |
Scenario: A user revokes OAuth access for a calendar app via Google Account settings. The app must detect this and clean up appropriately.
Detection Mechanisms:
| Mechanism | Trigger | Response Time |
|---|---|---|
| API Call Failure | 401 with invalid_grant |
Immediate |
| Push Channel Termination | Channel stopped server-side | < 1 minute |
| Token Introspection | Explicit check via tokeninfo endpoint | On-demand |
Required Client Behavior:
- Detect revocation via 401 response with error
invalid_grantorinvalid_token - Clear stored credentials including access token, refresh token, and sync tokens
- Purge cached calendar data per data retention policy (user expectation)
- Stop all sync operations and pending webhook listeners
- Present re-authentication flow if user returns to the app
Revocation Notification Payload:
{
"kind": "calendar#notification",
"resourceId": "calendar-resource-id",
"channelId": "client-channel-uuid",
"resourceState": "sync_disabled",
"reason": "access_revoked"
}Scenario: A user connects their Google account to a new calendar app for the first time. The app needs to import all existing events.
Pagination Parameters:
| Parameter | Default | Maximum | Description |
|---|---|---|---|
maxResults |
250 | 2500 | Events per page |
pageToken |
— | — | Continuation token |
timeMin |
— | — | Lower bound for event start |
timeMax |
— | — | Upper bound for event start |
singleEvents |
false | — | Expand recurring events |
Best Practices:
- Use
showDeleted=falsefor initial sync (no need for tombstones) - Set reasonable
timeMin(e.g., 1 year ago) to limit historical data - Use
singleEvents=trueif app cannot handle recurrence rules - Process pages in order; final page contains authoritative
nextSyncToken - Implement progress indication for user (may take 30+ seconds for large calendars)
Time Boundaries Recommendation:
timeMin: NOW - 6 months
timeMax: NOW + 2 years
Scenario: A user modifies an event in both Google Calendar and a third-party app while offline. When connectivity is restored, the changes conflict.
Conflict Detection:
Conflicts are detected using the sequence number and updated timestamp on each event. The API uses optimistic concurrency control.
| Field | Purpose |
|---|---|
sequence |
Increments on each organizer modification |
etag |
Opaque version identifier for conditional requests |
updated |
Server timestamp of last modification |
Recommended Implementation:
PATCH /calendar/v3/calendars/primary/events/{eventId}
If-Match: "etag-value-from-last-fetch"
Content-Type: application/json
{
"summary": "Updated Meeting Title",
"sequence": 3
}Response Codes:
| Code | Meaning | Client Action |
|---|---|---|
| 200 | Update succeeded | Store new etag, continue |
| 412 | Precondition failed (conflict) | Fetch latest, resolve, retry |
| 409 | Conflict (sequence number) | Increment sequence, retry |
Merge Strategy Guidance:
For field-level merging, clients should consider:
| Field | Merge Strategy |
|---|---|
summary |
Last-writer-wins or user choice |
start/end |
Last-writer-wins (time changes are significant) |
description |
Concatenate with conflict markers or user choice |
attendees |
Union of both lists |
reminders |
Local preference (user-specific) |
Scenario: A user imports events from another calendar system (e.g., Outlook, iCal file), requiring efficient bulk creation.
Batch Limits:
| Limit | Value | Notes |
|---|---|---|
| Requests per batch | 50 | Hard limit |
| Batch request size | 1 MB | Total payload |
| Batches per second | 10 | Per-user rate limit |
| Events per calendar per day | 10,000 | Import quota |
Best Practices:
- Pre-deduplicate imports using
iCalUIDto avoid conflicts - Use
conferenceDataVersion=1only if importing video conference data - Set
sendUpdates=nonefor bulk imports to avoid notification spam - Implement client-side rate limiting (100ms between batches minimum)
- Track import progress and allow resume on failure
Import Deduplication:
POST /calendar/v3/calendars/primary/events?conferenceDataVersion=0
Content-Type: application/json
{
"iCalUID": "[email protected]",
"summary": "Imported Event",
...
}If an event with the same iCalUID exists, the API returns 409 Conflict, allowing the client to decide whether to update or skip.
| Endpoint Category | p50 | p95 | p99 |
|---|---|---|---|
| Single event read | 50ms | 150ms | 300ms |
| Single event write | 100ms | 300ms | 500ms |
| Event list (≤100 events) | 100ms | 300ms | 600ms |
| Event list (≤1000 events) | 300ms | 800ms | 1500ms |
| Batch request (50 ops) | 500ms | 1500ms | 3000ms |
| Calendar list | 75ms | 200ms | 400ms |
| Watch channel setup | 100ms | 300ms | 500ms |
| Metric | Target | Measurement |
|---|---|---|
| Monthly uptime | 99.95% | (total_minutes - downtime_minutes) / total_minutes |
| Error rate (5xx) | < 0.1% | 5xx_responses / total_responses |
| Planned maintenance | < 4 hours/year | Announced 7 days in advance |
| Regional failover | < 60 seconds | Time to redirect traffic |
Uptime Calculation:
- Downtime = API returns 5xx or is unreachable for > 1 minute
- Partial degradation (elevated latency) does not count as downtime
- Maintenance windows excluded if announced per SLA
| Quota Type | Default Limit | Increase Available |
|---|---|---|
| Queries per day | 1,000,000 | Yes (request) |
| Queries per user per second | 10 | No |
| Queries per user per 100 seconds | 500 | No |
| Push channels per user | 100 | Yes (request) |
| Events per calendar | 100,000 | No |
| Calendars per user | 10,000 | No |
| Batch requests per second | 10 | No |
Rate Limit Response:
{
"error": {
"code": 429,
"message": "Rate Limit Exceeded",
"errors": [{
"domain": "usageLimits",
"reason": "rateLimitExceeded"
}]
}
}Headers included: Retry-After: 30 (seconds until retry is allowed)
| Data Type | Retention | Notes |
|---|---|---|
| Event data | Indefinite | Until user deletes |
| Deleted events (tombstones) | 30 days | For sync purposes |
| Sync tokens | 7 days minimum | May be invalidated earlier |
| Push channel registrations | Per TTL (max 30 days) | Must be renewed |
| API request logs | 30 days | For debugging |
| Audit logs | 6 months | For compliance |
Token Expiration Handling:
- Sync token expiration returns HTTP 410 Gone
- Push channel expiration sends termination notification
- OAuth tokens follow standard Google OAuth expiration (1 hour access, rotating refresh)
Authentication:
| Requirement | Specification |
|---|---|
| Protocol | OAuth 2.0 |
| Token type | Bearer token |
| Token lifetime | 1 hour (access), rotating (refresh) |
| PKCE | Required for mobile/desktop apps |
Transport Security:
| Requirement | Specification |
|---|---|
| Protocol | TLS 1.2+ required |
| Certificate validation | Required; no self-signed |
| HSTS | Enforced |
| Certificate pinning | Recommended for mobile apps |
Webhook Security:
| Requirement | Specification |
|---|---|
| Domain verification | Required via Search Console |
| HTTPS | Required; no HTTP callbacks |
| Signature validation | HMAC-SHA256 signature in header |
| IP allowlisting | Optional; Google IP ranges published |
Data Encryption:
| State | Encryption |
|---|---|
| In transit | TLS 1.2+ |
| At rest | AES-256 (Google-managed keys) |
| Client-side | Optional (not supported by API) |
| Metric | Target | Measurement Method |
|---|---|---|
| Sync latency (change → notification) | p50 < 3s, p99 < 10s | End-to-end instrumentation |
| API availability | 99.95% monthly | Uptime monitoring |
| Push notification delivery rate | > 99.5% | Delivered / Sent |
| Incremental sync efficiency | > 95% delta syncs | Delta syncs / Total syncs |
| Client adoption rate | 80% of top 100 apps | Developer console metrics |
| Metric | Target | Measurement Method |
|---|---|---|
| Error rate by endpoint | < 0.5% client errors, < 0.1% server errors | API logs |
| Average sync payload size | < 50 KB | Response size aggregation |
| Full sync frequency | < 1 per user per week | Sync token invalidation rate |
| Batch operation success rate | > 99% | Batch response analysis |
| OAuth token refresh success | > 99.9% | Auth server metrics |
| Metric | Target | Measurement Method |
|---|---|---|
| Time to first successful sync | < 30 minutes | Developer onboarding funnel |
| Documentation satisfaction | > 4.0/5.0 | Developer survey |
| Support ticket volume | < 100/month | Support system |
| API client library adoption | > 70% | User-agent analysis |
Key metrics to display in real-time:
- Request volume (QPS) by endpoint
- Latency distribution (p50, p95, p99)
- Error rate by type (4xx, 5xx)
- Push notification queue depth
- Active push channels
- Rate limit triggers per minute
- Regional traffic distribution
The following items are explicitly not covered by this PRD:
| Item | Reason | Future Consideration |
|---|---|---|
| CalDAV protocol support | Legacy protocol; REST API is strategic direction | No |
| Real-time collaborative editing | Different product (Google Docs model) | No |
| End-to-end encryption | Requires client key management; architectural change | Yes (separate PRD) |
| Offline-first architecture | Client concern | N/A |
| Free/busy aggregation across orgs | Enterprise feature (separate API) | Yes (Workspace PRD) |
| Natural language event creation | AI/ML feature (separate roadmap) | Yes (future PRD) |
| Video conferencing integration details | Covered by Meet API PRD | N/A |
| Admin console / domain-wide delegation | Workspace Admin SDK scope | N/A |
| GDPR data export | Covered by Takeout integration | N/A |
| Push notification relay infrastructure | FCM/APNs team ownership | N/A |
| Long-polling / SSE fallback | Future consideration for desktop clients | Yes (Phase 2) |
| Question | Options | Recommendation | Owner | Status |
|---|---|---|---|---|
| Should we support Server-Sent Events as push fallback? | (A) Webhook only, (B) Add SSE, (C) Add WebSocket | (B) Add SSE for desktop clients without webhook capability | Platform Team | Pending |
| How should we handle sync token invalidation during schema migrations? | (A) Mass invalidate, (B) Dual-write period, (C) Transparent migration | (B) Dual-write with 30-day overlap | Storage Team | Pending |
| Should batch operations support transactions? | (A) All-or-nothing, (B) Best-effort (current), (C) Partial with rollback | (B) Best-effort with detailed per-item status | API Team | Decided |
| Question | Context | Stakeholder | Status |
|---|---|---|---|
| Should we expose conflict resolution hints to clients? | Clients currently must implement their own merge logic | PM | Pending |
| What is the minimum sync token validity period? | 7 days is current; some partners request 30 days | PM, Partnerships | Pending |
| Should we offer premium SLA tiers for enterprise partners? | Higher quotas, dedicated support, guaranteed latency | Partnerships, Finance | Pending |