Scheduled Meetings
Book a meeting from a slot returned by /availability, read it back, reschedule it, or cancel it.
| Operation | Scope required |
|---|---|
Read (GET) | smart-scheduling:read |
| Book / reschedule / cancel | smart-scheduling:write |
Endpoints on this page
| Method | Endpoint | Description |
|---|---|---|
POST | /api/v1alpha1/scheduled-meetings | Book a meeting from a slot token returned by /availability. Idempotency-Key required. Details ↓ |
GET | /api/v1alpha1/scheduled-meetings/{meetingId} | Read a booked meeting's current state. Details ↓ |
PATCH | /api/v1alpha1/scheduled-meetings/{meetingId} | Reschedule a meeting to a new slot (host may change). Details ↓ |
DELETE | /api/v1alpha1/scheduled-meetings/{meetingId} | Cancel a meeting. Idempotent — second call also returns 204. Details ↓ |
Book a meeting
POST /api/v1alpha1/scheduled-meetings
Creates a meeting from a slot token previously returned by /availability.
Headers
| Header | Description |
|---|---|
Idempotency-Key | Required. Client-generated opaque string up to 255 chars. Replaying the same key within 24 hours returns the original response (with an X-Idempotent-Replay: true header) without rebooking. A repeated key with a different body is a 422 idempotency-key-conflict. |
Idempotency is required — not optional — on booking. A network timeout followed by an unguarded retry would otherwise result in double-bookings against the same host's calendar.
Request body
| Field | Type | Description |
|---|---|---|
slotToken | string | Slot token from /availability. Encodes the schedule, host, start time, and end time — no need to pass them separately. |
attendee | object | The person the meeting is being booked for. See Attendee. |
formValues | object | The same form values you submitted to /availability. Map of key → answer (scalar or array). The server verifies the values match those used to issue the slot token; a mismatch is 422 form-validation. Captured on the calendar event description and the post-booking webhook payload. |
surveyResponses | object | (optional) Map of question text → free-text answer. Unknown keys are rejected with 422 — silently dropping a response would make a schedule owner's question rename look like a working integration that had quietly stopped collecting answers. See survey-question notes. |
There is deliberately no preview flag on the booking request. Preview mode is inherited from the slot token — if the slot was issued from a preview: true availability call, the booking is preview-mode.
attendee object
| Attribute | Type | Description |
|---|---|---|
firstName | string | Attendee's given name. |
lastName | string | Attendee's family name. |
email | string | RFC 5322-valid email address. |
phone | string | (optional) E.164-formatted phone number. Presence triggers SMS confirmation; absence suppresses it. There is no separate toggle. |
timeZone | string | (optional) IANA time zone the attendee is booking from. Echoed in confirmation emails so the calendar event renders in a familiar zone. |
Response
See Meetingmodel for the canonical shape, sub-object semantics, and per-feature field documentation.
Confirmation notifications
| Notification | Triggered by |
|---|---|
| Confirmation email to host and attendee | Schedule's email-opt-in setting (configured in the Cirrus Insight admin UI; not overridable via the API) |
| SMS to attendee | Presence of attendee.phone |
To suppress both for a given booking, use a slot token issued from a preview: true availability call — preview bookings skip webhook delivery and CRM sync entirely.
Errors
409 slot-conflict— the slot is no longer bookable (host's calendar changed since/availability). Response includesrerunAvailability: true.410 slot-token-expired— the slot token was issued more than 10 minutes ago. Refresh availability.422 slot-token-invalid— signature verification failed, or the payload doesn't match the requested schedule / host / times.422 form-validation— form values differ from those used to issue the slot token, or a required value is missing.422 booking-validation-failed— the request passed schema checks but was rejected downstream (e.g., unknown survey key).422 idempotency-key-conflict— the sameIdempotency-Keywas used with a different request body.400 idempotency-key-required—Idempotency-Keyheader was missing.429 rate-limit-exceeded— too many requests.
curl -s "https://altus.cirrusinsight.com/api/v1alpha1/scheduled-meetings" \
-H "X-Cirrus-Api-Key: $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"slotToken": "eyJzY2hlZHVsZUlkIjoic2NoZWRf...",
"attendee": {
"firstName": "Sample",
"lastName": "Guest",
"email": "guest@example.com",
"phone": "+14155551234",
"timeZone": "America/New_York"
},
"formValues": {
"region": "NA-East",
"companySize": "51-200"
},
"surveyResponses": {
"What brings you here today?": "Pipeline visibility"
}
}'import requests
import uuid
headers = {
"X-Cirrus-Api-Key": token,
"Idempotency-Key": str(uuid.uuid4()),
}
response = requests.post(
"https://altus.cirrusinsight.com/api/v1alpha1/scheduled-meetings",
headers=headers,
json={
"slotToken": "eyJzY2hlZHVsZUlkIjoic2NoZWRf...",
"attendee": {
"firstName": "Sample",
"lastName": "Guest",
"email": "guest@example.com",
"phone": "+14155551234",
"timeZone": "America/New_York",
},
"formValues": {
"region": "NA-East",
"companySize": "51-200",
},
"surveyResponses": {
"What brings you here today?": "Pipeline visibility",
},
},
)
response.raise_for_status()
meeting = response.json()using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Cirrus-Api-Key", token);
var body = new
{
slotToken = "eyJzY2hlZHVsZUlkIjoic2NoZWRf...",
attendee = new
{
firstName = "Sample",
lastName = "Guest",
email = "guest@example.com",
phone = "+14155551234",
timeZone = "America/New_York",
},
formValues = new
{
region = "NA-East",
companySize = "51-200",
},
surveyResponses = new Dictionary<string, string>
{
["What brings you here today?"] = "Pipeline visibility",
},
};
var request = new HttpRequestMessage(
HttpMethod.Post,
"https://altus.cirrusinsight.com/api/v1alpha1/scheduled-meetings")
{
Content = new StringContent(
JsonSerializer.Serialize(body),
Encoding.UTF8,
"application/json"),
};
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
var meeting = await response.Content.ReadAsStringAsync();HTTP/1.1 201 Created
Content-Type: application/json{
"id": "mtg_...",
"orgId": "org_...",
"host": {
"userId": "usr_...",
"email": "alex.rivera@example.com",
"firstName": "Alex",
"lastName": "Rivera",
"displayName": "Alex Rivera"
},
"attendee": {
"email": "guest@example.com",
"firstName": "Sample",
"lastName": "Guest",
"phone": "+14155551234",
"timeZone": "America/New_York"
},
"additionalAttendees": [],
"title": "Cirrus Insight Demo",
"startTime": "2026-06-16T17:00:00Z",
"endTime": "2026-06-16T17:30:00Z",
"timeZone": "America/Los_Angeles",
"location": {
"type": "video",
"provider": "zoom",
"joinUrl": "https://us02web.zoom.us/j/...",
"address": null
},
"conferencing": {
"provider": "zoom",
"providerMeetingId": "1234567890"
},
"calendarEventId": "google-event-uid",
"status": "scheduled",
"createdAt": "2026-06-15T14:23:01Z",
"updatedAt": "2026-06-15T14:23:01Z",
"smartScheduling": {
"scheduleId": "sched_...",
"formValues": [
{ "key": "region", "answer": "NA-East" }
],
"surveyResponses": [
{ "question": "What brings you here today?", "answer": "Pipeline visibility" }
],
"rescheduleUrl": "https://...",
"cancelUrl": "https://...",
"campaign": {
"id": null, "source": null, "medium": null,
"campaign": null, "term": null, "content": null
},
"preview": false
},
"personalScheduling": null,
"teamScheduling": null,
"meetingAi": null
}Read a scheduled meeting
GET /api/v1alpha1/scheduled-meetings/{meetingId}
Returns a previously booked meeting. Useful for agents that want to confirm state without keeping their own copy — the create response and this read response share exactly one DTO.
Path parameters
| Parameter | Type | Description |
|---|---|---|
meetingId | string | The meeting ID, prefixed mtg_. |
Response
Same shape as POST /scheduled-meetings. status is clock-derived (scheduled before end-time, completed after, canceled overrides both) so a cached value may go stale without a write.
Errors
404 meeting-not-found— meeting does not exist, or is not visible to this org
curl -s "https://altus.cirrusinsight.com/api/v1alpha1/scheduled-meetings/mtg_01H8YKQ2N9DEMO" \
-H "X-Cirrus-Api-Key: $TOKEN"import requests
meeting_id = "mtg_01H8YKQ2N9DEMO"
response = requests.get(
f"https://altus.cirrusinsight.com/api/v1alpha1/scheduled-meetings/{meeting_id}",
headers={"X-Cirrus-Api-Key": token},
)
response.raise_for_status()
meeting = response.json()using System.Net.Http;
using System.Net.Http.Headers;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Cirrus-Api-Key", token);
var meetingId = "mtg_01H8YKQ2N9DEMO";
var response = await client.GetAsync(
$"https://altus.cirrusinsight.com/api/v1alpha1/scheduled-meetings/{meetingId}");
response.EnsureSuccessStatusCode();
var meeting = await response.Content.ReadAsStringAsync();Reschedule a meeting
PATCH /api/v1alpha1/scheduled-meetings/{meetingId}
Changes a meeting's time (and optionally host) by providing a new slot token. If the new slot belongs to a different host, the platform performs a host-swap reschedule.
Cannot change the attendee — for that, cancel and rebook. Cannot move a meeting between schedules — the new slot token must belong to the same schedule as the original meeting (422 schedule-mismatch otherwise).
Request body
| Field | Type | Description |
|---|---|---|
slotToken | string | New slot token from /availability. May belong to a different host than the original meeting, but must belong to the same schedule. |
reason | string | (optional) Free-text reason. Stored on the meeting's audit trail and included in the reschedule webhook payload. |
Idempotency-Key is required, same rules as booking.
Response
200 OK with the updated ScheduledMeetingmodel.
A scheduling.smartscheduler.rescheduled webhook fires after a successful reschedule.
Errors
Same set as booking, plus:
409 meeting-not-reschedulable— the meeting is already canceled or completed422 schedule-mismatch— the slot token'sscheduleIddiffers from the meeting's schedule
curl -s -X PATCH "https://altus.cirrusinsight.com/api/v1alpha1/scheduled-meetings/mtg_01H8YKQ2N9DEMO" \
-H "X-Cirrus-Api-Key: $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"slotToken": "eyJzY2hlZHVsZUlkIjoic2NoZWRf...",
"reason": "Attendee requested to move to next week"
}'import requests
import uuid
meeting_id = "mtg_01H8YKQ2N9DEMO"
headers = {
"X-Cirrus-Api-Key": token,
"Idempotency-Key": str(uuid.uuid4()),
}
response = requests.patch(
f"https://altus.cirrusinsight.com/api/v1alpha1/scheduled-meetings/{meeting_id}",
headers=headers,
json={
"slotToken": "eyJzY2hlZHVsZUlkIjoic2NoZWRf...",
"reason": "Attendee requested to move to next week",
},
)
response.raise_for_status()
meeting = response.json()using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Cirrus-Api-Key", token);
var meetingId = "mtg_01H8YKQ2N9DEMO";
var body = new
{
slotToken = "eyJzY2hlZHVsZUlkIjoic2NoZWRf...",
reason = "Attendee requested to move to next week",
};
var request = new HttpRequestMessage(
new HttpMethod("PATCH"),
$"https://altus.cirrusinsight.com/api/v1alpha1/scheduled-meetings/{meetingId}")
{
Content = new StringContent(
JsonSerializer.Serialize(body),
Encoding.UTF8,
"application/json"),
};
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
var meeting = await response.Content.ReadAsStringAsync();Cancel a meeting
DELETE /api/v1alpha1/scheduled-meetings/{meetingId}
Cancels a previously booked meeting. The calendar event is removed, the attendee receives a cancellation notification (if configured), and a scheduling.smartscheduler.canceled webhook fires.
Query parameters
| Parameter | Type | Description |
|---|---|---|
sendNotifications | boolean | (optional, default true) When false, suppresses the cancellation email and SMS. Useful for silent cleanup operations. |
reason | string | (optional, max 500 chars) Free-text reason. Surfaced in the cancellation webhook payload. |
Cancel takes query parameters rather than a body because DELETE bodies aren't universally supported by HTTP clients.
Response
204 No Content on success.
Cancelling an already-canceled meeting also returns 204. DELETE states a desired end condition; that condition already holds, so a retry after a client timeout succeeds silently rather than being treated as a failure. Notifications only fire on the first cancel — a second DELETE does not re-send.
Subsequent GET /scheduled-meetings/{id} returns the meeting with status: "canceled".
Errors
404 meeting-not-found— meeting does not exist or is not visible to this org410 meeting-completed— cannot cancel a meeting whose end time has already passed
curl -s -X DELETE \
"https://altus.cirrusinsight.com/api/v1alpha1/scheduled-meetings/mtg_01H8YKQ2N9DEMO?sendNotifications=true&reason=Invitee%20requested%20cancellation" \
-H "X-Cirrus-Api-Key: $TOKEN"import requests
meeting_id = "mtg_01H8YKQ2N9DEMO"
response = requests.delete(
f"https://altus.cirrusinsight.com/api/v1alpha1/scheduled-meetings/{meeting_id}",
headers={"X-Cirrus-Api-Key": token},
params={
"sendNotifications": "true",
"reason": "Invitee requested cancellation",
},
)
response.raise_for_status()using System.Net.Http;
using System.Net.Http.Headers;
using System.Web;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Cirrus-Api-Key", token);
var meetingId = "mtg_01H8YKQ2N9DEMO";
var query = HttpUtility.ParseQueryString(string.Empty);
query["sendNotifications"] = "true";
query["reason"] = "Invitee requested cancellation";
var uri = new UriBuilder(
$"https://altus.cirrusinsight.com/api/v1alpha1/scheduled-meetings/{meetingId}")
{
Query = query.ToString()
}.Uri;
var response = await client.DeleteAsync(uri);
response.EnsureSuccessStatusCode();