{"openapi":"3.1.0","info":{"title":"CalendarPipe API","version":"1","description":"REST API for calendar events and hosted calendar management. Authenticate via `Authorization: Bearer <key>` header, with a key scoped to the operations you need.\n\n## Versioning and deprecation\n\nThis is v1, versioned in the path. New endpoints, new optional request fields, new response fields and new error `code` values are additive and can arrive at any time — parse leniently and ignore what you do not recognise. Removing or renaming anything, tightening validation, or changing what a status means is breaking, and gets 12 months' notice.\n\nBefore removal, an operation is marked `deprecated: true` here and its responses carry [`Deprecation`](https://www.rfc-editor.org/rfc/rfc9745) and [`Sunset`](https://www.rfc-editor.org/rfc/rfc8594) headers dating the change. Full policy: https://docs.calendarpipe.com/developers/versioning","license":{"name":"Proprietary","url":"https://www.calendarpipe.com/terms-of-service"},"x-deprecation-policy":{"noticePeriodMonths":12,"signals":["Deprecation","Sunset"],"documentation":"https://docs.calendarpipe.com/developers/versioning"}},"servers":[{"url":"/","description":"CalendarPipe"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key from the CalendarPipe dashboard (Authorization: Bearer <key>).\n\nEach key carries a set of scopes chosen when it is created, and every\noperation lists the scope it needs under `security`. A key that\nauthenticates but lacks the scope gets 403 with\n`code = \"insufficient_scope\"`, `details.required_scope` naming the scope\nthat would have worked, and a matching `WWW-Authenticate: Bearer` header.\n\n- `calendars:read` — List connected calendar accounts and their calendars.\n- `events:read` — Read events from any connected calendar.\n- `events:write` — Create, update and delete events, and send or cancel invitations.\n- `hosted-calendars:read` — Read hosted calendars and their settings.\n- `hosted-calendars:write` — Create, update and delete hosted calendars, rotate their tokens, and manage webhooks.\n- `invitations:read` — List invitations a hosted calendar has received.\n- `invitations:write` — Respond to an invitation. Accepting one adds its event to the calendar.\n- `sync-rules:read` — Read sync rules and dry-run a gate function against them.\n- `sync-rules:write` — Create, update, delete and force a re-sync of sync rules.","x-scopes":{"calendars:read":"List connected calendar accounts and their calendars.","events:read":"Read events from any connected calendar.","events:write":"Create, update and delete events, and send or cancel invitations.","hosted-calendars:read":"Read hosted calendars and their settings.","hosted-calendars:write":"Create, update and delete hosted calendars, rotate their tokens, and manage webhooks.","invitations:read":"List invitations a hosted calendar has received.","invitations:write":"Respond to an invitation. Accepting one adds its event to the calendar.","sync-rules:read":"Read sync rules and dry-run a gate function against them.","sync-rules:write":"Create, update, delete and force a re-sync of sync rules."}}},"schemas":{"ApiError":{"type":"object","properties":{"error":{"type":"string","description":"Short label matching the HTTP status. Human-facing; do not branch on it.","example":"Forbidden"},"code":{"type":"string","enum":["validation_failed","bad_request","unauthorized","api_access_blocked","insufficient_scope","not_found","calendar_not_writable","calendar_not_found","conflict","unprocessable_entity","rate_limited","internal_error","upstream_error"],"description":"Stable machine-readable cause. This is the field to switch on — `error` and `message` are both free to change.\n\n- `validation_failed` — A field failed validation. `details` maps each rejected field to its reasons; whole-object problems arrive under `_errors`.\n- `bad_request` — The request was understood but cannot be carried out as written — often a calendar reference that names an account without naming the calendar.\n- `unauthorized` — Missing, invalid, or revoked API key.\n- `api_access_blocked` — The account is on the Free plan, which has no API access.\n- `insufficient_scope` — The key authenticated but was not granted a scope this operation needs. `details.required_scope` names the one that would have worked.\n- `not_found` — No such resource, or it is not owned by the authenticated account. The two are deliberately indistinguishable.\n- `calendar_not_writable` — The connected calendar account can no longer write to the target calendar — access was revoked, or the calendar is read-only. Distinct from `insufficient_scope`, which is about the API key rather than the calendar connection. Retrying will not help; reconnect the account or grant it write access.\n- `calendar_not_found` — The target calendar no longer exists on the provider. Distinct from `not_found`, which is about the resource the request named. Retrying will not help; point the request at a calendar that still exists.\n- `conflict` — The request conflicts with the current state of the resource.\n- `unprocessable_entity` — The request was well-formed but a resource it references cannot satisfy it.\n- `rate_limited` — Too many requests. Back off and retry.\n- `internal_error` — Something failed inside CalendarPipe. Safe to retry.\n- `upstream_error` — A calendar provider (Google, Microsoft, Apple) rejected the request or was unreachable. Usually transient.","example":"insufficient_scope"},"message":{"type":"string","description":"Human-readable description of what went wrong.","example":"This API key is missing the \"events:write\" scope."},"details":{"type":"object","additionalProperties":{},"description":"Context for this particular failure — the rejected fields on a validation error, `required_scope` on a scope error, a `documentation` link where one helps."}},"required":["error","code","message"]},"PaginationMeta":{"type":"object","properties":{"offset":{"type":"number","example":0},"limit":{"type":"number","example":50},"total":{"type":"number","example":100,"description":"Number of events fetched for this response before offset/limit slicing. When `truncated` is true this is a lower bound — more events exist beyond the per-calendar snapshot cap (2500)."},"has_more":{"type":"boolean","example":false,"description":"True when another offset page is available within the fetched set, or when the provider snapshot was truncated at the hard cap (so the last page is not necessarily complete)."},"truncated":{"type":"boolean","example":false,"description":"True when at least one calendar snapshot hit the 2500-event hard cap with more provider events remaining. Narrow `start`/`end` to read further."}},"required":["offset","limit","total","has_more","truncated"]},"CalendarItem":{"type":"object","properties":{"id":{"type":"string","description":"Composite calendar ID. Format: `<accountUUID>:<providerCalendarId>` for external calendars, `hosted:<uuid>` for hosted calendars, `ics:<uuid>` for ICS feed connections. Pass this value as `source` or `target` when creating a sync rule.","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890:user@example.com"},"name":{"type":"string","example":"Work Calendar"}},"required":["id","name"]},"CalendarAccount":{"type":"object","properties":{"id":{"type":"string","example":"abc-uuid-123"},"provider":{"type":"string","enum":["google","microsoft","apple","ics","calendarpipe"]},"email":{"type":["string","null"],"example":"user@example.com"},"enabled":{"type":"boolean"},"createdAt":{"type":["string","null"],"example":"2026-01-01T00:00:00.000Z"},"calendars":{"type":["array","null"],"items":{"$ref":"#/components/schemas/CalendarItem"},"description":"List of calendars within this account. Null if calendars could not be fetched."}},"required":["id","provider","email","enabled","createdAt","calendars"]},"ApiEvent":{"type":"object","properties":{"id":{"type":"string","description":"Stable CalendarPipe event ID. This is the handle to pass to PATCH and DELETE — `providerEventId` is informational.","example":"evt_Z29vZ2xlOmFiYzEyMw"},"calendarId":{"type":"string","description":"Composite calendar ID. Format: `<accountUUID>:<providerCalendarId>` for external calendars, `hosted:<uuid>` for hosted calendars, `ics:<uuid>` for ICS feeds. When reading, a bare account UUID means the account default calendar, which Google and Microsoft have and Apple does not; writes always require the calendar to be named.","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890:user@example.com"},"provider":{"type":"string","enum":["google","microsoft","apple","ics","calendarpipe"]},"providerEventId":{"type":"string","description":"The event's ID on the provider. For Apple this is the iCalendar UID, which identifies the event but does not locate it — use `id` to address it."},"title":{"type":["string","null"]},"description":{"type":["string","null"]},"location":{"type":["string","null"]},"start":{},"end":{},"isAllDay":{"type":"boolean"},"status":{"type":["string","null"]},"responseStatus":{"type":["string","null"],"enum":["accepted","declined","tentative","needsAction","organizer",null],"description":"The calendar owner's RSVP to the event, or null when the owner is not an attendee.","example":"accepted"},"visibility":{"type":["string","null"]},"showAs":{"type":["string","null"],"enum":["free","busy",null],"description":"Whether the event shows the calendar owner as free or busy. Null when the source calendar does not expose free/busy status.","example":"busy"},"attendees":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string","format":"email"},"displayName":{"type":"string"},"status":{"type":"string","enum":["NEEDS-ACTION","ACCEPTED","DECLINED","TENTATIVE"],"description":"This attendee's RSVP (iCalendar PARTSTAT), when the source calendar exposes it. Omitted when unknown. For the calendar owner's own response, see the event-level `responseStatus`.","example":"ACCEPTED"}},"required":["email"]}},"organizer":{},"recurringEventId":{"type":["string","null"],"description":"The series id when this event is one occurrence of a recurring series; null for one-off events. All providers return recurring events as individual dated occurrences."},"durationMinutes":{"type":["number","null"]},"dayOfWeek":{"type":["number","null"],"description":"0 = Sunday, 6 = Saturday, evaluated in UTC","example":1},"hour":{"type":["number","null"],"description":"Hour of day the event starts (0-23), in UTC","example":9},"isWeekday":{"type":["boolean","null"],"description":"Whether the event starts on a weekday (Monday-Friday), evaluated in UTC"},"attendeeCount":{"type":"number","example":3},"updatedAt":{"type":["string","null"]}},"required":["id","calendarId","provider","providerEventId","title","description","location","isAllDay","status","responseStatus","visibility","showAs","attendees","recurringEventId","durationMinutes","dayOfWeek","hour","isWeekday","attendeeCount","updatedAt"]},"CreateEvent":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":500,"example":"Team meeting"},"description":{"type":["string","null"],"maxLength":5000,"example":"Discuss roadmap"},"location":{"type":["string","null"],"maxLength":500,"example":"Conference room"},"start":{"type":"object","properties":{"dateTime":{"type":"string","format":"date-time","example":"2026-03-10T10:00:00Z"},"timeZone":{"type":"string","example":"America/New_York"}},"required":["dateTime"]},"end":{"type":"object","properties":{"dateTime":{"type":"string","format":"date-time","example":"2026-03-10T10:00:00Z"},"timeZone":{"type":"string","example":"America/New_York"}},"required":["dateTime"]},"isAllDay":{"type":"boolean","example":false},"visibility":{"type":"string","enum":["public","private","default"]},"status":{"type":"string","enum":["confirmed","tentative","cancelled"]},"attendees":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string","format":"email","example":"alice@example.com"}},"required":["email"]},"maxItems":500,"description":"Attendees to invite (maximum 500 per event)."}},"required":["title","start","end"]},"UpdateEvent":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":500,"example":"Updated meeting title"},"description":{"type":["string","null"],"maxLength":5000,"example":"Updated description"},"location":{"type":["string","null"],"maxLength":500,"example":"New room"},"start":{"type":"object","properties":{"dateTime":{"type":"string","format":"date-time","example":"2026-03-10T10:00:00Z"},"timeZone":{"type":"string","example":"America/New_York"}},"required":["dateTime"]},"end":{"type":"object","properties":{"dateTime":{"type":"string","format":"date-time","example":"2026-03-10T10:00:00Z"},"timeZone":{"type":"string","example":"America/New_York"}},"required":["dateTime"]},"isAllDay":{"type":"boolean","description":"On Google and Microsoft events, send this together with `start` and `end` — moving between all-day and timed needs the new times, and those are not read back first."},"visibility":{"type":"string","enum":["public","private","default"]},"status":{"type":"string","enum":["confirmed","tentative","cancelled"],"description":"Not supported on Microsoft events, which expose no writable status — those reject this field with 400. Use DELETE to remove the event."},"attendees":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string","format":"email","example":"bob@example.com"}},"required":["email"]},"description":"Attendees to invite. Hosted and Apple calendars only — Google and Microsoft events reject this field with 400, so edit their attendees in your calendar app. The maximum of 500 per event applies to adding attendees; an event already above it can still be edited and cancelled."}}},"InvitationSendResult":{"type":"object","properties":{"sent":{"type":"number","description":"Number of invitations sent","example":3},"skipped":{"type":"number","description":"Number of attendees skipped (already responded or invalid)","example":1}},"required":["sent","skipped"]},"InvitationItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},"event_uid":{"type":"string","example":"evt-uid-12345"},"organizer_email":{"type":"string","format":"email","example":"organizer@example.com"},"payload":{"description":"Full invitation payload (event details, respondUrl, etc.)"},"delivery_status":{"type":"string","description":"Current delivery status","example":"pending"},"created_at":{"type":"string","format":"date-time","example":"2026-01-01T00:00:00.000Z"}},"required":["id","event_uid","organizer_email","delivery_status","created_at"]},"InvitationRespond":{"type":"object","properties":{"status":{"type":"string","enum":["ACCEPTED","DECLINED","TENTATIVE"],"description":"RSVP status for the invitation","example":"ACCEPTED"}},"required":["status"]},"WebhookRegister":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"HTTPS webhook endpoint URL (must use https://)","example":"https://your-agent.example.com/webhook"},"secret":{"type":"string","description":"Webhook signing secret (must start with whsec_ prefix)","example":"whsec_your-secret-here"}},"required":["url","secret"]},"HostedCalendar":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},"name":{"type":"string","example":"My Work Calendar"},"description":{"type":["string","null"],"example":"Team meetings and deadlines"},"color":{"type":["string","null"],"example":"#3B82F6"},"timezone":{"type":"string","example":"America/New_York"},"feedToken":{"type":"string","format":"uuid","description":"Secret token for the .ics feed URL only. Distinct from calendarEmail."},"ctag":{"type":"integer","description":"Change tag — increments on every event mutation","example":1},"createdAt":{"type":"string","format":"date-time","example":"2026-01-01T00:00:00.000Z"},"updatedAt":{"type":"string","format":"date-time","example":"2026-01-01T00:00:00.000Z"},"organizerDisplayName":{"type":["string","null"],"description":"Display name used as the ORGANIZER in calendar invitations and shown to attendees. When this calendar is invited as an attendee, this name appears instead of the raw calendar address.","example":"Scheduling Bot"},"calendarEmail":{"type":"string","format":"email","description":"Stable routable email address for this calendar (cal-{emailToken}@in.calendarpipe.com). Used for invitation ORGANIZER / Reply-To and inbound RSVP routing. Independent of feedToken — regenerating the feed token does not change this address.","example":"cal-a1b2c3d4-e5f6-7890-abcd-ef1234567890@in.calendarpipe.com"}},"required":["id","name","description","color","timezone","feedToken","ctag","createdAt","updatedAt","organizerDisplayName","calendarEmail"]},"CreateHostedCalendar":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"My Work Calendar"},"description":{"type":["string","null"],"maxLength":500,"example":"Team meetings"},"color":{"type":["string","null"],"pattern":"^#[0-9A-Fa-f]{6}$","example":"#3B82F6"},"timezone":{"type":"string","default":"UTC","example":"America/New_York"},"organizerDisplayName":{"type":["string","null"],"maxLength":100,"description":"Display name used as the ORGANIZER in calendar invitations. Also shown as the attendee name when this calendar is invited to other events.","example":"Scheduling Bot"}},"required":["name"]},"UpdateHostedCalendar":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"My Work Calendar"},"description":{"type":["string","null"],"maxLength":500,"example":"Team meetings"},"color":{"type":["string","null"],"pattern":"^#[0-9A-Fa-f]{6}$","example":"#3B82F6"},"timezone":{"type":"string","example":"America/New_York"},"organizerDisplayName":{"type":["string","null"],"maxLength":100,"description":"Display name used as the ORGANIZER in calendar invitations. Also shown as the attendee name when this calendar is invited to other events.","example":"Scheduling Bot"}}},"CreatedExternalEvent":{"type":"object","properties":{"id":{"type":"string","description":"Pass this to PATCH and DELETE, or to GET the event back.","example":"evt_Z29vZ2xlOmFiYzEyMw"},"providerEventId":{"type":"string"},"calendarId":{"type":"string","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890:user@example.com"},"provider":{"type":"string","enum":["google","microsoft","apple"]}},"required":["id","providerEventId","calendarId","provider"]},"SyncRule":{"type":"object","properties":{"id":{"type":"string","example":"8f2c1a44-0000-4000-8000-000000000000"},"name":{"type":"string","example":"Work → Personal"},"source":{"type":"string","description":"Calendar reference, as returned by `GET /api/v1/calendars`. One of: `<accountUUID>:<providerCalendarId>` (Google/Microsoft/Apple), `hosted:<uuid>`, `ics:<uuid>` (source only), or `invitation:<email>` (target only, Pro plan)."},"sourceName":{"type":"string","example":"Work"},"target":{"type":"string","description":"Calendar reference, as returned by `GET /api/v1/calendars`. One of: `<accountUUID>:<providerCalendarId>` (Google/Microsoft/Apple), `hosted:<uuid>`, `ics:<uuid>` (source only), or `invitation:<email>` (target only, Pro plan)."},"targetName":{"type":"string","example":"Personal Feed"},"code":{"type":["string","null"],"description":"Gate function source. Null when the rule has no gate."},"codeSource":{"type":"string","enum":["code","visual","template"],"description":"How the gate was authored. Updating `code` via this API sets it to `code` and discards any visual-builder configuration."},"enabled":{"type":"boolean"},"eventColor":{"type":["string","null"],"enum":["tomato","flamingo","tangerine","banana","sage","basil","peacock","blueberry","lavender","grape","graphite",null],"description":"Default colour for events this rule writes. Null leaves them the destination calendar's own colour. A gate's `transform.color` overrides it per event."},"capabilities":{"type":"object","properties":{"outOfOffice":{"type":"boolean"},"autoDecline":{"type":"boolean"},"eventColor":{"type":"boolean"}},"required":["outOfOffice","autoDecline","eventColor"],"description":"What the target calendar supports. Setting `showAs: \"outOfOffice\"` from a gate has no effect on a target where `outOfOffice` is false."},"createdAt":{"type":["string","null"]},"updatedAt":{"type":["string","null"]},"sync":{"type":"object","properties":{"lastSyncedAt":{"type":["string","null"]},"syncedThroughAt":{"type":["string","null"]},"eventsSynced":{"type":"number"},"errorCount":{"type":"number"},"lastError":{"type":["string","null"]}},"required":["lastSyncedAt","syncedThroughAt","eventsSynced","errorCount","lastError"]}},"required":["id","name","source","sourceName","target","targetName","code","codeSource","enabled","eventColor","capabilities","createdAt","updatedAt","sync"]},"CreateSyncRule":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"example":"Work → Personal"},"source":{"type":"string","description":"Calendar reference, as returned by `GET /api/v1/calendars`. One of: `<accountUUID>:<providerCalendarId>` (Google/Microsoft/Apple), `hosted:<uuid>`, `ics:<uuid>` (source only), or `invitation:<email>` (target only, Pro plan)."},"target":{"type":"string","description":"Calendar reference, as returned by `GET /api/v1/calendars`. One of: `<accountUUID>:<providerCalendarId>` (Google/Microsoft/Apple), `hosted:<uuid>`, `ics:<uuid>` (source only), or `invitation:<email>` (target only, Pro plan)."},"code":{"type":["string","null"],"description":"Gate function source. Omit for a default pass-through gate; pass null for no gate."},"enabled":{"type":"boolean","description":"Defaults to false so a rule can be tested before it syncs."}},"required":["name","source","target"]},"UpdateSyncRule":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"code":{"type":["string","null"],"description":"Replaces the gate function and forces a full re-sync so every event is re-processed. Discards any visual-builder configuration."},"enabled":{"type":"boolean"},"eventColor":{"type":["string","null"],"enum":["tomato","flamingo","tangerine","banana","sage","basil","peacock","blueberry","lavender","grape","graphite",null],"description":"Default colour for the events this rule writes; null clears it. Forces a full re-sync so copies already on the calendar are recoloured. Honoured on Google targets only."}}},"DryRunRequest":{"type":"object","properties":{"code":{"type":"string","description":"Gate function source to test."},"ruleId":{"type":"string","format":"uuid","description":"Read events from this rule's source calendar."},"source":{"type":"string","description":"Read events from this calendar directly, so code can be tested before a rule exists. Calendar reference, as returned by `GET /api/v1/calendars`. One of: `<accountUUID>:<providerCalendarId>` (Google/Microsoft/Apple), `hosted:<uuid>`, `ics:<uuid>` (source only), or `invitation:<email>` (target only, Pro plan). Mutually exclusive with ruleId."},"limit":{"type":"integer","minimum":1,"maximum":20,"description":"Events to test against. Defaults to 5, maximum 20."}},"required":["code"]},"DryRunResponse":{"type":"object","properties":{"eventsSource":{"type":"string","enum":["real","sample"],"description":"Whether the run used real calendar events or built-in samples."},"fallbackReason":{"type":["string","null"],"enum":["no_source","no_events","fetch_failed",null],"description":"Why samples were used despite real events being requested. Null when none were requested, or when real events were used."},"results":{"type":"array","items":{"type":"object","properties":{"input":{"type":"object","additionalProperties":{},"description":"The event as the gate function sees it."},"output":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":["string","null"]},"location":{"type":["string","null"]},"visibility":{"type":["string","null"]},"showAs":{"type":["string","null"]},"startDateTime":{"type":"string"},"endDateTime":{"type":"string"}},"required":["title","description","location","visibility","showAs","startDateTime","endDateTime"]},"passed":{"type":"boolean"},"transformed":{"type":"boolean"},"executionTimeMs":{"type":"number"},"errors":{"type":["array","null"],"items":{"type":"string"}},"reason":{"type":["string","null"]}},"required":["input","output","passed","transformed","executionTimeMs","errors","reason"]}}},"required":["eventsSource","fallbackReason","results"]}},"parameters":{}},"paths":{"/api/v1/calendars":{"get":{"operationId":"listCalendars","summary":"List connected calendar accounts with their calendars","description":"Returns all calendar accounts connected to the authenticated user, including the list of calendars within each account. Each calendar has a composite ID (`<accountUUID>:<providerCalendarId>`) that can be used directly with other endpoints.","security":[{"bearerAuth":["calendars:read"]}],"responses":{"200":{"description":"List of connected calendar accounts with their calendars","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CalendarAccount"}}},"required":["data"]}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/calendars/{id}/events":{"get":{"operationId":"listCalendarEvents","summary":"List events for a calendar","description":"Returns paginated events for a specific calendar on any connected provider — Google, Microsoft, Apple/CalDAV, hosted, or an ICS feed. Use a composite ID (`<accountUUID>:<providerCalendarId>`) to target a specific sub-calendar, `hosted:<id>` for a hosted calendar, or `ics:<id>` for an ICS feed. A bare account UUID fetches the account default calendar; Apple accounts have no default calendar, so they require the composite form and reject a bare UUID with 400. External provider snapshots are capped at 2500 events per calendar for the requested window; when that cap is hit, `meta.truncated` is `true` and `meta.has_more` stays true on the last page — narrow `start`/`end` to continue.","security":[{"bearerAuth":["events:read"]}],"parameters":[{"schema":{"type":"string","description":"Composite calendar ID (`<accountUUID>:<calendarId>`), bare account UUID (the account default calendar; not valid for Apple), `hosted:<id>` for hosted calendars, or `ics:<id>` for ICS feeds","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890:user@example.com"},"required":true,"description":"Composite calendar ID (`<accountUUID>:<calendarId>`), bare account UUID (the account default calendar; not valid for Apple), `hosted:<id>` for hosted calendars, or `ics:<id>` for ICS feeds","name":"id","in":"path"},{"schema":{"type":["integer","null"],"minimum":0,"default":0,"description":"Pagination offset (number of records to skip)","example":0},"required":false,"description":"Pagination offset (number of records to skip)","name":"offset","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Number of records to return (1–100)","example":50},"required":false,"description":"Number of records to return (1–100)","name":"limit","in":"query"},{"schema":{"type":"string","format":"date-time","description":"ISO 8601 datetime (with offset) — filter events starting at or after this time","example":"2026-01-01T00:00:00Z"},"required":false,"description":"ISO 8601 datetime (with offset) — filter events starting at or after this time","name":"start","in":"query"},{"schema":{"type":"string","format":"date-time","description":"ISO 8601 datetime (with offset) — filter events starting before this time","example":"2026-12-31T23:59:59Z"},"required":false,"description":"ISO 8601 datetime (with offset) — filter events starting before this time","name":"end","in":"query"}],"responses":{"200":{"description":"Paginated list of events for the specified calendar","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ApiEvent"}},"meta":{"$ref":"#/components/schemas/PaginationMeta"}},"required":["data","meta"]}}}},"400":{"description":"Bad request — invalid query parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Calendar not found or not owned by the authenticated user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"post":{"operationId":"createEvent","summary":"Create an event on a calendar","description":"Creates a new event on a hosted or external calendar. Use a composite ID (`<accountUUID>:<providerCalendarId>`) to create on a specific sub-calendar. For hosted calendars, use `hosted:<id>`. A bare account UUID is rejected with 400 on every provider: a create must name the calendar it lands on. ICS feeds are read-only and reject creates with 400. Send an `Idempotency-Key` header to make retries safe on hosted calendars: a repeat returns the original event with status 200 and sends no second invitation.","security":[{"bearerAuth":["events:write"]}],"parameters":[{"schema":{"type":"string","description":"Composite calendar ID (`<accountUUID>:<calendarId>`) or `hosted:<id>` for hosted calendars. A bare account UUID is rejected — a create must name its calendar.","example":"hosted:a1b2c3d4-e5f6-7890-abcd-ef1234567890"},"required":true,"description":"Composite calendar ID (`<accountUUID>:<calendarId>`) or `hosted:<id>` for hosted calendars. A bare account UUID is rejected — a create must name its calendar.","name":"id","in":"path"},{"schema":{"type":"string","maxLength":255,"description":"Hosted calendars only. Repeat a create safely: the same key returns the event the first attempt made, with status 200. Scoped per calendar.","example":"meeting-4821"},"required":false,"description":"Hosted calendars only. Repeat a create safely: the same key returns the event the first attempt made, with status 200. Scoped per calendar.","name":"Idempotency-Key","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEvent"}}}},"responses":{"200":{"description":"Idempotency-Key replay — returns the event the first request created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ApiEvent"}},"required":["data"]}}}},"201":{"description":"Event created. Hosted calendars return the full event; Google, Microsoft, and Apple return an acknowledgement carrying the new event id.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"anyOf":[{"$ref":"#/components/schemas/ApiEvent"},{"$ref":"#/components/schemas/CreatedExternalEvent"}]}},"required":["data"]}}}},"400":{"description":"Bad request — validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Payment required — Pro plan required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Calendar not found or not owned by the authenticated user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"502":{"description":"Bad gateway — upstream provider error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/events":{"get":{"operationId":"listEvents","summary":"List events across all calendars","description":"Returns paginated events merged across the caller's calendars. By default that is the default calendar of each Google and Microsoft account, plus every hosted calendar and ICS feed. Apple accounts are not included by default because CalDAV has no default calendar — name the calendar in `calendarIds` to read one. Each external calendar snapshot is capped at 2500 events; if any source hits that cap, `meta.truncated` is `true`.","security":[{"bearerAuth":["events:read"]}],"parameters":[{"schema":{"type":["integer","null"],"minimum":0,"default":0,"description":"Pagination offset (number of records to skip)","example":0},"required":false,"description":"Pagination offset (number of records to skip)","name":"offset","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"Number of records to return (1–100)","example":50},"required":false,"description":"Number of records to return (1–100)","name":"limit","in":"query"},{"schema":{"type":"string","format":"date-time","description":"ISO 8601 datetime (with offset) — filter events starting at or after this time","example":"2026-01-01T00:00:00Z"},"required":false,"description":"ISO 8601 datetime (with offset) — filter events starting at or after this time","name":"start","in":"query"},{"schema":{"type":"string","format":"date-time","description":"ISO 8601 datetime (with offset) — filter events starting before this time","example":"2026-12-31T23:59:59Z"},"required":false,"description":"ISO 8601 datetime (with offset) — filter events starting before this time","name":"end","in":"query"},{"schema":{"type":"string","description":"Comma-separated list of composite calendar IDs to filter by — `<accountUUID>:<calendarId>`, `hosted:<id>`, or `ics:<id>`. Required to read an Apple calendar. When omitted, returns events from the default calendar of each Google/Microsoft account plus all hosted calendars and ICS feeds.","example":"a1b2c3d4:user@example.com,a1b2c3d4:family@group.calendar.google.com"},"required":false,"description":"Comma-separated list of composite calendar IDs to filter by — `<accountUUID>:<calendarId>`, `hosted:<id>`, or `ics:<id>`. Required to read an Apple calendar. When omitted, returns events from the default calendar of each Google/Microsoft account plus all hosted calendars and ICS feeds.","name":"calendarIds","in":"query"}],"responses":{"200":{"description":"Paginated list of events across all calendars","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ApiEvent"}},"meta":{"$ref":"#/components/schemas/PaginationMeta"}},"required":["data","meta"]}}}},"400":{"description":"Bad request — invalid query parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/events/{eventId}":{"patch":{"operationId":"updateEvent","summary":"Update an event","description":"Updates a hosted or external provider event. Only the fields present in the body change — an omitted field is left exactly as it stands, including fields this API does not expose, such as reminders, alarms and colour. Send an explicit `null` to clear `description` or `location`. For external events, provide `accountId` as a query parameter naming the calendar (`<accountUUID>:<calendarId>`).","security":[{"bearerAuth":["events:write"]}],"parameters":[{"schema":{"type":"string","description":"Synthetic event ID (evt_* format)","example":"evt_Z29vZ2xlOmFiYzEyMw"},"required":true,"description":"Synthetic event ID (evt_* format)","name":"eventId","in":"path"},{"schema":{"type":"string","description":"Required for external provider events. Must name the calendar in composite format (`<accountUUID>:<calendarId>`); a bare account UUID is rejected with 400, because it does not identify which calendar to write to. Apple events are addressed by the event ID itself, so a bare account UUID is accepted for them.","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890:user@example.com"},"required":false,"description":"Required for external provider events. Must name the calendar in composite format (`<accountUUID>:<calendarId>`); a bare account UUID is rejected with 400, because it does not identify which calendar to write to. Apple events are addressed by the event ID itself, so a bare account UUID is accepted for them.","name":"accountId","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEvent"}}}},"responses":{"200":{"description":"Event updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ApiEvent"}},"required":["data"]}}}},"400":{"description":"Bad request — validation failed, or accountId missing or naming an account without a calendar","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Payment required — Pro plan required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Event not found or not owned by the authenticated user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Apple calendars only — the event changed on the calendar between being read and this update. Fetch it again and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"502":{"description":"Bad gateway — upstream provider error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"delete":{"operationId":"deleteEvent","summary":"Delete an event","description":"Deletes a hosted or external provider event. For external events, provide `accountId` as a query parameter naming the calendar (`<accountUUID>:<calendarId>`). Returns 404 when the event is not on that calendar, so a 204 means an event was actually removed.","security":[{"bearerAuth":["events:write"]}],"parameters":[{"schema":{"type":"string","description":"Synthetic event ID (evt_* format)","example":"evt_Z29vZ2xlOmFiYzEyMw"},"required":true,"description":"Synthetic event ID (evt_* format)","name":"eventId","in":"path"},{"schema":{"type":"string","description":"Required for external provider events. Must name the calendar in composite format (`<accountUUID>:<calendarId>`); a bare account UUID is rejected with 400, because it does not identify which calendar to write to. Apple events are addressed by the event ID itself, so a bare account UUID is accepted for them.","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890:user@example.com"},"required":false,"description":"Required for external provider events. Must name the calendar in composite format (`<accountUUID>:<calendarId>`); a bare account UUID is rejected with 400, because it does not identify which calendar to write to. Apple events are addressed by the event ID itself, so a bare account UUID is accepted for them.","name":"accountId","in":"query"}],"responses":{"204":{"description":"Event deleted successfully"},"400":{"description":"Bad request — accountId missing, or naming an account without a calendar","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Payment required — Pro plan required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Event not found or not owned by the authenticated user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"502":{"description":"Bad gateway — upstream provider error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/hosted-calendars":{"post":{"operationId":"createHostedCalendar","summary":"Create a hosted calendar","description":"Creates a new hosted calendar owned by the authenticated user.","security":[{"bearerAuth":["hosted-calendars:write"]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateHostedCalendar"}}}},"responses":{"201":{"description":"Hosted calendar created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/HostedCalendar"}},"required":["data"]}}}},"400":{"description":"Bad request — validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Payment required — Pro plan required for API access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"get":{"operationId":"listHostedCalendars","summary":"List hosted calendars","description":"Returns all hosted calendars owned by the authenticated user.","security":[{"bearerAuth":["hosted-calendars:read"]}],"responses":{"200":{"description":"List of hosted calendars","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/HostedCalendar"}}},"required":["data"]}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Payment required — Pro plan required for API access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/hosted-calendars/{id}":{"get":{"operationId":"getHostedCalendar","summary":"Get a hosted calendar","description":"Returns a single hosted calendar owned by the authenticated user.","security":[{"bearerAuth":["hosted-calendars:read"]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Hosted calendar ID"},"required":true,"description":"Hosted calendar ID","name":"id","in":"path"}],"responses":{"200":{"description":"Hosted calendar","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/HostedCalendar"}},"required":["data"]}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Payment required — Pro plan required for API access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Hosted calendar not found or not owned by the authenticated user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"patch":{"operationId":"updateHostedCalendar","summary":"Update a hosted calendar","description":"Updates fields on a hosted calendar owned by the authenticated user.","security":[{"bearerAuth":["hosted-calendars:write"]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Hosted calendar ID"},"required":true,"description":"Hosted calendar ID","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateHostedCalendar"}}}},"responses":{"200":{"description":"Updated hosted calendar","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/HostedCalendar"}},"required":["data"]}}}},"400":{"description":"Bad request — validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Payment required — Pro plan required for API access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Hosted calendar not found or not owned by the authenticated user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"delete":{"operationId":"deleteHostedCalendar","summary":"Delete a hosted calendar","description":"Deletes a hosted calendar and all its events (cascades to hosted_events).","security":[{"bearerAuth":["hosted-calendars:write"]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Hosted calendar ID"},"required":true,"description":"Hosted calendar ID","name":"id","in":"path"}],"responses":{"204":{"description":"Hosted calendar deleted (or did not exist)"},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Payment required — Pro plan required for API access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/hosted-calendars/{id}/regenerate-token":{"post":{"operationId":"regenerateHostedCalendarToken","summary":"Regenerate feed token","description":"Generates a new UUID feed_token for the hosted calendar, invalidating the previous .ics feed URL. Does not change calendarEmail.","security":[{"bearerAuth":["hosted-calendars:write"]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Hosted calendar ID"},"required":true,"description":"Hosted calendar ID","name":"id","in":"path"}],"responses":{"200":{"description":"Hosted calendar with new feed token","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/HostedCalendar"}},"required":["data"]}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Payment required — Pro plan required for API access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Hosted calendar not found or not owned by the authenticated user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/calendars/{id}/events/{eventId}/invite":{"post":{"operationId":"sendEventInvitations","summary":"Send invitations to event attendees","description":"Sends METHOD:REQUEST invitations to all NEEDS-ACTION attendees. Idempotent — safe to call multiple times. Hosted calendars only (use `hosted:<calendarId>` as account ID).","security":[{"bearerAuth":["events:write"]}],"parameters":[{"schema":{"type":"string","description":"Calendar account ID — must use hosted:<calendarId> format"},"required":true,"description":"Calendar account ID — must use hosted:<calendarId> format","name":"id","in":"path"},{"schema":{"type":"string","description":"Synthetic event ID (evt_* format)"},"required":true,"description":"Synthetic event ID (evt_* format)","name":"eventId","in":"path"}],"responses":{"200":{"description":"Invitation send results","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/InvitationSendResult"}},"required":["data"]}}}},"400":{"description":"Bad request — invalid ID format, not a hosted calendar, or too many attendees","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Calendar or event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Too many requests — invitation email rate limit exceeded (500 per user per hour)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"502":{"description":"Bad gateway — email delivery failure","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/calendars/{id}/events/{eventId}/cancel":{"post":{"operationId":"cancelEventInvitations","summary":"Send cancellation to all attendees","description":"Sends METHOD:CANCEL to ALL attendees regardless of their response status. Increments the event sequence number. Hosted calendars only (use `hosted:<calendarId>` as account ID).","security":[{"bearerAuth":["events:write"]}],"parameters":[{"schema":{"type":"string","description":"Calendar account ID — must use hosted:<calendarId> format"},"required":true,"description":"Calendar account ID — must use hosted:<calendarId> format","name":"id","in":"path"},{"schema":{"type":"string","description":"Synthetic event ID (evt_* format)"},"required":true,"description":"Synthetic event ID (evt_* format)","name":"eventId","in":"path"}],"responses":{"200":{"description":"Cancellation send results","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/InvitationSendResult"}},"required":["data"]}}}},"400":{"description":"Bad request — invalid ID format, not a hosted calendar, or too many attendees","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Calendar or event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Too many requests — invitation email rate limit exceeded (500 per user per hour)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"502":{"description":"Bad gateway — email delivery failure","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/hosted-calendars/{id}/invitations":{"get":{"operationId":"listInvitations","summary":"List inbound invitations","description":"Returns invitations received by this hosted calendar, ordered oldest-first. Use the optional `status` query parameter to filter by delivery status (e.g. `pending`, `delivered`).","security":[{"bearerAuth":["invitations:read"]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Hosted calendar ID"},"required":true,"description":"Hosted calendar ID","name":"id","in":"path"},{"schema":{"type":"string","description":"Filter by delivery_status (e.g. pending, delivered)","example":"pending"},"required":false,"description":"Filter by delivery_status (e.g. pending, delivered)","name":"status","in":"query"}],"responses":{"200":{"description":"List of invitations","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/InvitationItem"}}},"required":["data"]}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Payment required — Pro plan required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Hosted calendar not found or not owned by the authenticated user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/hosted-calendars/{id}/invitations/{uid}/respond":{"post":{"operationId":"respondToInvitation","summary":"Respond to an invitation (RSVP)","description":"Accepts, declines, or tentatively accepts an invitation. Updates the attendee partstat on the organizer's event (for internal invitations) and sends a METHOD:REPLY ICS email to the organizer. Identical repeated responses are idempotent (no second email). New responses are rate-limited per user.","security":[{"bearerAuth":["invitations:write"]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Hosted calendar ID"},"required":true,"description":"Hosted calendar ID","name":"id","in":"path"},{"schema":{"type":"string","description":"Event UID from the invitation"},"required":true,"description":"Event UID from the invitation","name":"uid","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationRespond"}}}},"responses":{"200":{"description":"RSVP response recorded (or returned idempotently when unchanged)","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"status":{"type":"string"},"idempotent":{"type":"boolean"},"hostedEventId":{"type":"string","format":"uuid"}},"required":["status"]}},"required":["data"]}}}},"400":{"description":"Bad request — invalid JSON or validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Calendar, invitation, or event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Too many replies — maximum 500 replies per user per hour","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/hosted-calendars/{id}/webhook":{"put":{"operationId":"registerWebhook","summary":"Register a webhook for invitation delivery","description":"Registers an HTTPS webhook endpoint to receive real-time invitation delivery. Overwrites any previously registered webhook. Payloads use standard-webhooks HMAC signature verification (headers: webhook-id, webhook-timestamp, webhook-signature).","security":[{"bearerAuth":["hosted-calendars:write"]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Hosted calendar ID"},"required":true,"description":"Hosted calendar ID","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookRegister"}}}},"responses":{"200":{"description":"Webhook registered successfully","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"url":{"type":"string","example":"https://your-agent.example.com/webhook"},"registeredAt":{"type":"string","format":"date-time","example":"2026-01-01T00:00:00.000Z"}},"required":["url","registeredAt"]}},"required":["data"]}}}},"400":{"description":"Bad request — validation failed (url must be https, secret must start with whsec_)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Hosted calendar not found or not owned by the authenticated user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"delete":{"operationId":"removeWebhook","summary":"Remove webhook registration","description":"Removes the webhook endpoint for this hosted calendar. Invitation delivery falls back to polling via GET /api/v1/hosted-calendars/{id}/invitations.","security":[{"bearerAuth":["hosted-calendars:write"]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Hosted calendar ID"},"required":true,"description":"Hosted calendar ID","name":"id","in":"path"}],"responses":{"204":{"description":"Webhook removed successfully"},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Hosted calendar not found or not owned by the authenticated user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/sync-rules":{"get":{"operationId":"listSyncRules","summary":"List sync rules","description":"Returns every sync rule owned by the authenticated user, newest first.","security":[{"bearerAuth":["sync-rules:read"]}],"responses":{"200":{"description":"List of sync rules","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SyncRule"}}},"required":["data"]}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Payment required — Pro plan required for API access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"post":{"operationId":"createSyncRule","summary":"Create a sync rule","description":"Creates a sync rule from one calendar to another. Source and target are addressed with the ids from `GET /api/v1/calendars` and cannot be changed afterwards — delete and recreate to re-point a rule.","security":[{"bearerAuth":["sync-rules:write"]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSyncRule"}}}},"responses":{"201":{"description":"Sync rule created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SyncRule"}},"required":["data"]}}}},"400":{"description":"Bad request — validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Payment required — Pro plan required for API access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Source or target calendar not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Gate function failed validation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/sync-rules/dry-run":{"post":{"operationId":"dryRunSyncRule","summary":"Test gate code against events","description":"Runs gate code against real events from a calendar (or built-in samples) and returns what each event would become. Nothing is written. Pass `source` to test code before creating a rule, or `ruleId` to test against an existing one.","security":[{"bearerAuth":["sync-rules:read","events:read"]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DryRunRequest"}}}},"responses":{"200":{"description":"Dry-run results","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/DryRunResponse"}},"required":["data"]}}}},"400":{"description":"Bad request — validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Payment required — Pro plan required for API access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Rule or calendar not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/sync-rules/{id}":{"get":{"operationId":"getSyncRule","summary":"Get a sync rule","security":[{"bearerAuth":["sync-rules:read"]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Sync rule ID"},"required":true,"description":"Sync rule ID","name":"id","in":"path"}],"responses":{"200":{"description":"Sync rule","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SyncRule"}},"required":["data"]}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Payment required — Pro plan required for API access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Sync rule not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"patch":{"operationId":"updateSyncRule","summary":"Update a sync rule","description":"Updates the name, gate code, or enabled state. Source and target are immutable. Changing `code` forces a full re-sync so every event is re-processed with the new gate.","security":[{"bearerAuth":["sync-rules:write"]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Sync rule ID"},"required":true,"description":"Sync rule ID","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSyncRule"}}}},"responses":{"200":{"description":"Updated sync rule","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SyncRule"}},"required":["data"]}}}},"400":{"description":"Bad request — validation failed or no fields given","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Payment required — Pro plan required for API access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Sync rule not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Gate function failed validation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"delete":{"operationId":"deleteSyncRule","summary":"Delete a sync rule","description":"Deletes the rule. For invitation-delivery rules, cancellations are sent to the invitee first. Idempotent.","security":[{"bearerAuth":["sync-rules:write"]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Sync rule ID"},"required":true,"description":"Sync rule ID","name":"id","in":"path"}],"responses":{"204":{"description":"Sync rule deleted"},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Payment required — Pro plan required for API access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/sync-rules/{id}/resync":{"post":{"operationId":"resyncSyncRule","summary":"Force a full re-sync","description":"Clears the rule's sync cursors so the next scheduled run re-fetches and re-processes every event. Returns 202 — the re-sync itself happens on that next run, not during this request.","security":[{"bearerAuth":["sync-rules:write"]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Sync rule ID"},"required":true,"description":"Sync rule ID","name":"id","in":"path"}],"responses":{"202":{"description":"Re-sync requested","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"resyncRequested":{"type":"boolean"}},"required":["id","resyncRequested"]}},"required":["data"]}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Payment required — Pro plan required for API access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Forbidden — the API key is missing a scope this operation requires. `details.required_scope` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Sync rule not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}}},"webhooks":{}}