{
  "schemaVersion": "1.0",
  "title": "Cal.diy calendars and free/busy",
  "description": "Use these capabilities to inspect connected calendars and availability, work with events, choose the destination for new bookings, or change conflict detection. This page owns all 21 operations in the calendar family.",
  "canonical": "https://getaip.org/docs/connectors/cal-diy/capabilities/calendars-and-free-busy",
  "route": "/docs/connectors/cal-diy/capabilities/calendars-and-free-busy",
  "source": "docs/connectors/cal-diy/capabilities/calendars-and-free-busy.md",
  "protocol": "Agent Interoperability Protocol",
  "protocolVersion": "1.0",
  "section": "Connectors",
  "documentType": "Connector",
  "language": "en",
  "downloads": {
    "md": "/docs/download/connectors/cal-diy/capabilities/calendars-and-free-busy.md",
    "txt": "/docs/download/connectors/cal-diy/capabilities/calendars-and-free-busy.txt",
    "json": "/docs/download/connectors/cal-diy/capabilities/calendars-and-free-busy.json",
    "pdf": "/docs/download/connectors/cal-diy/capabilities/calendars-and-free-busy.pdf"
  },
  "content": {
    "format": "text/markdown",
    "markdown": "---\ntitle: Cal.diy calendars and free/busy\ndescription: Read calendar state and availability or change events, routing, and conflict detection\nkind: capability-reference\naudience: application-developer\nappliesTo: \"1.x\"\nwritingStandard: \"aip-docs/1.0\"\nlastReviewedRevision: \"97be86e9efedf07ecf1783b03800f683f107fb04\"\nconnector: cal-diy\ncapabilityIds:\n  - cap:cal_diy:calendar.list\n  - cap:cal_diy:calendar.provider.check\n  - cap:cal_diy:calendar.ics_feed.check\n  - cap:cal_diy:calendar.busy_time.list\n  - cap:cal_diy:calendar.connection.list\n  - cap:cal_diy:calendar.connection.free_busy.get\n  - cap:cal_diy:calendar.free_busy.get\n  - cap:cal_diy:calendar.connection.event.list\n  - cap:cal_diy:calendar.connection.event.get\n  - cap:cal_diy:calendar.connection.event.create\n  - cap:cal_diy:calendar.connection.event.update\n  - cap:cal_diy:calendar.connection.event.delete\n  - cap:cal_diy:calendar.event.list\n  - cap:cal_diy:calendar.event.get\n  - cap:cal_diy:calendar.event.create\n  - cap:cal_diy:calendar.event.update\n  - cap:cal_diy:calendar.event.delete\n  - cap:cal_diy:calendar.disconnect\n  - cap:cal_diy:calendar.destination.update\n  - cap:cal_diy:calendar.selected.add\n  - cap:cal_diy:calendar.selected.delete\n---\n\n# Cal.diy calendars and free/busy\n\nUse these capabilities to inspect connected calendars and availability, work\nwith events, choose the destination for new bookings, or change conflict\ndetection. This page owns all 21 operations in the calendar family.\n\nAll operations use provider API version `2024-08-13`. Every input and result in\nthis family is restricted, contains PII, and requires redaction.\n\n## Choose a calendar surface\n\nCal.diy exposes two event surfaces. Choose one before constructing input:\n\n| Surface | Selector | Provider scope |\n|---|---|---|\n| Unified connection | Integer `connection_id`; optional `calendarId` | A connection returned by Cal.diy |\n| Direct provider | `calendar` fixed to `google` | Google event and free/busy routes only |\n\nThe broader provider checks accept `google`, `office365`, or `apple`. That does\nnot expand the direct event surface: its published schema still requires\n`\"calendar\": \"google\"`.\n\n## Choose an operation\n\n### Connection and availability reads\n\n| Capability | Provider request | Input |\n|---|---|---|\n| `cap:cal_diy:calendar.list` | `GET /v2/calendars` | Empty object |\n| `cap:cal_diy:calendar.provider.check` | `GET /v2/calendars/{calendar}/check` | Provider |\n| `cap:cal_diy:calendar.ics_feed.check` | `GET /v2/calendars/ics-feed/check` | Empty object |\n| `cap:cal_diy:calendar.busy_time.list` | `GET /v2/calendars/busy-times` | Range and calendars |\n| `cap:cal_diy:calendar.connection.list` | `GET /v2/calendars/connections` | Empty object |\n| `cap:cal_diy:calendar.connection.free_busy.get` | `GET /v2/calendars/connections/{connection_id}/freebusy` | Connection and range |\n| `cap:cal_diy:calendar.free_busy.get` | `GET /v2/calendars/{calendar}/freebusy` | Google and range |\n\nAll seven operations are low-risk, approval-free reads at the capability\ncontract level.\n\n### Unified connection events\n\n| Capability | Provider request | Risk | Approval |\n|---|---|---|---|\n| `cap:cal_diy:calendar.connection.event.list` | `GET /v2/calendars/connections/{connection_id}/events` | Low | No |\n| `cap:cal_diy:calendar.connection.event.get` | `GET /v2/calendars/connections/{connection_id}/events/{event_id}` | Low | No |\n| `cap:cal_diy:calendar.connection.event.create` | `POST /v2/calendars/connections/{connection_id}/events` | Medium | Required |\n| `cap:cal_diy:calendar.connection.event.update` | `PATCH /v2/calendars/connections/{connection_id}/events/{event_id}` | Medium | Required |\n| `cap:cal_diy:calendar.connection.event.delete` | `DELETE /v2/calendars/connections/{connection_id}/events/{event_id}` | High | Required |\n\n### Direct Google events\n\n| Capability | Provider request | Risk | Approval |\n|---|---|---|---|\n| `cap:cal_diy:calendar.event.list` | `GET /v2/calendars/{calendar}/events` | Low | No |\n| `cap:cal_diy:calendar.event.get` | `GET /v2/calendars/{calendar}/events/{event_uid}` | Low | No |\n| `cap:cal_diy:calendar.event.create` | `POST /v2/calendars/{calendar}/events` | Medium | Required |\n| `cap:cal_diy:calendar.event.update` | `PATCH /v2/calendars/{calendar}/events/{event_uid}` | Medium | Required |\n| `cap:cal_diy:calendar.event.delete` | `DELETE /v2/calendars/{calendar}/events/{event_uid}` | High | Required |\n\nFor every direct operation in this table, `calendar` must equal `google`.\n\n### Routing and conflict detection\n\n| Capability | Provider request | Risk | Approval |\n|---|---|---|---|\n| `cap:cal_diy:calendar.disconnect` | `POST /v2/calendars/{calendar}/disconnect` | High | Required |\n| `cap:cal_diy:calendar.destination.update` | `PUT /v2/destination-calendars` | Medium | Required |\n| `cap:cal_diy:calendar.selected.add` | `POST /v2/selected-calendars` | Medium | Required |\n| `cap:cal_diy:calendar.selected.delete` | `DELETE /v2/selected-calendars` | High | Required |\n\nDisconnect, both event deletes, and selected-calendar delete are destructive.\n\n## Inspect calendar connections\n\nList connected calendars with an empty input:\n\n```json\n{}\n```\n\nUse `calendar.provider.check` to test one provider connection:\n\n```json\n{\n  \"calendar\": \"google\"\n}\n```\n\nThe exact provider enum is `google`, `office365`, or `apple`. Provider check\nshows whether the authenticated integration is connected and healthy at that\nrequest boundary. It does not prove that every calendar is visible or writable.\n\n`calendar.ics_feed.check` also accepts an empty object and checks the\nauthenticated user's ICS feed connection. `calendar.connection.list` returns\ncredential-safe unified connection identifiers; the connector does not publish\ntheir provider-owned response fields.\n\nThese reads inspect connections that already exist. Provider connect and save\nroutes that establish browser OAuth sessions or credentials remain outside the\nagent-callable business-operation surface.\n\n## Read busy time across selected calendars\n\n`calendar.busy_time.list` requires a time zone, a bounded range, and at least\none explicit calendar:\n\n```json\n{\n  \"timeZone\": \"UTC\",\n  \"dateFrom\": \"2026-08-03\",\n  \"dateTo\": \"2026-08-04T18:00:00Z\",\n  \"calendarsToLoad\": [\n    {\n      \"credentialId\": 42,\n      \"externalId\": \"primary@example.com\"\n    }\n  ]\n}\n```\n\n| Field | Constraint |\n|---|---|\n| `timeZone` | Non-empty string |\n| `dateFrom`, `dateTo` | ISO date or date-time string |\n| `calendarsToLoad` | Array with at least one object |\n| `credentialId` | Integer required in each object |\n| `externalId` | Non-empty string required in each object |\n\nThe client sends these values as query parameters. Nested calendar objects use\nbracket notation such as `calendarsToLoad[0][credentialId]`. The schema does not\nset a maximum or require unique calendar objects, and it does not assert that\n`dateFrom` precedes `dateTo`.\n\nBusy intervals are scheduling evidence, not a reservation or a guarantee that\na later booking will succeed.\n\n## Read unified connection events\n\nList events for a unified connection with a required range:\n\n```json\n{\n  \"connection_id\": 73,\n  \"from\": \"2026-08-03T00:00:00Z\",\n  \"to\": \"2026-08-04T00:00:00Z\",\n  \"timeZone\": \"UTC\",\n  \"calendarId\": \"team@example.com\"\n}\n```\n\n`connection_id`, `from`, and `to` are required. `from` and `to` accept either\nan ISO date or date-time. Optional `timeZone` and `calendarId` must be non-empty.\nThe connection id is a path segment; all range fields and `calendarId` become\nquery parameters.\n\nRead one event with `connection_id` and a non-empty `event_id`. An optional\n`calendarId` selects a calendar within that connection and is sent as a query\nparameter:\n\n```json\n{\n  \"connection_id\": 73,\n  \"event_id\": \"provider-event-uid\",\n  \"calendarId\": \"team@example.com\"\n}\n```\n\n## Create a unified connection event\n\nCreate requires the connection, title, start, and end:\n\n```json\n{\n  \"connection_id\": 73,\n  \"calendarId\": \"team@example.com\",\n  \"title\": \"Architecture review\",\n  \"start\": {\n    \"time\": \"2026-08-03T10:00:00Z\",\n    \"timeZone\": \"UTC\"\n  },\n  \"end\": {\n    \"time\": \"2026-08-03T10:30:00Z\",\n    \"timeZone\": \"UTC\"\n  },\n  \"description\": \"Review the proposed integration boundary.\",\n  \"attendees\": [\n    {\n      \"email\": \"avery@example.com\",\n      \"name\": \"Avery Patel\"\n    }\n  ]\n}\n```\n\n`title` must be non-empty. Both `start` and `end` require a date-time `time`\nand a non-empty `timeZone`. `description` may be a string or `null`.\n`attendees` may contain objects with a required `email` and optional `name`;\nthe array has no published minimum or uniqueness constraint. Additional fields\nare rejected at the event, date-time, and attendee object boundaries.\n\n`calendarId` is optional. The client puts it in the query while sending event\ndetails as JSON. The connector contract carries `write` and `external_network`,\nbut not `send_message`. Attendee input is therefore not evidence that the\nprovider sent an invitation.\n\nThe create operation names `calendar.connection.event.delete` as best-effort\ncompensation. Compensation requires a separate approval and is not automatic.\n\n## Update or delete a unified connection event\n\nUpdate requires `connection_id` and `event_id`. It may include `calendarId`\nand any of these body fields:\n\n| Field | Constraint |\n|---|---|\n| `title` | String; an empty string passes the connector schema |\n| `start`, `end` | Object with optional date-time `time` and string `timeZone` |\n| `description` | String or `null` |\n| `attendees` | Array or `null` |\n| `status` | `accepted`, `pending`, `declined`, `cancelled`, or `null` |\n\nAn attendee update requires `email` and may include `name`, `self`, `optional`,\n`host`, and `responseStatus`. Response status accepts `accepted`, `pending`,\n`declined`, `needsAction`, or `null`.\n\nThe schema allows an identifier-only update and empty `start` or `end` objects.\nSuch input can pass local validation without expressing a useful provider\nchange. Include only an intentional, provider-supported patch.\n\nDelete uses `connection_id` and `event_id`, plus optional `calendarId`. The two\nids become path segments and `calendarId` becomes a query parameter. Delete is\nhigh risk and has no rollback contract.\n\nBoth update and delete first read the exact unified event. A successful\npreflight proves only that the event was readable before mutation.\n\n## Read, create, or change a direct Google event\n\nThe direct surface mirrors the unified event model with these selector changes:\n\n- every input requires `\"calendar\": \"google\"`;\n- list also requires `from` and `to`, with optional `timeZone` and `calendarId`;\n- get and delete require a non-empty `event_uid`;\n- create has no `calendarId` field;\n- update requires `event_uid` and uses the shared optional patch fields.\n\nCreate and update use the field constraints described for unified events.\nDirect create names `calendar.event.delete` as best-effort compensation.\nDirect update and delete first run `calendar.event.get` for the same\n`calendar` and `event_uid`.\n\nThe direct schema does not publish Office 365 or Apple event variants. Use a\nunified connection when its returned identifier is the intended routing model.\n\n## Read free/busy\n\nUnified free/busy requires `connection_id`, `from`, and `to`. Direct free/busy\nrequires `calendar` fixed to `google`, `from`, and `to`. Both accept optional\nnon-empty `timeZone`; all range fields are query parameters.\n\n```json\n{\n  \"connection_id\": 73,\n  \"from\": \"2026-08-03\",\n  \"to\": \"2026-08-04\",\n  \"timeZone\": \"UTC\"\n}\n```\n\nThe input schema accepts dates or date-times and does not enforce ordering or a\nmaximum interval. A free/busy response is provider-owned and restricted. It\ndoes not by itself apply schedule rules, event-type buffers, reservations, or\nbooking policy.\n\n## Change the destination calendar\n\n`calendar.destination.update` changes where new bookings are written:\n\n```json\n{\n  \"integration\": \"google_calendar\",\n  \"externalId\": \"primary@example.com\"\n}\n```\n\n`integration` must be `apple_calendar`, `google_calendar`, or\n`office365_calendar`. `externalId` is required and non-empty. An optional\nnon-empty `delegationCredentialId` selects delegated provider credentials.\n\nThe connector first lists calendars. This does not prove that the requested\n`externalId` appeared in that result; provider validation still decides the\nmutation. The operation is medium risk and has no compensating capability.\n\n## Add or remove a conflict calendar\n\nAdd requires an integration name, external calendar id, and integer credential\nid:\n\n```json\n{\n  \"integration\": \"google_calendar\",\n  \"externalId\": \"team@example.com\",\n  \"credentialId\": 42\n}\n```\n\nAll three fields are required. `integration` and `externalId` must be\nnon-empty; `credentialId` is an integer. Optional `delegationCredentialId` must\nbe non-empty. The schema does not constrain `integration` to the destination\ncalendar enum.\n\nRemove uses the same route but requires `credentialId` as a non-empty string,\nnot an integer. The client sends `integration`, `externalId`, `credentialId`,\nand optional `delegationCredentialId` as query parameters on `DELETE`.\n\nBoth operations first list calendars. Add declares\n`calendar.selected.delete` as best-effort compensation. Remove is destructive\nand has no further rollback.\n\n## Disconnect a provider credential\n\nDisconnect requires a provider and integer credential id:\n\n```json\n{\n  \"calendar\": \"office365\",\n  \"id\": 42\n}\n```\n\n`calendar` must be `google`, `office365`, or `apple`. The client sends a `POST`\nto the provider-specific disconnect path and leaves `id` in the JSON body.\nBefore dispatch, it checks that provider connection without using the id.\n\nInteger identifiers in this family have no additional minimum in the published\nschema. Cal.diy can still reject a value outside the provider's domain.\n\nThe operation is high risk and destructive. It deletes an owned credential and\ninvalidates the connection. No compensation or automatic credential recovery\nis declared.\n\n## Preflight reads\n\n| Mutation | Connector preflight |\n|---|---|\n| Unified or direct event create | `calendar.list` |\n| Destination update | `calendar.list` |\n| Selected-calendar add or delete | `calendar.list` |\n| Unified event update or delete | Exact unified event get |\n| Direct event update or delete | Exact direct event get |\n| Calendar disconnect | Provider check for `calendar` |\n\nA preflight is a point-in-time read, not a lock, plan, reservation, or approval.\n\n## Shared read contract\n\nAll 11 reads are low risk, synchronous, retry-safe, and eligible for connector\nretry. Their idempotency key is optional, action-scoped, and collision-checked\nagainst the input hash. No capability-level approval is required.\n\nIdentity, tenant, external-account, credential-scope, and restricted-data\npolicy still apply to every read.\n\n## Shared mutation contract\n\nAll ten mutations require:\n\n| Contract field | Value |\n|---|---|\n| Human approval | Required |\n| Approval selector | Tenant policy |\n| Approval validity window | 900,000 ms |\n| Approval evidence | Reason, input snapshot, policy decision |\n| Idempotency | Required; external-account scope |\n| Collision behavior | Revalidate input hash |\n| Connector retry | Not supported |\n| Retry safety | Unsafe |\n| Execution | Synchronous only |\n| Transaction modes | `dry_run`, `plan`, `commit`, `reconcile` |\n| Plan before commit | Not required |\n| Dry-run fidelity | Policy and schema only |\n\nSix non-destructive mutations are medium risk. Calendar disconnect, both event\ndeletes, and selected-calendar delete are high risk and destructive. None of\nthe ten mutations carries the `send_message` side-effect marker.\n\nEvent create on either surface and selected-calendar add each declare one\nbest-effort delete compensation. All other mutations declare rollback as not\nsupported. Never interpret a compensation declaration as automatic execution\nor restoration of invitations, notifications, external state, or history.\n\n## Result and redaction contract\n\nThe connector returns the validated provider JSON body as AIP output. A\nnon-empty string `status` is required. `data`, `pagination`, and additional\nprovider fields are optional; their shapes are not guaranteed by the connector\nschema.\n\nThe connector recursively replaces secret-, key-, and token-named values with\n`[REDACTED]`. This targeted redaction does not remove event titles, attendee\naddresses, calendar ids, busy intervals, descriptions, or other PII unless a\nfield name matches the secret redactor. Keep the complete output restricted.\n\n## Failures and recovery\n\n| Failure | Relevant cause | Safe response |\n|---|---|---|\n| `connector.cal_diy.invalid_action` | Missing selector, invalid range shape, invalid enum, or unknown field | Correct the exact input before another call |\n| `connector.cal_diy.policy` | Mutation omitted its required idempotency key | Preserve approved input and add the missing key |\n| `connector.cal_diy.idempotency_collision` | One key owns different mutation input | Stop and inspect the original action |\n| `connector.cal_diy.in_flight` | A matching calendar mutation is executing | Read durable state and wait |\n| `connector.cal_diy.outcome_unknown` | Prior provider dispatch cannot be proved | Reconcile; do not create a replacement key |\n| `connector.cal_diy.authentication` | Provider returned `401` or `403` | Verify account binding, credential revision, and calendar ownership |\n| `connector.cal_diy.state_conflict` | Provider returned `409` or `412` | Refresh event or calendar state and review the mutation |\n| `connector.cal_diy.rate_limited` | Provider returned `429` | Retry reads within policy; reconcile mutations |\n| `connector.cal_diy.remote_temporary` | Provider returned a server error | Retry reads within policy; reconcile mutations |\n| `connector.cal_diy.transport` | Provider exchange failed | Retry reads only; treat dispatched mutations as uncertain |\n| `connector.cal_diy.invalid_provider_output` | Successful response lacked valid `status` | Preserve evidence and reconcile a mutation |\n\nPermanent rejection, invalid credentials, oversized responses, and durable\nsettlement failures use the common Cal.diy error family. Gateway and lifecycle\nerrors are described in the [global error reference](../../../reference/errors.md).\n\n## Verify calendar handling\n\nFor a read, retain the capability, exact selector and range, actor, tenant,\nexternal account, provider status, and provider request id when present. Verify\nthat the result remains inside its approved restricted-data destination.\n\nFor a mutation, also retain approval evidence and the original idempotency key.\nWait for a completed durable result, then read the exact event or list calendar\nstate. For an uncertain dispatch, reconcile the original operation before any\nnew action.\n\nThese checks validate application handling. They do not prove invitation\ndelivery, provider-wide visibility, live availability, cross-provider\nqualification, or production readiness.\n\n## Related documentation\n\n- [Cal.diy capability index](README.md)\n- [Core booking capabilities](bookings.md)\n- [Authentication and tenant routing](../getting-started/authentication-and-tenant-routing.md)\n- [Transactions and compensation](../../../concepts/transactions-and-compensation.md)\n- [Error reference](../../../reference/errors.md)\n",
    "text": "Cal.diy calendars and free/busy\n\nUse these capabilities to inspect connected calendars and availability, work\nwith events, choose the destination for new bookings, or change conflict\ndetection. This page owns all 21 operations in the calendar family.\n\nAll operations use provider API version 2024-08-13. Every input and result in\nthis family is restricted, contains PII, and requires redaction.\n\nChoose a calendar surface\n\nCal.diy exposes two event surfaces. Choose one before constructing input:\n\n| Surface | Selector | Provider scope |\n\n| Unified connection | Integer connectionid; optional calendarId | A connection returned by Cal.diy |\n| Direct provider | calendar fixed to google | Google event and free/busy routes only |\n\nThe broader provider checks accept google, office365, or apple. That does\nnot expand the direct event surface: its published schema still requires\n\"calendar\": \"google\".\n\nChoose an operation\n\nConnection and availability reads\n\n| Capability | Provider request | Input |\n\n| cap:caldiy:calendar.list | GET /v2/calendars | Empty object |\n| cap:caldiy:calendar.provider.check | GET /v2/calendars/{calendar}/check | Provider |\n| cap:caldiy:calendar.icsfeed.check | GET /v2/calendars/ics-feed/check | Empty object |\n| cap:caldiy:calendar.busytime.list | GET /v2/calendars/busy-times | Range and calendars |\n| cap:caldiy:calendar.connection.list | GET /v2/calendars/connections | Empty object |\n| cap:caldiy:calendar.connection.freebusy.get | GET /v2/calendars/connections/{connectionid}/freebusy | Connection and range |\n| cap:caldiy:calendar.freebusy.get | GET /v2/calendars/{calendar}/freebusy | Google and range |\n\nAll seven operations are low-risk, approval-free reads at the capability\ncontract level.\n\nUnified connection events\n\n| Capability | Provider request | Risk | Approval |\n\n| cap:caldiy:calendar.connection.event.list | GET /v2/calendars/connections/{connectionid}/events | Low | No |\n| cap:caldiy:calendar.connection.event.get | GET /v2/calendars/connections/{connectionid}/events/{eventid} | Low | No |\n| cap:caldiy:calendar.connection.event.create | POST /v2/calendars/connections/{connectionid}/events | Medium | Required |\n| cap:caldiy:calendar.connection.event.update | PATCH /v2/calendars/connections/{connectionid}/events/{eventid} | Medium | Required |\n| cap:caldiy:calendar.connection.event.delete | DELETE /v2/calendars/connections/{connectionid}/events/{eventid} | High | Required |\n\nDirect Google events\n\n| Capability | Provider request | Risk | Approval |\n\n| cap:caldiy:calendar.event.list | GET /v2/calendars/{calendar}/events | Low | No |\n| cap:caldiy:calendar.event.get | GET /v2/calendars/{calendar}/events/{eventuid} | Low | No |\n| cap:caldiy:calendar.event.create | POST /v2/calendars/{calendar}/events | Medium | Required |\n| cap:caldiy:calendar.event.update | PATCH /v2/calendars/{calendar}/events/{eventuid} | Medium | Required |\n| cap:caldiy:calendar.event.delete | DELETE /v2/calendars/{calendar}/events/{eventuid} | High | Required |\n\nFor every direct operation in this table, calendar must equal google.\n\nRouting and conflict detection\n\n| Capability | Provider request | Risk | Approval |\n\n| cap:caldiy:calendar.disconnect | POST /v2/calendars/{calendar}/disconnect | High | Required |\n| cap:caldiy:calendar.destination.update | PUT /v2/destination-calendars | Medium | Required |\n| cap:caldiy:calendar.selected.add | POST /v2/selected-calendars | Medium | Required |\n| cap:caldiy:calendar.selected.delete | DELETE /v2/selected-calendars | High | Required |\n\nDisconnect, both event deletes, and selected-calendar delete are destructive.\n\nInspect calendar connections\n\nList connected calendars with an empty input:\n\n{}\n\nUse calendar.provider.check to test one provider connection:\n\n{\n  \"calendar\": \"google\"\n}\n\nThe exact provider enum is google, office365, or apple. Provider check\nshows whether the authenticated integration is connected and healthy at that\nrequest boundary. It does not prove that every calendar is visible or writable.\n\ncalendar.icsfeed.check also accepts an empty object and checks the\nauthenticated user's ICS feed connection. calendar.connection.list returns\ncredential-safe unified connection identifiers; the connector does not publish\ntheir provider-owned response fields.\n\nThese reads inspect connections that already exist. Provider connect and save\nroutes that establish browser OAuth sessions or credentials remain outside the\nagent-callable business-operation surface.\n\nRead busy time across selected calendars\n\ncalendar.busytime.list requires a time zone, a bounded range, and at least\none explicit calendar:\n\n{\n  \"timeZone\": \"UTC\",\n  \"dateFrom\": \"2026-08-03\",\n  \"dateTo\": \"2026-08-04T18:00:00Z\",\n  \"calendarsToLoad\": [\n    {\n      \"credentialId\": 42,\n      \"externalId\": \"primary@example.com\"\n    }\n  ]\n}\n\n| Field | Constraint |\n\n| timeZone | Non-empty string |\n| dateFrom, dateTo | ISO date or date-time string |\n| calendarsToLoad | Array with at least one object |\n| credentialId | Integer required in each object |\n| externalId | Non-empty string required in each object |\n\nThe client sends these values as query parameters. Nested calendar objects use\nbracket notation such as calendarsToLoad[0][credentialId]. The schema does not\nset a maximum or require unique calendar objects, and it does not assert that\ndateFrom precedes dateTo.\n\nBusy intervals are scheduling evidence, not a reservation or a guarantee that\na later booking will succeed.\n\nRead unified connection events\n\nList events for a unified connection with a required range:\n\n{\n  \"connectionid\": 73,\n  \"from\": \"2026-08-03T00:00:00Z\",\n  \"to\": \"2026-08-04T00:00:00Z\",\n  \"timeZone\": \"UTC\",\n  \"calendarId\": \"team@example.com\"\n}\n\nconnectionid, from, and to are required. from and to accept either\nan ISO date or date-time. Optional timeZone and calendarId must be non-empty.\nThe connection id is a path segment; all range fields and calendarId become\nquery parameters.\n\nRead one event with connectionid and a non-empty eventid. An optional\ncalendarId selects a calendar within that connection and is sent as a query\nparameter:\n\n{\n  \"connectionid\": 73,\n  \"eventid\": \"provider-event-uid\",\n  \"calendarId\": \"team@example.com\"\n}\n\nCreate a unified connection event\n\nCreate requires the connection, title, start, and end:\n\n{\n  \"connectionid\": 73,\n  \"calendarId\": \"team@example.com\",\n  \"title\": \"Architecture review\",\n  \"start\": {\n    \"time\": \"2026-08-03T10:00:00Z\",\n    \"timeZone\": \"UTC\"\n  },\n  \"end\": {\n    \"time\": \"2026-08-03T10:30:00Z\",\n    \"timeZone\": \"UTC\"\n  },\n  \"description\": \"Review the proposed integration boundary.\",\n  \"attendees\": [\n    {\n      \"email\": \"avery@example.com\",\n      \"name\": \"Avery Patel\"\n    }\n  ]\n}\n\ntitle must be non-empty. Both start and end require a date-time time\nand a non-empty timeZone. description may be a string or null.\nattendees may contain objects with a required email and optional name;\nthe array has no published minimum or uniqueness constraint. Additional fields\nare rejected at the event, date-time, and attendee object boundaries.\n\ncalendarId is optional. The client puts it in the query while sending event\ndetails as JSON. The connector contract carries write and externalnetwork,\nbut not sendmessage. Attendee input is therefore not evidence that the\nprovider sent an invitation.\n\nThe create operation names calendar.connection.event.delete as best-effort\ncompensation. Compensation requires a separate approval and is not automatic.\n\nUpdate or delete a unified connection event\n\nUpdate requires connectionid and eventid. It may include calendarId\nand any of these body fields:\n\n| Field | Constraint |\n\n| title | String; an empty string passes the connector schema |\n| start, end | Object with optional date-time time and string timeZone |\n| description | String or null |\n| attendees | Array or null |\n| status | accepted, pending, declined, cancelled, or null |\n\nAn attendee update requires email and may include name, self, optional,\nhost, and responseStatus. Response status accepts accepted, pending,\ndeclined, needsAction, or null.\n\nThe schema allows an identifier-only update and empty start or end objects.\nSuch input can pass local validation without expressing a useful provider\nchange. Include only an intentional, provider-supported patch.\n\nDelete uses connectionid and eventid, plus optional calendarId. The two\nids become path segments and calendarId becomes a query parameter. Delete is\nhigh risk and has no rollback contract.\n\nBoth update and delete first read the exact unified event. A successful\npreflight proves only that the event was readable before mutation.\n\nRead, create, or change a direct Google event\n\nThe direct surface mirrors the unified event model with these selector changes:\n• every input requires \"calendar\": \"google\";\n• list also requires from and to, with optional timeZone and calendarId;\n• get and delete require a non-empty eventuid;\n• create has no calendarId field;\n• update requires eventuid and uses the shared optional patch fields.\n\nCreate and update use the field constraints described for unified events.\nDirect create names calendar.event.delete as best-effort compensation.\nDirect update and delete first run calendar.event.get for the same\ncalendar and eventuid.\n\nThe direct schema does not publish Office 365 or Apple event variants. Use a\nunified connection when its returned identifier is the intended routing model.\n\nRead free/busy\n\nUnified free/busy requires connectionid, from, and to. Direct free/busy\nrequires calendar fixed to google, from, and to. Both accept optional\nnon-empty timeZone; all range fields are query parameters.\n\n{\n  \"connectionid\": 73,\n  \"from\": \"2026-08-03\",\n  \"to\": \"2026-08-04\",\n  \"timeZone\": \"UTC\"\n}\n\nThe input schema accepts dates or date-times and does not enforce ordering or a\nmaximum interval. A free/busy response is provider-owned and restricted. It\ndoes not by itself apply schedule rules, event-type buffers, reservations, or\nbooking policy.\n\nChange the destination calendar\n\ncalendar.destination.update changes where new bookings are written:\n\n{\n  \"integration\": \"googlecalendar\",\n  \"externalId\": \"primary@example.com\"\n}\n\nintegration must be applecalendar, googlecalendar, or\noffice365calendar. externalId is required and non-empty. An optional\nnon-empty delegationCredentialId selects delegated provider credentials.\n\nThe connector first lists calendars. This does not prove that the requested\nexternalId appeared in that result; provider validation still decides the\nmutation. The operation is medium risk and has no compensating capability.\n\nAdd or remove a conflict calendar\n\nAdd requires an integration name, external calendar id, and integer credential\nid:\n\n{\n  \"integration\": \"googlecalendar\",\n  \"externalId\": \"team@example.com\",\n  \"credentialId\": 42\n}\n\nAll three fields are required. integration and externalId must be\nnon-empty; credentialId is an integer. Optional delegationCredentialId must\nbe non-empty. The schema does not constrain integration to the destination\ncalendar enum.\n\nRemove uses the same route but requires credentialId as a non-empty string,\nnot an integer. The client sends integration, externalId, credentialId,\nand optional delegationCredentialId as query parameters on DELETE.\n\nBoth operations first list calendars. Add declares\ncalendar.selected.delete as best-effort compensation. Remove is destructive\nand has no further rollback.\n\nDisconnect a provider credential\n\nDisconnect requires a provider and integer credential id:\n\n{\n  \"calendar\": \"office365\",\n  \"id\": 42\n}\n\ncalendar must be google, office365, or apple. The client sends a POST\nto the provider-specific disconnect path and leaves id in the JSON body.\nBefore dispatch, it checks that provider connection without using the id.\n\nInteger identifiers in this family have no additional minimum in the published\nschema. Cal.diy can still reject a value outside the provider's domain.\n\nThe operation is high risk and destructive. It deletes an owned credential and\ninvalidates the connection. No compensation or automatic credential recovery\nis declared.\n\nPreflight reads\n\n| Mutation | Connector preflight |\n\n| Unified or direct event create | calendar.list |\n| Destination update | calendar.list |\n| Selected-calendar add or delete | calendar.list |\n| Unified event update or delete | Exact unified event get |\n| Direct event update or delete | Exact direct event get |\n| Calendar disconnect | Provider check for calendar |\n\nA preflight is a point-in-time read, not a lock, plan, reservation, or approval.\n\nShared read contract\n\nAll 11 reads are low risk, synchronous, retry-safe, and eligible for connector\nretry. Their idempotency key is optional, action-scoped, and collision-checked\nagainst the input hash. No capability-level approval is required.\n\nIdentity, tenant, external-account, credential-scope, and restricted-data\npolicy still apply to every read.\n\nShared mutation contract\n\nAll ten mutations require:\n\n| Contract field | Value |\n\n| Human approval | Required |\n| Approval selector | Tenant policy |\n| Approval validity window | 900,000 ms |\n| Approval evidence | Reason, input snapshot, policy decision |\n| Idempotency | Required; external-account scope |\n| Collision behavior | Revalidate input hash |\n| Connector retry | Not supported |\n| Retry safety | Unsafe |\n| Execution | Synchronous only |\n| Transaction modes | dryrun, plan, commit, reconcile |\n| Plan before commit | Not required |\n| Dry-run fidelity | Policy and schema only |\n\nSix non-destructive mutations are medium risk. Calendar disconnect, both event\ndeletes, and selected-calendar delete are high risk and destructive. None of\nthe ten mutations carries the sendmessage side-effect marker.\n\nEvent create on either surface and selected-calendar add each declare one\nbest-effort delete compensation. All other mutations declare rollback as not\nsupported. Never interpret a compensation declaration as automatic execution\nor restoration of invitations, notifications, external state, or history.\n\nResult and redaction contract\n\nThe connector returns the validated provider JSON body as AIP output. A\nnon-empty string status is required. data, pagination, and additional\nprovider fields are optional; their shapes are not guaranteed by the connector\nschema.\n\nThe connector recursively replaces secret-, key-, and token-named values with\n[REDACTED]. This targeted redaction does not remove event titles, attendee\naddresses, calendar ids, busy intervals, descriptions, or other PII unless a\nfield name matches the secret redactor. Keep the complete output restricted.\n\nFailures and recovery\n\n| Failure | Relevant cause | Safe response |\n\n| connector.caldiy.invalidaction | Missing selector, invalid range shape, invalid enum, or unknown field | Correct the exact input before another call |\n| connector.caldiy.policy | Mutation omitted its required idempotency key | Preserve approved input and add the missing key |\n| connector.caldiy.idempotencycollision | One key owns different mutation input | Stop and inspect the original action |\n| connector.caldiy.inflight | A matching calendar mutation is executing | Read durable state and wait |\n| connector.caldiy.outcomeunknown | Prior provider dispatch cannot be proved | Reconcile; do not create a replacement key |\n| connector.caldiy.authentication | Provider returned 401 or 403 | Verify account binding, credential revision, and calendar ownership |\n| connector.caldiy.stateconflict | Provider returned 409 or 412 | Refresh event or calendar state and review the mutation |\n| connector.caldiy.ratelimited | Provider returned 429 | Retry reads within policy; reconcile mutations |\n| connector.caldiy.remotetemporary | Provider returned a server error | Retry reads within policy; reconcile mutations |\n| connector.caldiy.transport | Provider exchange failed | Retry reads only; treat dispatched mutations as uncertain |\n| connector.caldiy.invalidprovideroutput | Successful response lacked valid status | Preserve evidence and reconcile a mutation |\n\nPermanent rejection, invalid credentials, oversized responses, and durable\nsettlement failures use the common Cal.diy error family. Gateway and lifecycle\nerrors are described in the global error reference (../../../reference/errors.md).\n\nVerify calendar handling\n\nFor a read, retain the capability, exact selector and range, actor, tenant,\nexternal account, provider status, and provider request id when present. Verify\nthat the result remains inside its approved restricted-data destination.\n\nFor a mutation, also retain approval evidence and the original idempotency key.\nWait for a completed durable result, then read the exact event or list calendar\nstate. For an uncertain dispatch, reconcile the original operation before any\nnew action.\n\nThese checks validate application handling. They do not prove invitation\ndelivery, provider-wide visibility, live availability, cross-provider\nqualification, or production readiness.\n\nRelated documentation\n• Cal.diy capability index (README.md)\n• Core booking capabilities (bookings.md)\n• Authentication and tenant routing (../getting-started/authentication-and-tenant-routing.md)\n• Transactions and compensation (../../../concepts/transactions-and-compensation.md)\n• Error reference (../../../reference/errors.md)\n"
  },
  "integrity": {
    "algorithm": "sha256",
    "sourceDigest": "c199034e1e2c5032332d2d9e917681a676c8c983282bdff20ec10b103096f9b9"
  }
}
