Calendars & Appointments
Calendars define how and when people can book time on your SMBcrm account: the services offered, appointment length, and availability. Appointments are the individual bookings placed on those calendars, whether created through a booking widget or directly through the API. Some endpoints refer to them as events rather than appointments.
Base URL: https://services.smbcrm.com · Version header: v3 ·
Scopes: calendars.readonly / calendars.write for calendars, availability schedules,
and services; calendars/events.readonly / calendars/events.write for appointments,
notes, notifications, blocked slots, and service bookings;
calendars/groups.readonly / calendars/groups.write for calendar groups. See
Scopes.
List & retrieve calendars
Section titled “List & retrieve calendars”locationId is required. Two optional query parameters narrow the list: groupId returns
only the calendars in one calendar group, and showDrafted (default
true) includes draft calendars. Pass showDrafted=false to return only active calendars.
curl "https://services.smbcrm.com/calendars/?locationId=<location_id>" \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "calendars": [ { "id": "<calendar_id>", "locationId": "<location_id>", "name": "Sales Consultation", "description": "30-minute intro call with a sales rep", "calendarType": "event", "isActive": true, "slotDuration": 30, "slotDurationUnit": "mins" } ]}Each calendar has the fields listed under
Create, update & delete a calendar, plus its id.
curl https://services.smbcrm.com/calendars/<calendar_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "calendar": { "id": "<calendar_id>", "locationId": "<location_id>", "name": "Sales Consultation", "description": "30-minute intro call with a sales rep", "calendarType": "event", "isActive": true, "slotDuration": 30, "slotDurationUnit": "mins", "durationOptions": [ { "duration": 15, "durationUnit": "mins", "isDefault": false, "order": 1 }, { "duration": 30, "durationUnit": "mins", "isDefault": true, "order": 2 }, { "duration": 60, "durationUnit": "mins", "isDefault": false, "order": 3 } ] }}Create, update & delete a calendar
Section titled “Create, update & delete a calendar”locationId and name are required. The remaining fields configure the calendar’s type,
team, meeting lengths, booking limits, and booking form.
curl -X POST https://services.smbcrm.com/calendars/ \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "locationId": "<location_id>", "name": "Sales Consultation", "description": "30-minute intro call with a sales rep", "calendarType": "event", "slotDuration": 30, "slotDurationUnit": "mins" }'{ "calendar": { "id": "<calendar_id>", "locationId": "<location_id>", "name": "Sales Consultation", "description": "30-minute intro call with a sales rep", "calendarType": "event", "isActive": true, "slotDuration": 30, "slotDurationUnit": "mins" }}Calendar fields
| Field | Description |
|---|---|
locationId |
Your account/location ID. Required on create. |
name |
Calendar name. Required on create. |
description |
Calendar description. |
calendarType |
round_robin, event, class_booking, collective, service_booking, or personal. Accepted on create. |
isActive |
Default true. Send false to create the calendar as a draft. |
groupId |
The calendar group the calendar belongs to. |
slug, widgetSlug |
URL slugs for the calendar and its booking widget. |
widgetType |
default (the neo layout) or classic. Default classic. |
eventTitle |
Title given to appointments on this calendar. Default {{contact.name}}. |
eventColor |
Appointment color as a hex value. Default #039be5. |
eventType |
Round robin distribution: RoundRobin_OptimizeForAvailability (default) or RoundRobin_OptimizeForEqualDistribution. |
teamMembers |
The team members who take bookings. Required for Round Robin, Collective, Class, and Service calendars. A personal calendar must have exactly one team member. |
locationConfigurations |
Meeting locations for an event calendar. |
slotDuration, slotDurationUnit |
Meeting length. Default 30. The unit is mins or hours. |
durationOptions |
Several selectable meeting lengths. See below. |
slotInterval, slotIntervalUnit |
Time between the booking slots shown on the calendar. Default 30. The unit is mins or hours. |
slotBuffer, slotBufferUnit |
Time added after an appointment. The unit is mins or hours. |
preBuffer, preBufferUnit |
Time added before an appointment. The unit is mins or hours. |
appointmentPerSlot |
Maximum bookings per slot, per user. On a class booking calendar, the maximum seats per slot. Default 1. |
appointmentPerDay |
Maximum appointments that can be booked in one day. |
allowBookingAfter, allowBookingAfterUnit |
Minimum scheduling notice. The unit is mins, hours, days, weeks, or months. |
allowBookingFor, allowBookingForUnit |
The booking window: how far ahead people can book. The unit is days, weeks, or months. |
countAvailableDaysOnly |
Default false. When true, only days with configured availability count toward the booking window. |
enableRecurring, recurring |
Recurring appointments. Enable them only on a calendar with one team member. See below. |
formId |
The form used for booking. |
stickyContact |
Enables sticky contact assignment. |
isLivePaymentMode |
Whether payments on this calendar use live mode. |
autoConfirm |
Confirm appointments automatically. Default true. |
shouldSendAlertEmailsToAssignedMember, alertEmail |
Send alert emails to the assigned team member, and the alert email address. |
googleInvitationEmails |
Send Google invitation emails. Default false. |
allowReschedule, allowCancellation |
Let bookers reschedule or cancel. Both default true. |
shouldAssignContactToTeamMember, shouldSkipAssigningContactForExisting |
Assign the contact to the team member on booking, and skip that assignment when the contact already exists. |
notes |
Notes for the calendar. |
pixelId |
Facebook Pixel ID for tracking. |
formSubmitType |
What happens after the booking form is submitted: ThankYouMessage (default) or RedirectURL. Set the message or the URL with formSubmitThanksMessage or formSubmitRedirectURL. |
guestType |
count_only or collect_detail. |
consentLabel |
Consent label text. |
calendarCoverImage |
Cover image URL. |
lookBusyConfig |
enabled (default false) and lookBusyPercentage, the percentage of slots to hide. Both are required when you send lookBusyConfig. |
Nested objects:
teamMembers[]items takeuserId(required),priority(0,0.5, or1; default0.5; used for round robin),isPrimary, andlocationConfigurations.isPrimaryis required on collective calendars, and only one member can be primary.locationConfigurations[]items takekind(required) andlocation.kindis one ofcustom,zoom_conference,google_conference,inbound_call,outbound_call,physical,booker, orms_teams_conference. Event calendars don’t supportzoom_conference,google_conference, orms_teams_conference, andlocationdoesn’t apply to those three kinds. Multiple locations are allowed only when one team member is selected. Class booking and collective calendars allow one location configuration per team member. Responses return ameetingIdfor each configuration, which you can pass asmeetingLocationIdwhen you book an appointment.durationOptions[]accepts up to 3 items. Each needsduration,durationUnit(minsorhours),isDefault, andorder(1-based display order). Exactly one item must haveisDefaultset totrue.slotDurationandslotDurationUnitremain the single-length setting. To get slots for one of the lengths, passdurationto free slots.recurringtakesfreq(DAILY,WEEKLY, orMONTHLY),count(up to24),bookingOption(skip,continue, orbook_next, which sets what happens when a recurring slot is unavailable), andbookingOverlapDefaultStatus(confirmedornew).
Responses can also include the older misspelled appoinmentPerSlot and appoinmentPerDay.
Those are deprecated. Send appointmentPerSlot and appointmentPerDay.
These request fields are deprecated: notifications (use
calendar notifications), openHours and availabilities (use
availability schedules), availabilityType, meetingLocation
(use locationConfigurations), the per-member meetingLocation and meetingLocationType
(use the per-member locationConfigurations), and lookBusyConfig.LookBusyPercentage (use
lookBusyPercentage).
No fields are required. The update body takes the fields in the table above, except
locationId, calendarType, and slotBufferUnit.
curl -X PUT https://services.smbcrm.com/calendars/<calendar_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "name": "Sales Consultation (30 min)", "slotDuration": 30 }'{ "calendar": { "id": "<calendar_id>", "locationId": "<location_id>", "name": "Sales Consultation (30 min)", "calendarType": "event", "isActive": true, "slotDuration": 30, "slotDurationUnit": "mins" }}To offer several meeting lengths, send durationOptions. The response returns the updated
calendar with the same durationOptions.
{ "durationOptions": [ { "duration": 15, "durationUnit": "mins", "isDefault": false, "order": 1 }, { "duration": 30, "durationUnit": "mins", "isDefault": true, "order": 2 }, { "duration": 60, "durationUnit": "mins", "isDefault": false, "order": 3 } ]}curl -X DELETE https://services.smbcrm.com/calendars/<calendar_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "success": true }Availability schedules
Section titled “Availability schedules”A schedule defines when a calendar or a user is available: weekly hours, hours on specific
dates, and the timezone they apply in. Schedules replace the deprecated openHours and
availabilities fields on the calendar object.
A schedule holds a list of rules and an IANA timezone. Each rule has:
type:wdayfor a recurring weekday, ordatefor one specific date.day: forwdayrules, one ofsundaythroughsaturday.date: fordaterules, aYYYY-MM-DDdate.intervals: a list of time windows, each withfromandtoin 24-hourHH:MMformat.
Event calendars
Section titled “Event calendars”Use these endpoints to set the availability of a single event calendar.
curl https://services.smbcrm.com/calendars/schedules/event-calendar/<calendar_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "schedule": { "calendarId": "<calendar_id>", "timezone": "America/Chicago", "rules": [ { "type": "wday", "day": "monday", "intervals": [{ "from": "09:00", "to": "17:00" }] } ] }}rules and timezone are required.
curl -X POST https://services.smbcrm.com/calendars/schedules/event-calendar/<calendar_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "timezone": "America/Chicago", "rules": [ { "type": "wday", "day": "monday", "intervals": [{ "from": "09:00", "to": "17:00" }] }, { "type": "wday", "day": "tuesday", "intervals": [{ "from": "09:00", "to": "17:00" }] } ] }'{ "schedule": { "calendarId": "<calendar_id>", "timezone": "America/Chicago", "rules": [ { "type": "wday", "day": "monday", "intervals": [{ "from": "09:00", "to": "17:00" }] }, { "type": "wday", "day": "tuesday", "intervals": [{ "from": "09:00", "to": "17:00" }] } ] }}Send rules, timezone, or both. Fields you leave out stay as they are. The response is
the updated schedule.
curl -X PUT https://services.smbcrm.com/calendars/schedules/event-calendar/<calendar_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "timezone": "America/New_York" }'User schedules
Section titled “User schedules”A user schedule sets the availability of one team member and can apply to several calendars.
locationId, userId, name, and timezone are required. rules and calendarIds are
optional.
curl -X POST https://services.smbcrm.com/calendars/schedules \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "locationId": "<location_id>", "userId": "<user_id>", "name": "Business Hours Schedule", "timezone": "America/Chicago", "rules": [ { "type": "wday", "day": "monday", "intervals": [{ "from": "09:00", "to": "17:00" }] } ], "calendarIds": ["<calendar_id>"] }'{ "schedule": { "id": "<schedule_id>", "name": "Business Hours Schedule", "locationId": "<location_id>", "userId": "<user_id>", "timezone": "America/Chicago", "rules": [ { "type": "wday", "day": "monday", "intervals": [{ "from": "09:00", "to": "17:00" }] } ], "calendarIds": ["<calendar_id>"], "deleted": false }}| Query parameter | Description |
|---|---|
locationId |
Your account/location ID. Required. |
userId |
Return the schedules of this user. Required. |
calendarId |
Limit results to schedules associated with one calendar. |
skip |
Number of schedules to skip. Default 0. |
limit |
Maximum number of schedules to return. Default 50, maximum 500. |
curl "https://services.smbcrm.com/calendars/schedules/search?locationId=<location_id>&userId=<user_id>" \ -H "Authorization: Bearer <token>" \ -H "Version: v3"The response is { "schedules": [...] }, where each item has the fields shown in the
create response above.
curl https://services.smbcrm.com/calendars/schedules/<schedule_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3"The response is { "schedule": { ... } }.
All fields are optional. Only the fields you send change.
curl -X PUT https://services.smbcrm.com/calendars/schedules/<schedule_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "name": "Summer hours", "timezone": "America/Chicago" }'The response is the updated { "schedule": { ... } }.
curl -X DELETE https://services.smbcrm.com/calendars/schedules/<schedule_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "success": true }curl -X PUT https://services.smbcrm.com/calendars/schedules/<schedule_id>/associations/<calendar_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "success": true }curl -X DELETE https://services.smbcrm.com/calendars/schedules/<schedule_id>/associations/<calendar_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "success": true }Check availability
Section titled “Check availability”startDate and endDate are required Unix timestamps in milliseconds, and the range
can’t be longer than 31 days. To cover a longer period, request it in 31-day chunks.
| Query parameter | Description |
|---|---|
timezone |
IANA timezone name used to compute the returned slot times. |
userId |
Return slots for one team member. |
userIds |
Return slots for several team members. |
duration |
Meeting length in minutes. Must match one of the calendar’s active durationOptions. If omitted, the default duration is used. |
curl "https://services.smbcrm.com/calendars/<calendar_id>/free-slots?startDate=1784073600000&endDate=1784419200000&timezone=America/Chicago" \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "2026-07-15": { "slots": ["2026-07-15T09:00:00-05:00", "2026-07-15T09:30:00-05:00", "2026-07-15T10:00:00-05:00"] }, "2026-07-16": { "slots": ["2026-07-16T09:00:00-05:00", "2026-07-16T09:30:00-05:00"] }}Calendar groups
Section titled “Calendar groups”A calendar group collects calendars on one booking page. Reading groups needs
calendars/groups.readonly. Creating, changing, and deleting them needs
calendars/groups.write. To filter by group, pass groupId when you list
calendars or appointments.
locationId is required.
curl "https://services.smbcrm.com/calendars/groups?locationId=<location_id>" \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "groups": [ { "id": "<group_id>", "locationId": "<location_id>", "name": "Sales Team", "description": "Book time with our sales team", "slug": "sales-team", "isActive": true } ]}locationId, name, description, and slug are required. isActive is optional. Check
the slug with validate-slug first.
curl -X POST https://services.smbcrm.com/calendars/groups \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "locationId": "<location_id>", "name": "Sales Team", "description": "Book time with our sales team", "slug": "sales-team" }'{ "group": { "id": "<group_id>", "locationId": "<location_id>", "name": "Sales Team", "description": "Book time with our sales team", "slug": "sales-team", "isActive": true }}Send locationId and slug. Run this before you create or rename a group.
curl -X POST https://services.smbcrm.com/calendars/groups/validate-slug \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "locationId": "<location_id>", "slug": "sales-team" }'{ "available": true }name, description, and slug are all required. The response is the updated
{ "group": { ... } }.
curl -X PUT https://services.smbcrm.com/calendars/groups/<group_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "name": "Sales Team", "description": "Book time with our sales team", "slug": "sales" }'isActive is required.
curl -X PUT https://services.smbcrm.com/calendars/groups/<group_id>/status \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "isActive": false }'{ "success": true }curl -X DELETE https://services.smbcrm.com/calendars/groups/<group_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "success": true }Calendar notifications
Section titled “Calendar notifications”Notification settings control the messages a calendar sends around a booking: who receives
them, on which channel, and when. They replace the deprecated notifications array on the
calendar body.
| Query parameter | Description |
|---|---|
isActive |
Filter by active status. |
deleted |
Include deleted notifications. |
limit |
Number of records to return. Default 100. |
skip |
Number of records to skip. Default 0. |
curl "https://services.smbcrm.com/calendars/<calendar_id>/notifications?limit=100" \ -H "Authorization: Bearer <token>" \ -H "Version: v3"The response is an array of notification objects, in the shape shown in the create response below.
The request body is an array, and every item applies to the calendar in the path.
| Field | Description |
|---|---|
receiverType |
Who gets the notification: contact, guest, assignedUser, emails, phoneNumbers, or business. Required. |
channel |
email, inApp, sms, or whatsapp. Required. |
notificationType |
booked, confirmation, cancellation, reminder, followup, or reschedule. Required. |
isActive |
Default true. |
templateId, subject, body |
Email content. Not needed for in-app notifications. |
beforeTime |
When to send a reminder, as a list of timeOffset and unit pairs. Not needed for other notification types. |
afterTime |
When to send a follow-up, in the same shape. Not needed for other notification types. |
additionalEmailIds, additionalPhoneNumbers |
Extra recipients. |
selectedUsers |
Users for in-app and business email notifications. Accepts user IDs and the keyword sub_account_admin. |
fromAddress, fromName, fromNumber |
Sender details: the from address for email, the from name for email and SMS, and the from number for SMS. |
curl -X POST https://services.smbcrm.com/calendars/<calendar_id>/notifications \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '[ { "receiverType": "contact", "channel": "email", "notificationType": "reminder", "subject": "Your appointment is coming up", "body": "See you soon.", "beforeTime": [{ "timeOffset": 1, "unit": "hours" }] } ]'[ { "_id": "<notification_id>", "receiverType": "contact", "channel": "email", "notificationType": "reminder", "isActive": true, "subject": "Your appointment is coming up", "body": "See you soon.", "beforeTime": [{ "timeOffset": 1, "unit": "hours" }], "deleted": false }]curl https://services.smbcrm.com/calendars/<calendar_id>/notifications/<notification_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3"The response is a single notification object.
The body takes the fields from the create table, all optional, plus deleted to
soft-delete the setting.
curl -X PUT https://services.smbcrm.com/calendars/<calendar_id>/notifications/<notification_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "isActive": false }'The response is { "message": "..." } with the result of the update.
curl -X DELETE https://services.smbcrm.com/calendars/<calendar_id>/notifications/<notification_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3"The response is { "message": "..." } with the result of the delete.
List & retrieve appointments
Section titled “List & retrieve appointments”| Query parameter | Description |
|---|---|
locationId |
Your account/location ID. Required. |
startTime |
Start of the range as a Unix timestamp in milliseconds. Required. |
endTime |
End of the range as a Unix timestamp in milliseconds. Required. |
calendarId, userId, groupId |
Send at least one. userId is the owner of the appointment and groupId is a calendar group. |
curl "https://services.smbcrm.com/calendars/events?locationId=<location_id>&calendarId=<calendar_id>&startTime=1784073600000&endTime=1784678400000" \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "events": [ { "id": "<event_id>", "calendarId": "<calendar_id>", "locationId": "<location_id>", "contactId": "<contact_id>", "groupId": "<group_id>", "title": "Sales Consultation", "appointmentStatus": "confirmed", "assignedUserId": "<user_id>", "users": [], "address": "https://meet.example.com/abc-def", "isRecurring": false, "startTime": "2026-07-15T20:00:00.000Z", "endTime": "2026-07-15T20:30:00.000Z", "createdBy": { "userId": "<user_id>", "source": "public_api" }, "dateAdded": "2026-07-10T15:04:00.000Z", "dateUpdated": "2026-07-10T15:04:00.000Z" } ]}assignedUserId is the primary owner of the appointment, and users lists the secondary
owners. Events can also include notes, description, deleted, and assignedResources.
rescheduledAt appears only on appointments that have been rescheduled.
For a recurring appointment, isRecurring is true and rrule holds the recurrence rule.
Each occurrence has its own instance id, and masterEventId is the ID of the series.
To list the appointments of one contact, use GET /contacts/{contactId}/appointments with
the contacts.readonly scope. See Contacts. It doesn’t need a
calendar, user, or group.
eventId is an event ID or the instance ID of one occurrence of a recurring appointment.
curl https://services.smbcrm.com/calendars/events/appointments/<event_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "event": { "id": "<event_id>", "calendarId": "<calendar_id>", "locationId": "<location_id>", "contactId": "<contact_id>", "groupId": "<group_id>", "title": "Sales Consultation", "appointmentStatus": "confirmed", "assignedUserId": "<user_id>", "users": [], "address": "https://meet.example.com/abc-def", "isRecurring": false, "startTime": "2026-07-16T18:00:00.000Z", "endTime": "2026-07-16T18:30:00.000Z", "rescheduledAt": "2026-07-15T21:10:00.000Z", "createdBy": { "userId": "<user_id>", "source": "public_api" }, "dateAdded": "2026-07-10T15:04:00.000Z", "dateUpdated": "2026-07-15T21:10:00.000Z" }}Book, update & delete appointments
Section titled “Book, update & delete appointments”calendarId, locationId, contactId, and startTime are required. endTime is optional.
curl -X POST https://services.smbcrm.com/calendars/events/appointments \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "calendarId": "<calendar_id>", "locationId": "<location_id>", "contactId": "<contact_id>", "startTime": "2026-07-15T20:00:00.000Z", "endTime": "2026-07-15T20:30:00.000Z", "title": "Sales Consultation" }'{ "id": "<event_id>", "calendarId": "<calendar_id>", "locationId": "<location_id>", "contactId": "<contact_id>", "title": "Sales Consultation", "appointmentStatus": "confirmed", "startTime": "2026-07-15T20:00:00.000Z", "endTime": "2026-07-15T20:30:00.000Z", "isRecurring": false, "dateAdded": "2026-07-10T15:04:00.000Z", "dateUpdated": "2026-07-10T15:04:00.000Z"}Optional fields
| Field | Description |
|---|---|
title |
Appointment title. |
appointmentStatus |
new, confirmed, cancelled, showed, noshow, invalid, completed, or active. Accepted on create and update. |
assignedUserId |
The user who owns the appointment. |
description, address |
Appointment description and address. |
meetingLocationType |
custom, zoom, gmeet, phone, address, ms_teams, or google. Defaults to custom when you send address. |
meetingLocationId |
Picks a meeting location by its ID from the calendar’s locationConfigurations or a team member’s locationConfigurations. Default default. |
overrideLocationConfig |
Send false when you send only meetingLocationId, and true when you send only meetingLocationType. |
toNotify |
Default true. When false, automations don’t run for this appointment. |
ignoreDateRange |
When true, the calendar’s minimum scheduling notice and date range are ignored. |
ignoreFreeSlotValidation |
When true, the time slot validation is skipped, including the date range check. |
rrule |
An RFC 5545 recurrence rule, such as RRULE:FREQ=DAILY;INTERVAL=1;COUNT=5. DTSTART isn’t required. Applied only when ignoreFreeSlotValidation is true. |
No fields are required. The body takes the optional fields from the table above, plus
startTime, endTime, and calendarId. appointmentStatus accepts new, confirmed,
cancelled, showed, noshow, invalid, completed, or active.
For a recurring appointment, pass the masterEventId as eventId to change the original
series.
curl -X PUT https://services.smbcrm.com/calendars/events/appointments/<event_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "startTime": "2026-07-16T18:00:00.000Z", "endTime": "2026-07-16T18:30:00.000Z", "appointmentStatus": "confirmed" }'{ "id": "<event_id>", "calendarId": "<calendar_id>", "locationId": "<location_id>", "contactId": "<contact_id>", "title": "Sales Consultation", "appointmentStatus": "confirmed", "startTime": "2026-07-16T18:00:00.000Z", "endTime": "2026-07-16T18:30:00.000Z", "isRecurring": false, "dateAdded": "2026-07-10T15:04:00.000Z", "dateUpdated": "2026-07-15T21:10:00.000Z"}eventId is the same id returned by the appointment endpoints above. For a recurring
appointment, pass the masterEventId to delete the original series. This endpoint also
deletes blocked slots. The request takes an empty JSON object as its body.
curl -X DELETE https://services.smbcrm.com/calendars/events/<event_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{}'{ "succeeded": true }Appointment notes
Section titled “Appointment notes”Notes attach free text to an appointment. Reading them needs calendars/events.readonly.
Writing them needs calendars/events.write.
limit and offset are required. When hasMore is true, request the next page with a
larger offset.
curl "https://services.smbcrm.com/calendars/appointments/<event_id>/notes?limit=20&offset=0" \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "notes": [ { "id": "<note_id>", "body": "Customer asked about parking", "userId": "<user_id>", "contactId": "<contact_id>", "dateAdded": "2026-07-15T20:45:00.000Z", "createdBy": { "id": "<user_id>", "name": "Alex Rivera" } } ], "hasMore": false}body is required and can be up to 5,000 characters. userId is the author.
curl -X POST https://services.smbcrm.com/calendars/appointments/<event_id>/notes \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "body": "Customer asked about parking", "userId": "<user_id>" }'{ "note": { "id": "<note_id>", "body": "Customer asked about parking", "userId": "<user_id>", "contactId": "<contact_id>", "dateAdded": "2026-07-15T20:45:00.000Z" }}body is required, and userId is optional. The response is the updated { "note": { ... } }.
curl -X PUT https://services.smbcrm.com/calendars/appointments/<event_id>/notes/<note_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "body": "Customer asked about parking and bike racks" }'curl -X DELETE https://services.smbcrm.com/calendars/appointments/<event_id>/notes/<note_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "success": true }Blocked slots
Section titled “Blocked slots”A blocked slot reserves time on a calendar so nobody can book it. Reading blocked slots
needs calendars/events.readonly. Creating and changing them needs calendars/events.write.
The query parameters are the same as for listing appointments:
locationId, startTime, and endTime (Unix milliseconds) are required, and you send at
least one of calendarId, userId, or groupId.
curl "https://services.smbcrm.com/calendars/blocked-slots?locationId=<location_id>&calendarId=<calendar_id>&startTime=1784073600000&endTime=1784678400000" \ -H "Authorization: Bearer <token>" \ -H "Version: v3"The response is { "events": [...] }, with the same event fields as the appointment list.
| Field | Description |
|---|---|
locationId |
Your account/location ID. Required. |
calendarId |
The calendar to block. Required. Set either calendarId or assignedUserId, not both. |
assignedUserId |
The user to block. Set either calendarId or assignedUserId, not both. |
title |
Title of the blocked slot. |
startTime, endTime |
The blocked range. |
curl -X POST https://services.smbcrm.com/calendars/events/block-slots \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "locationId": "<location_id>", "calendarId": "<calendar_id>", "title": "Team offsite", "startTime": "2026-07-17T14:00:00.000Z", "endTime": "2026-07-17T22:00:00.000Z" }'{ "id": "<event_id>", "locationId": "<location_id>", "calendarId": "<calendar_id>", "title": "Team offsite", "startTime": "2026-07-17T14:00:00.000Z", "endTime": "2026-07-17T22:00:00.000Z"}The body takes the same fields as create. The response has the same shape as the create
response, with status 201.
curl -X PUT https://services.smbcrm.com/calendars/events/block-slots/<event_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "locationId": "<location_id>", "calendarId": "<calendar_id>", "title": "Team offsite", "startTime": "2026-07-17T15:00:00.000Z", "endTime": "2026-07-17T22:00:00.000Z" }'To remove a blocked slot, call DELETE /calendars/events/{eventId} as you would for an
appointment.
Services
Section titled “Services”Services are the bookable offerings behind service_booking calendars. A service has a
price, a duration, assigned staff, and optional variations. Reading the catalog and service
locations needs calendars.readonly. Changing them needs calendars.write.
Service catalog
Section titled “Service catalog”| Query parameter | Description |
|---|---|
locationId |
Your account/location ID. Required. |
serviceCategoryId |
Limit results to one service category. |
isPrivate |
true returns only private services, false only public ones. Leave it out to return all services. |
curl "https://services.smbcrm.com/calendars/services/catalog?locationId=<location_id>" \ -H "Authorization: Bearer <token>" \ -H "Version: v3"The response is { "services": [...] }.
locationId, name, slug, and staff are required. staff is a list of
{ "id": "<staff_id>" } objects and needs at least one entry.
| Field | Description |
|---|---|
description, eventColor, coverImage |
Description, event color as hex, and cover image URL. |
serviceCategoryId |
Service category. The default category is used when you leave it out. |
payment |
amount, deposit, and depositType (percentage or amount). The default amount is 0, and the currency comes from your Service Global Settings. |
serviceDuration, serviceDurationUnit |
Appointment length. The unit is mins or hours. |
preBuffer, preBufferUnit |
Time before the appointment. The unit is mins or hours. |
postBuffer, postBufferUnit |
Time after the appointment. The unit is mins or hours. |
isPrivate |
Hide the service from public booking pages. |
formId |
Custom form shown on the booking page when only one service is selected. |
variations |
Variations of the service. Pass an empty array for none. Each item needs name and can set the duration, buffer, and payment fields above. |
curl -X POST https://services.smbcrm.com/calendars/services/catalog \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "locationId": "<location_id>", "name": "Hair Styling", "slug": "hair-styling", "staff": [{ "id": "<staff_id>" }], "serviceDuration": 30, "serviceDurationUnit": "mins", "payment": { "amount": 50, "deposit": 20, "depositType": "amount" }, "variations": [] }'{ "service": { "id": "<service_id>", "locationId": "<location_id>", "name": "Hair Styling", "slug": "hair-styling", "payment": { "amount": 50, "deposit": 20, "depositType": "amount" }, "serviceDuration": 30, "serviceDurationUnit": "mins", "isPrivate": false, "variations": [], "staff": [{ "id": "<staff_id>" }] }}curl https://services.smbcrm.com/calendars/services/catalog/<service_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3"The response is { "service": { ... } }.
All fields are optional. The body takes the same fields as create, except locationId. In
variations, an empty array removes all variations. Include an id to update an existing
variation, or leave it out to add a new one.
curl -X PUT https://services.smbcrm.com/calendars/services/catalog/<service_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "name": "Hair Styling and Blow Dry", "serviceDuration": 45 }'The response is the updated { "service": { ... } }.
curl -X DELETE https://services.smbcrm.com/calendars/services/catalog/<service_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3"The response is { "success": true } with an optional message.
Service locations
Section titled “Service locations”locationId is required.
curl "https://services.smbcrm.com/calendars/services/locations?locationId=<location_id>" \ -H "Authorization: Bearer <token>" \ -H "Version: v3"The response is { "serviceLocations": [...] }.
locationId, name, and slug are required. Optional fields are phone, address,
coverImage, and locationType (offline or ask_booker). Use a full street address
when locationType is offline, and a label for the booker when it is ask_booker.
curl -X POST https://services.smbcrm.com/calendars/services/locations \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "locationId": "<location_id>", "name": "Midtown Studio", "slug": "midtown-studio", "address": "789 5th Avenue, Floor 3, New York, NY 10022", "locationType": "offline" }'{ "id": "<service_location_id>", "locationId": "<location_id>", "name": "Midtown Studio", "slug": "midtown-studio", "isActive": true, "isPrivate": false, "locationType": "offline", "address": "789 5th Avenue, Floor 3, New York, NY 10022"}curl https://services.smbcrm.com/calendars/services/locations/<service_location_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3"The response is the service location object shown above.
All fields are optional: name, slug, phone, address, coverImage, and
locationType.
curl -X PUT https://services.smbcrm.com/calendars/services/locations/<service_location_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "phone": "+15125550142" }'The response is the updated service location.
curl -X DELETE https://services.smbcrm.com/calendars/services/locations/<service_location_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3"The response is { "success": true } with an optional message.
Service bookings
Section titled “Service bookings”A service booking is an appointment made against one or more services. Reading bookings
needs calendars/events.readonly. Creating, changing, and deleting them needs
calendars/events.write.
| Query parameter | Description |
|---|---|
locationId |
Your account/location ID. Required. |
startTime |
Start of the range as a Unix timestamp in milliseconds, sent as a string. Required. |
endTime |
End of the range as a Unix timestamp in milliseconds, sent as a string. Required. |
timezone |
Timezone for the results. |
serviceLocationId |
Limit results to one service location. |
curl "https://services.smbcrm.com/calendars/services/bookings?locationId=<location_id>&startTime=1784073600000&endTime=1784678400000" \ -H "Authorization: Bearer <token>" \ -H "Version: v3"The response is { "bookings": [...] }.
locationId, contactId, startTime, endTime, timezone, and services are required.
Each item in services takes an id (required), staffId, position, and addOns. Each
add-on takes an id (required), quantity, and duration in minutes.
Optional fields are serviceLocationId (the default service location is used when you
leave it out), meetingLocation (required when the service location is ask_booker),
title, and status (confirmed or new; the status set in your Service Global Settings
is used when you leave it out).
Two optional query parameters, both false by default, relax validation:
overrideAvailabilityskips the time slot validation, including the checks thatskipSchedulingNoticecovers.skipSchedulingNoticeignores the minimum scheduling notice and date range.
curl -X POST https://services.smbcrm.com/calendars/services/bookings \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "locationId": "<location_id>", "contactId": "<contact_id>", "startTime": "2026-07-15T20:00:00.000Z", "endTime": "2026-07-15T20:30:00.000Z", "timezone": "America/Chicago", "services": [{ "id": "<service_id>", "staffId": "<staff_id>" }], "status": "confirmed" }'{ "bookingId": "<booking_id>", "locationId": "<location_id>", "contactId": "<contact_id>", "serviceLocationId": "<service_location_id>", "title": "Hair Styling", "startTime": "2026-07-15T20:00:00.000Z", "endTime": "2026-07-15T20:30:00.000Z", "services": [ { "id": "<service_id>", "serviceStaffId": "<staff_id>", "serviceStartTime": "2026-07-15T20:00:00.000Z", "serviceEndTime": "2026-07-15T20:30:00.000Z" } ], "timezone": "America/Chicago", "status": "confirmed", "deleted": false}The response can also include a messages array with informational or warning messages,
such as a note that a meeting location was ignored for a service location that doesn’t use
one.
curl https://services.smbcrm.com/calendars/services/bookings/<booking_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3"The response is the booking object shown above.
All fields are optional: serviceLocationId, meetingLocation, title, status,
startTime, endTime, timezone, and services. status accepts confirmed,
cancelled, invalid, new, showed, or noshow. If you send services, they replace
the services on the booking. The overrideAvailability and skipSchedulingNotice query
parameters work as they do on create.
curl -X PUT https://services.smbcrm.com/calendars/services/bookings/<booking_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3" \ -H "Content-Type: application/json" \ -d '{ "status": "showed" }'The response is the updated booking.
curl -X DELETE https://services.smbcrm.com/calendars/services/bookings/<booking_id> \ -H "Authorization: Bearer <token>" \ -H "Version: v3"{ "success": true, "message": "Service booking deleted successfully" }Related
Section titled “Related”- Contacts: the
contactIdyou pass when booking an appointment. - Users: the
userIdvalues for team members, appointment owners, and schedules. - Opportunities & Pipelines: track a booked appointment through a sales pipeline.
- Workflows: automate reminders and follow-ups around bookings.
