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
PENDINGwhile 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
| Endpoint | Purpose |
|---|---|
POST /v3/resource/:id/externalCalendar | Create a connection (returns authUrl). |
PUT /v3/resource/:id/externalCalendar/:externalCalendarId/regenerateAuthUrl | Reissue an OAuth URL when the previous link expired. |
GET /v3/resource/:id/externalCalendars | List calendars for the resource (pending + active). |
DELETE /v3/resource/:id/externalCalendar/:externalCalendarId | Disconnect and remove cached unavailability. |
GET /v3/resource/:id/externalCalendars/list | Fetch provider calendars once OAuth succeeds. Optional query: provider, externalCalendarId (infer provider when only one connection exists). |
POST /v3/resource/:id/externalCalendars/select | Choose read calendars and an optional single write calendar (readCalendarIds, writeCalendarId). Optional query: provider, externalCalendarId. |
POST /v3/resource/:id/externalCalendar/createUnavailabilityBulk | Manually 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
- Create connection:
POST /v3/resource/:id/externalCalendarwith{ "provider": "google" }. Response includes anauthUrl. - Authorize: Provider authenticates using the returned URL; OnSched receives refresh tokens and marks the record as active.
- Select calendars:
- Call
GET /v3/resource/:id/externalCalendars/listto show available calendars (omitproviderwhen the resource has a single connected provider). - Invoke
POST /v3/resource/:id/externalCalendars/selectwith arrays of IDs to read, plus the ID to write to (passexternalCalendarIdfrom OAuth when disambiguating accounts).
- Call
- 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=.... - 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/availabilitywithsyncExternal=trueexcludes 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=falseand appointment requests withignoreExternalEvents=trueintentionally 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:googleoroutlook.pending: Boolean indicating OAuth completion.read/write: Flags that determine whether events block availability or receive appointment events.calendarIdandcalendarEmail: 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=trueonGET /v3/availabilityfetches 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
blockAvailabilityOnExternalCalendarFailurewhen uncertain Resources must be removed fromsyncExternal=trueresults 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
writeflag cleared.
Troubleshooting
- No Google calendars listed: The resource must authorize first; only then can
/listsucceed. - External events missing: Confirm the resource chosen read calendars via
/selectand that the user granted read permissions. TriggercreateUnavailabilityBulkfor immediate ingestion if needed. syncExternal=truestill 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 runcreateUnavailabilityBulkfor the affected range.
External calendar syncing keeps OnSched availability accurate without revealing internal scheduling rules, while still respecting provider preferences.
Updated 1 day ago
