Replace an external calendar’s imported busy-time snapshot.
Action: app_topolo_calendar.external_calendars.sync · POST /api/admin/external-calendars/{calendarId}/sync
Declared permission: app_topolo_calendar.availability:write. For authorized Calendar users; permissions and resource access still apply.
Before you start
- Use the current credential to confirm the production organization and inspect this exact action before preparing a call.
- Declared permission: app_topolo_calendar.availability:write. Permission discovery is not a grant.
- Discover the authorized Calendar workspace and pass it as resourceType=workspace/resourceId separately from this action input. Calendar records and IDs are scoped to the selected host/workspace.
- Validate and plan the exact payload, obtain effect-specific authorization, then execute unchanged only in a separately authorized operational task. Do not run these synthetic inputs.
Inputs and field meanings
| Field | Presence | Declared constraint | Meaning |
|---|---|---|---|
calendarId | required | string | Calendar record ID discovered with external_calendars.list; distinct from a Nexus connection ID. |
blocks | optional | array | For manual/ICS, the complete replacement busy-block set for this calendar. Empty/omitted clears its stored blocks; it is not a patch. |
blocks[].startAt | required | string | Offset-aware ISO8601 instant. The source stores normalized UTC; retain the intended host/invitee timezone interpretation. |
blocks[].endAt | required | string | Offset-aware end instant after start. Create derives end from duration/type; update/reschedule can use this explicit end. |
blocks[].sourceUid | optional | nullable/union | Optional source occurrence identifier used to interpret an imported busy block. |
blocks[].summary | optional | nullable/union | Optional imported busy-time summary; it can contain private information. |
blocks[].busyType | optional | string; enum=["busy", "tentative", "out_of_office", "unknown"] | busy, tentative, out_of_office or unknown classification; review the actual effect on offered slots. |
nextSyncCursor | optional | nullable/union | Optional synchronization cursor stored with the calendar. Keep its provenance; do not invent a provider cursor. |
windowStart | optional | string | Offset-aware beginning of the booking query or provider sync fetch window; use a meaningful window and pair with windowEnd. |
windowEnd | optional | string | Offset-aware end after windowStart. Manual/ICS sync does not use this field as a deletion boundary. |
Worked request and response
Source-reviewed illustration only. Nothing below was executed; all identifiers, counts, states, credentials and URLs are synthetic. Replace identifiers through discovery before any separately authorized call.
Resource context: workspace; it is not an extra action input field.
Illustrative action input:
{
"calendarId": "cal_example",
"blocks": [
{
"startAt": "2026-11-23T12:00:00+04:00",
"endAt": "2026-11-23T13:00:00+04:00",
"sourceUid": "illustrative-busy",
"summary": "Illustrative busy time",
"busyType": "busy"
}
],
"nextSyncCursor": null
}
Illustrative HTTP 200 source response:
{
"externalCalendar": {
"id": "cal_example",
"hostId": "hst_example",
"provider": "manual",
"accountEmail": "host@example.com",
"displayName": "Example busy calendar",
"providerCalendarId": null,
"nexusConnectionId": null,
"connectionStatus": null,
"connectionStatusReason": null,
"lastSyncedAt": "2026-11-01T00:00:00.000Z",
"status": "active",
"syncCursor": null,
"lastError": null,
"createdAt": "2026-11-01T00:00:00.000Z",
"updatedAt": "2026-11-01T00:00:00.000Z"
},
"busyBlocks": [
{
"id": "busy_example",
"externalCalendarId": "cal_example",
"hostId": "hst_example",
"startAt": "2026-11-23T08:00:00.000Z",
"endAt": "2026-11-23T09:00:00.000Z",
"sourceUid": "illustrative-busy",
"summary": "Illustrative busy time",
"busyType": "busy",
"createdAt": "2026-11-01T00:00:00.000Z",
"updatedAt": "2026-11-01T00:00:00.000Z"
}
]
}
This manual example imports 12:00–13:00 local as 08:00–09:00 UTC. The source deletes all stored blocks for this host/calendar before inserting supplied blocks, not merely blocks in a request window. The returned busyBlocks query can include other calendars.
The illustrative response passes the declared structural schema. This establishes only schema compatibility, not deployed shape, values, authorization or business outcome.
For newly imported busy blocks, the reviewed insert sets updated_at to CURRENT_TIMESTAMP; the illustrative updatedAt therefore records the import time.
Effects and verification
- For manual/ICS, replaces the entire calendar block set; omitted blocks default to an empty array and can clear it.
- For Google/Microsoft/CalDAV, resolves Nexus credentials and fetches provider busy time using the sync window instead of treating supplied blocks as the provider result.
- Does not cancel existing bookings.
- Read the external calendar state and computed availability for the intended window; reconcile imported IDs and sync status separately from existing bookings.
Refusals and recovery
Selected source refusals are listed below; runtime middleware can return additional structured errors.
| HTTP | Error | Supported response |
|---|---|---|
| 403 | forbidden | Reconcile the declared permission, organization and verified resource grant with the legitimate access owner; do not switch context to bypass a refusal. |
| 404 | no_host | Read host.get; if it returns host:null, create the intended workspace host through host.update before retrying. |
| 404 | not_found | List records in the selected workspace and discover the identifier again. Do not probe another workspace by guessing IDs. |
| 400 | invalid_busy_block | Every block must have valid times with end after start. |
| 502 | external_sync_failed | Retain the structured failure and reconcile provider/Nexus/Calendar state before retrying. |
Recovery: No universal rollback exists. For a manual/ICS record, a separately authorized sync can restore a retained complete block snapshot/cursor. A provider sync must be corrected at the appropriate provider/connection boundary and reverified.
After an uncertain mutation, reconcile the named records and provider/delivery evidence before repeating it.
Related actions
app_topolo_calendar.external_calendars.list: Discover external calendar IDs, connection state and sync history.app_topolo_calendar.availability.get: Read the current host’s complete weekly rules and date overrides.app_topolo_calendar.bookings.list: Find bookings within an explicit time window.
Declared schemas and provenance
The hidden action guide carries these exact current declared schemas. They are not rewritten to make the illustrations pass.
Input schema:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"calendarId": {
"type": "string",
"minLength": 1
},
"blocks": {
"type": "array",
"items": {
"type": "object",
"properties": {
"startAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
},
"endAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
},
"sourceUid": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"summary": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"busyType": {
"type": "string",
"enum": [
"busy",
"tentative",
"out_of_office",
"unknown"
]
}
},
"required": [
"startAt",
"endAt"
],
"additionalProperties": false
}
},
"nextSyncCursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"windowStart": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
},
"windowEnd": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
}
},
"required": [
"calendarId"
],
"additionalProperties": false
}
Output schema:
{
"type": "object",
"title": "Sync external calendar response",
"description": "Operation result returned by external_calendars.sync.",
"properties": {
"success": {
"type": "boolean"
},
"id": {
"type": "string"
},
"data": {
"oneOf": [
{
"type": "object",
"title": "external record",
"properties": {
"id": {
"type": "string",
"description": "Stable identifier when the external exposes one."
}
},
"additionalProperties": true
},
{
"type": "object",
"additionalProperties": true
}
]
},
"result": {
"type": "object",
"additionalProperties": true
},
"message": {
"type": "string"
}
},
"additionalProperties": true,
"x-topolo-contract-source": "derived-action-metadata",
"x-topolo-contract-precision": "structural"
}
Source: TopoloCalendar@b378ceb73800ee1d1e6c4ff6f0b4179918cf632c, apps/api/src/routes/admin-external-calendars.ts. Current catalog metadata and source were compared without executing the application.
- Source-reviewed synthetic input/response; not captured or executed.
- Source revision is not proof of current deployment, caller access, provider connection or notification delivery.
- The illustrative response passes the declared structural schema. This establishes only schema compatibility, not deployed shape, values, authorization or business outcome.
- windowStart/windowEnd bound provider fetching; the reviewed manual/ICS branch does not use them to restrict deletion. Empty/omitted blocks can reopen previously suppressed slots.