External Calendar Sync Guide

Connect Google and Outlook calendars to resources so external events block availability and confirmed appointments create calendar events.

When to Use

  • Keep provider calendars in sync so double-bookings never occur.
  • Let providers own their existing Google or Outlook workflows while the API remains the system of record.
  • Trigger immediate syncs before quoting availability (syncExternal=true).

Concepts

  • Resource-Centric: The supported Merchant Portal workflow connects one Google or Outlook account to a Resource (/v3/resource/:id/...) at a time. A connected account can expose multiple read calendars plus one optional write calendar.
  • Pending vs Active: Health is PENDING while OAuth, first event verification, or a selected-calendar event sync is in progress.
  • Read vs Write: Read calendars block time; the single write calendar receives confirmed appointments.

Endpoint Overview

EndpointPurpose
POST /v3/resource/:id/externalCalendarCreate a connection (returns authUrl).
PUT /v3/resource/:id/externalCalendar/:externalCalendarId/regenerateAuthUrlReissue an OAuth URL when the previous link expired.
GET /v3/resource/:id/externalCalendarsList calendars for the resource (pending + active).
DELETE /v3/resource/:id/externalCalendar/:externalCalendarIdDisconnect and remove cached unavailability.
GET /v3/resource/:id/externalCalendars/listFetch provider calendars once OAuth succeeds. Optional query: provider, externalCalendarId (infer provider when only one connection exists).
POST /v3/resource/:id/externalCalendars/selectChoose read calendars and an optional single write calendar (readCalendarIds, writeCalendarId). Optional query: provider, externalCalendarId.
POST /v3/resource/:id/externalCalendar/createUnavailabilityBulkManually trigger ingestion of external events into OnSched’s Unavailability table for a date range.

All routes require verifyToken + verifyCompany. Google and Outlook both support selecting multiple calendars for reading and one optional calendar for writing.

Typical Setup Flow

  1. Create connection: POST /v3/resource/:id/externalCalendar with { "provider": "google" }. Response includes an authUrl.
  2. Authorize: Provider authenticates using the returned URL; OnSched receives refresh tokens and marks the record as active.
  3. Select calendars:
    • Call GET /v3/resource/:id/externalCalendars/list to show available calendars (omit provider when the resource has a single connected provider).
    • Invoke POST /v3/resource/:id/externalCalendars/select with arrays of IDs to read, plus the ID to write to (pass externalCalendarId from OAuth when disambiguating accounts).
  4. Sync events: Availability automatically respects external events. To force an immediate ingest (for same-minute updates), call POST /v3/resource/:id/externalCalendar/createUnavailabilityBulk?startDate=...&endDate=....
  5. Invalidate caches: Calendar changes clear affected availability caches, ensuring fresh slots the next time you call GET /v3/availability.

Fail-Closed Availability Protection

Resources support the opt-in blockAvailabilityOnExternalCalendarFailure setting. It defaults to false.

When enabled:

  • GET /v3/availability with syncExternal=true excludes the Resource while authorization or an event sync is pending, or after its latest selected read-calendar sync failed.
  • Hold, book, and reschedule validation reject the slot through the normal slot-unavailable response while external events are respected.
  • syncExternal=false and appointment requests with ignoreExternalEvents=true intentionally bypass both external busy events and calendar-health enforcement.
  • A Resource with no connected account, or a connected account with no read calendars selected, remains eligible.
  • Legacy connections begin as UNKNOWN, which is non-blocking until the first successful or failed event-sync attempt.

Authenticated Resource responses include:

{
  "blockAvailabilityOnExternalCalendarFailure": true,
  "externalCalendarHealth": {
    "status": "HEALTHY",
    "lastAttemptAt": "2026-07-16T20:00:00.000Z",
    "lastSuccessAt": "2026-07-16T20:00:00.000Z",
    "failureCode": null
  }
}

Health statuses are NOT_CONNECTED, PENDING, NOT_MONITORED, UNKNOWN, HEALTHY, and FAILED. PENDING covers incomplete OAuth, first event-read verification, and a currently running selected-calendar event sync. Failed responses expose only a sanitized failureCode: AUTH, RATE_LIMITED, TIMEOUT, PROVIDER_UNAVAILABLE, or UNKNOWN.

Data You Receive

Each calendar record contains:

  • provider: google or outlook.
  • pending: Boolean indicating OAuth completion.
  • read / write: Flags that determine whether events block availability or receive appointment events.
  • calendarId and calendarEmail: Maps back to the provider’s identifiers.
  • refreshToken: Stored securely; never share this outside your backend.

Ingest behavior: Provider events marked free or transparent (for example Google Working Location, Outlook showAs: free) are not stored as Unavailability and do not block availability.

Calendar view: GET /v3/unavailability rows that originate from external sync include optional external_transparency, external_event_type, and external_busy for styling or debugging busy provider events.

Best Practices

  • Limit External Syncs: syncExternal=true on GET /v3/availability fetches external events just-in-time but adds latency. Use it only when the UI must account for updates that happened seconds ago. Otherwise rely on the background sync cadence triggered by bookings and periodic jobs.
  • Plan for Provider Failures: If a provider or OAuth issue prevents one selected read calendar from syncing, OnSched leaves previously stored external busy blocks in place and records the aggregate Resource health as failed. Enable blockAvailabilityOnExternalCalendarFailure when uncertain Resources must be removed from syncExternal=true results until every selected read calendar syncs successfully.
  • Clean Disconnects: Always delete calendars before removing resources so the API can revoke provider tokens and remove stored external blocks.
  • Read vs Write: Selecting more than one write calendar is not allowed. If you change the write target, the old calendar automatically has its write flag cleared.

Troubleshooting

  • No Google calendars listed: The resource must authorize first; only then can /list succeed.
  • External events missing: Confirm the resource chosen read calendars via /select and that the user granted read permissions. Trigger createUnavailabilityBulk for immediate ingestion if needed.
  • syncExternal=true still shows old external blocks: A skipped provider sync does not delete previously stored busy blocks. Reauthorize the calendar when OAuth tokens expire, then retry the availability request or run createUnavailabilityBulk for the affected range.

External calendar syncing keeps OnSched availability accurate without revealing internal scheduling rules, while still respecting provider preferences.


Did this page help you?