Skip to content

Calendar

Knobs Calendar supports multiple calendars per workspace, single and recurring events, attendees with RSVPs, free/busy lookups, external ICS subscriptions, and a token-gated published ICS feed.

All routes are workspace-scoped and accept a session JWT or API key, except the public feed at the end. Calendars use the per-object ACL model (owner + grants + workspaceAccess) with one extra rung: freeBusy, which reveals busy intervals only. Event attendees can always read and RSVP to their own events regardless of calendar access.

Endpoint Description
GET /v1/workspaces/{wid}/calendar/calendars List calendars you can see (ensures your default personal calendar exists)
POST /v1/workspaces/{wid}/calendar/calendars Create {name, color?, timeZone?}
GET /v1/workspaces/{wid}/calendar/calendars/{cid} Calendar metadata
PATCH /v1/workspaces/{wid}/calendar/calendars/{cid} Rename / recolor / retimezone
DELETE /v1/workspaces/{wid}/calendar/calendars/{cid} Delete (the default calendar can’t be deleted)
GET /v1/workspaces/{wid}/calendar/calendars/{cid}/access Sharing panel (incl. the freeBusy level)
PUT /v1/workspaces/{wid}/calendar/calendars/{cid}/access Replace {workspaceAccess, grants}

Render views from instances — the server expands recurrences and applies ACL/privacy filtering:

Endpoint Description
GET /v1/workspaces/{wid}/calendar/instances Expanded instances; ?from=&to= (RFC 3339, required) and ?calendarIds= (comma-separated)
POST /v1/workspaces/{wid}/calendar/events Create an event (single or recurring)
GET /v1/workspaces/{wid}/calendar/events/{eid} The event document (series master, single, or override)
PATCH /v1/workspaces/{wid}/calendar/events/{eid} Edit; on a series add ?mode=single|following|all&originalStart=
DELETE /v1/workspaces/{wid}/calendar/events/{eid} Delete/cancel with the same ?mode=&originalStart= scoping
POST /v1/workspaces/{wid}/calendar/events/{eid}/rsvp {response, originalStart?} — attendee self-service (accepted/declined/tentative; per-occurrence when originalStart is set)
Terminal window
curl -X POST https://api.knobs.io/v1/workspaces/{wid}/calendar/events \
-H "Authorization: Bearer knak_..." \
-H "Content-Type: application/json" \
-d '{
"calendarId": "c_11ff…",
"title": "Sprint planning",
"startLocal": "2026-07-21T10:00:00",
"endLocal": "2026-07-21T11:00:00",
"timeZone": "America/New_York",
"recurrence": ["RRULE:FREQ=WEEKLY;BYDAY=TU"],
"attendeeEmails": ["sam@acme.knobs.io"],
"reminders": [{"method": "notification", "minutesBefore": 10}]
}'

Times are wall-clock local (startLocal/endLocal) plus an IANA timeZone — recurring events stay at the same local time across DST. allDay: true with date-only values creates all-day events. Recurrence is RFC 5545 RRULE strings.

Terminal window
curl -X PATCH "https://api.knobs.io/v1/workspaces/{wid}/calendar/events/{eid}?mode=single&originalStart=2026-07-28T14:00:00Z" \
-H "Authorization: Bearer knak_..." \
-H "Content-Type: application/json" \
-d '{"title": "Sprint planning (moved)", "startLocal": "2026-07-28T11:00:00", "endLocal": "2026-07-28T12:00:00"}'

mode=single overrides one occurrence, mode=following splits the series, mode=all edits the master.

Endpoint Description
POST /v1/workspaces/{wid}/calendar/freebusy {uids?[], emails?[], from, to} → merged busy blocks + working hours per target (intervals only, never titles)
GET /v1/workspaces/{wid}/calendar/settings Your display timezone, working hours, default reminders
PUT /v1/workspaces/{wid}/calendar/settings Replace those settings
Endpoint Description
POST /v1/workspaces/{wid}/calendar/events:quick-add NLP quick-add {text, tz, confirm}confirm:false returns {draft} for review; confirm:true creates → {draft, event}. Rate-limited
POST /v1/workspaces/{wid}/calendar/events:extract-from-mail LLM event draft from a thread {threadId, tz?}{draft, sourceRef}
POST /v1/workspaces/{wid}/calendar/events:create-from-mail Non-AI source-link create {objectId, title?} from a mail thread
POST /v1/workspaces/{wid}/calendar/events:create-from-chat Same from a chat message
POST /v1/workspaces/{wid}/calendar/invites:import Import an inbound iCalendar invite {messageId, respond?} — adds it to your default calendar and optionally sends the iTIP reply
Endpoint Description
GET /v1/workspaces/{wid}/calendar/subscriptions List ICS subscriptions
POST /v1/workspaces/{wid}/calendar/subscriptions Add {url, name} — creates a read-only overlay calendar
DELETE /v1/workspaces/{wid}/calendar/subscriptions/{sid} Remove (and its overlay calendar)
POST /v1/workspaces/{wid}/calendar/subscriptions/{sid}/poll Poll the feed now

Subscribed calendars are read-only (403 read_only_calendar on event writes).

Owners can publish a calendar behind a capability token:

Endpoint Description
POST /v1/workspaces/{wid}/calendar/calendars/{cid}/feed/rotate Mint/rotate the publish token → {token, path} — shown once; rotating invalidates the old URL

The published feed itself is the platform’s one anonymous data-bearing GET (note: not under /v1):

GET /calendar-feeds/{cid}/{token}/calendar.ics
Terminal window
curl https://api.knobs.io/calendar-feeds/{cid}/{token}/calendar.ics

It requires no bearer, is rate-limited per IP, answers a uniform 404 to a bad calendar or token, and exports private events as busy placeholders. Paste the URL into any calendar app that supports ICS subscriptions.