Skip to content

Meeting Scheduling

Path: /meetings

Control Meetings are recurring meetings tied to a control's recurring evidence-collection objective — for example a quarterly PCI Scope Review or a Review of Reviews. Instead of tracking the meeting as a side task in someone's personal calendar, it becomes a first-class part of the control's cadence: the meeting recurs on the same frequency vocabulary as the control schedule, and when it is linked to a recurring control test its next occurrence advances in lock-step with that test.

Key Elements

  • Control Meetings page — Lists the recurring meetings, the control each supports, the cadence, the provider, the next occurrence, and the attendees.
  • Schedule now — Invokes the configured calendar provider to materialize the next occurrence and records the provider-assigned event id.
  • Download .ics — Returns a valid RFC 5545 calendar file you can import into any calendar client, regardless of which provider the meeting uses.

Creating a Control Meeting

A meeting is created against a specific control:

POST /api/v1/orgs/{org_id}/controls/{control_id}/meetings

The body sets the title, an optional description/agenda, the recurrence frequency, the provider, the meeting duration_minutes, timezone, whether it is_online, an optional location/join URL, and the attendees list.

  1. Open Control Meetings and choose the control the meeting supports (e.g. CC6.1).
  2. Give the meeting a title (e.g. PCI Scope Review) and an agenda.
  3. Choose the cadence — DAILY, WEEKLY, MONTHLY, QUARTERLY, or ANNUAL. This reuses the same frequency enum as the control schedule.
  4. Add attendees (see below).
  5. Save. The meeting appears in the list with its next occurrence computed from the cadence.

Auto-link to the recurring objective

If you do not supply a control_schedule_id, the meeting auto-links to the control's existing ControlSchedule row when one exists. Once linked, the meeting's next_occurrence advances together with the recurring control test — scheduling the review is no longer a separate manual chore.

Attendee Assignment

Each meeting carries an attendee list. An attendee is either an internal user (by user_id) or an external person identified by email + name. Every attendee has a role that maps onto standard calendar attendee semantics:

Role Calendar meaning
required Required participant
optional Optional participant
resource A room or resource mailbox

On update, supplying an attendees list performs a full replacement of the existing attendee set, so send the complete intended list each time.

How It Ties to the Control Evidence Cadence

The control's recurring evidence-collection objective and the meeting share one cadence vocabulary (ScheduleFrequency). When a meeting is linked to a control_schedule_id:

  • It recurs on the same frequency as the control test.
  • As the linked ControlSchedule recurs, the meeting's last_occurrence and next_occurrence are advanced one cadence step in step with the test.

This keeps the "we met to review this control" record aligned with the "we collected evidence for this control" record, so an auditor sees one coherent recurring objective rather than two drifting timelines.

Provider Matrix

The provider decides how a meeting is materialized on a real (or simulated) calendar. The provider is selected per-meeting and the factory falls back to the mock provider for any unknown value.

Provider Value Behavior Use when
Mock mock Mints a synthetic external_event_id (mock-<uuid>); contacts no external calendar. Re-scheduling reuses the existing id so the synthetic event is stable across recurrences. The default on the demo and in any offline environment.
ICS ics Generates a downloadable RFC 5545 .ics VEVENT (with RRULE, attendees, and proper line-folding) you import into any client. The offline-real option — a genuine, portable calendar artifact with no tenant required.
Microsoft Graph graph Creates the recurring event in a Microsoft 365 tenant via Graph app-only. You have a Microsoft 365 tenant and want the event to land in real Exchange calendars.

Demo default is Mock

The demo has no Microsoft 365 tenant, so meetings default to the Mock provider. POST /meetings/{id}/schedule returns a synthetic external_event_id with an honest note that no external calendar was contacted. The ICS download is available on every meeting regardless of provider.

Scheduling and downloading

POST /api/v1/orgs/{org_id}/meetings/{meeting_id}/schedule   # invoke the provider
GET  /api/v1/orgs/{org_id}/meetings/{meeting_id}/ics        # download an .ics

schedule invokes the meeting's provider and persists the returned external_event_id plus a last_occurrence timestamp. The .ics endpoint returns Content-Type: text/calendar as an attachment and works for any provider. The quarterly cadence maps to RRULE:FREQ=MONTHLY;INTERVAL=3.

Microsoft Graph for Real Tenants

The Graph provider is implemented behind the same interface but documented as configure-your-tenant — it is not active on the demo. When the tenant is unconfigured, schedule raises ProviderNotConfigured, which the endpoint surfaces as HTTP 503 rather than crashing the request.

App-only flow

The provider uses the client-credentials (app-only) flow:

  1. Acquire a token from the Entra tenant token endpoint (https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token, scope https://graph.microsoft.com/.default).
  2. Check availability via POST /users/{organizer}/calendar/getSchedule against the attendee mailboxes to read free/busy. This is a supported app-only call. The availability read is non-fatal — a failure to read free/busy does not block event creation.
  3. Create the recurring event via POST /users/{organizer}/events with a recurrence pattern derived from the cadence (quarterly → absoluteMonthly with interval: 3) and per-attendee entries carrying the event time zone.

findMeetingTimes is delegated-only

Microsoft's findMeetingTimes (suggest-a-slot) API is delegated-only — it is not available to an app-only token. The app-only path therefore builds candidate slots from getSchedule rather than asking Graph to suggest times.

Tenant setup

  1. Register an application in Microsoft Entra ID (App registrations → New registration).
  2. Create a client secret for the app (Certificates & secrets).
  3. Grant application permissions and obtain admin consent:
    • Calendars.ReadWrite — to create the event on the organizer mailbox.
    • Schedule.Read.All — to read attendee free/busy via getSchedule.
  4. Choose an organizer mailbox — the mailbox the app creates events on behalf of.

Configuration keys

Set the following environment variables / settings, then select the graph provider on a meeting:

Key Purpose
MS_GRAPH_TENANT_ID Microsoft Entra tenant id for app-only access
MS_GRAPH_CLIENT_ID The registered app (client) id
MS_GRAPH_CLIENT_SECRET The app client secret
MS_GRAPH_ORGANIZER_EMAIL Mailbox events are created on behalf of

The provider considers itself configured only when all four are present. Until then it stays gated, and any attempt to schedule via Graph returns 503 with a message naming the missing keys and required permissions.

No SMTP on the demo

The demo has no SMTP relay, so invites are log-only. Use the .ics download as the offline-real way to deliver an invite to attendees.