Availability and Calendar API
Two routes read from the same Calendar connector cache but expose deliberately different levels of detail — see Security Posture and the Calendar connector page for why the split exists.
GET /api/availability
Org-internal team free/busy for the Availability surface. Returns busy block times plus
per-person sync state — no event titles or details are selected or returned by this route at
all, not just hidden in the UI. The query itself only selects busy/free state, scoped to
employees (is_employee = true, email matching the internal domain).
Each person carries email (the internal staff address, used as guests on the page's Google
Calendar link), status (readable, stale, error, unknown) and cant_see_calendar. The
flag is true only for a readable calendar with zero busy blocks in its whole synced window and
no event on that calendar in calendar_events for the same window (an id-only, limit 1 read per
candidate; nothing from the event is returned). The page treats such a person like stale/unknown —
never as free — unless the viewer ticks "Include anyway". summary.cant_see counts them.
GET /api/calendar
Org-internal calendar intelligence from the event-detail cache — titles, attendees, project
matching, unmatched-meeting review. This is the route that exposes what /api/availability
deliberately withholds, and it is scoped more narrowly as a result (see the Calendar connector
page for exactly which roles can see it).
POST /api/calendar
Creates an approval proposal for a calendar write — it never writes to Google directly.
{ "action": "propose_event", "..." : "..." }
{ "action": "set_project_override", "event_id": "calendar-event-uuid", "project_id": "project-uuid", "force_unmatched": false, "reason": "Calendar view correction" }
{ "action": "clear_project_override", "event_id": "calendar-event-uuid" }
patchForManualProjectOverride / patchForClearedProjectOverride write to
calendar_event_project_overrides, a table separate from the sync cache, so the next hourly sync
doesn't silently erase a human correction. The proposal-shaped actions go through
buildCalendarApprovalRow and land in the same approvals queue as every other proposed action —
execution happens later, through calendar-act,
after a human approves.
Where the code lives
web/api/availability.js,_availability.jsweb/api/calendar.js,_calendar.jsweb/api/_approvals.js—shapeApproval, shared with the general approvals route