Skip to content

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.

GET/calendars/

List the calendars configured in your SMBcrm account/location.

scope calendars.readonlyauth Location token or PIT

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.

Terminal window
curl "https://services.smbcrm.com/calendars/?locationId=<location_id>" \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{
"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.

GET/calendars/{calendarId}

Fetch a single calendar by ID.

scope calendars.readonlyauth Location token or PIT
Terminal window
curl https://services.smbcrm.com/calendars/<calendar_id> \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{
"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 }
]
}
}
POST/calendars/

Create a calendar in your SMBcrm account/location.

scope calendars.writeauth Location token or PIT

locationId and name are required. The remaining fields configure the calendar’s type, team, meeting lengths, booking limits, and booking form.

Terminal window
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"
}'
200 OK
{
"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 take userId (required), priority (0, 0.5, or 1; default 0.5; used for round robin), isPrimary, and locationConfigurations. isPrimary is required on collective calendars, and only one member can be primary.
  • locationConfigurations[] items take kind (required) and location. kind is one of custom, zoom_conference, google_conference, inbound_call, outbound_call, physical, booker, or ms_teams_conference. Event calendars don’t support zoom_conference, google_conference, or ms_teams_conference, and location doesn’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 a meetingId for each configuration, which you can pass as meetingLocationId when you book an appointment.
  • durationOptions[] accepts up to 3 items. Each needs duration, durationUnit (mins or hours), isDefault, and order (1-based display order). Exactly one item must have isDefault set to true. slotDuration and slotDurationUnit remain the single-length setting. To get slots for one of the lengths, pass duration to free slots.
  • recurring takes freq (DAILY, WEEKLY, or MONTHLY), count (up to 24), bookingOption (skip, continue, or book_next, which sets what happens when a recurring slot is unavailable), and bookingOverlapDefaultStatus (confirmed or new).

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).

PUT/calendars/{calendarId}

Update settings on an existing calendar.

scope calendars.writeauth Location token or PIT

No fields are required. The update body takes the fields in the table above, except locationId, calendarType, and slotBufferUnit.

Terminal window
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 }'
200 OK
{
"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.

Request body
{
"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 }
]
}
DELETE/calendars/{calendarId}

Permanently delete a calendar.

scope calendars.writeauth Location token or PIT
Terminal window
curl -X DELETE https://services.smbcrm.com/calendars/<calendar_id> \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{ "success": true }

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: wday for a recurring weekday, or date for one specific date.
  • day: for wday rules, one of sunday through saturday.
  • date: for date rules, a YYYY-MM-DD date.
  • intervals: a list of time windows, each with from and to in 24-hour HH:MM format.

Use these endpoints to set the availability of a single event calendar.

GET/calendars/schedules/event-calendar/{calendarId}

Fetch the availability schedule of an event calendar.

scope calendars.readonlyauth Location token or PIT
Terminal window
curl https://services.smbcrm.com/calendars/schedules/event-calendar/<calendar_id> \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{
"schedule": {
"calendarId": "<calendar_id>",
"timezone": "America/Chicago",
"rules": [
{ "type": "wday", "day": "monday", "intervals": [{ "from": "09:00", "to": "17:00" }] }
]
}
}
POST/calendars/schedules/event-calendar/{calendarId}

Create the availability schedule of an event calendar.

scope calendars.writeauth Location token or PIT

rules and timezone are required.

Terminal window
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" }] }
]
}'
201 Created
{
"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" }] }
]
}
}
PUT/calendars/schedules/event-calendar/{calendarId}

Update the availability schedule of an event calendar.

scope calendars.writeauth Location token or PIT

Send rules, timezone, or both. Fields you leave out stay as they are. The response is the updated schedule.

Terminal window
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" }'

A user schedule sets the availability of one team member and can apply to several calendars.

POST/calendars/schedules

Create a schedule for a user.

scope calendars.writeauth Location token or PIT

locationId, userId, name, and timezone are required. rules and calendarIds are optional.

Terminal window
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>"]
}'
201 Created
{
"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
}
}
GET/calendars/schedules/search

Search the schedules of a user in a location.

scope calendars.readonlyauth Location token or PIT
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.
Terminal window
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.

GET/calendars/schedules/{id}

Fetch a schedule by ID.

scope calendars.readonlyauth Location token or PIT
Terminal window
curl https://services.smbcrm.com/calendars/schedules/<schedule_id> \
-H "Authorization: Bearer <token>" \
-H "Version: v3"

The response is { "schedule": { ... } }.

PUT/calendars/schedules/{id}

Update a schedule's name, rules, or timezone.

scope calendars.writeauth Location token or PIT

All fields are optional. Only the fields you send change.

Terminal window
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": { ... } }.

DELETE/calendars/schedules/{id}

Permanently delete a schedule and its rules.

scope calendars.writeauth Location token or PIT
Terminal window
curl -X DELETE https://services.smbcrm.com/calendars/schedules/<schedule_id> \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{ "success": true }
PUT/calendars/schedules/{id}/associations/{calendarId}

Add a calendar to a schedule.

scope calendars.writeauth Location token or PIT
Terminal window
curl -X PUT https://services.smbcrm.com/calendars/schedules/<schedule_id>/associations/<calendar_id> \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{ "success": true }
DELETE/calendars/schedules/{id}/associations/{calendarId}

Remove a calendar from a schedule.

scope calendars.writeauth Location token or PIT
Terminal window
curl -X DELETE https://services.smbcrm.com/calendars/schedules/<schedule_id>/associations/<calendar_id> \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{ "success": true }
GET/calendars/{calendarId}/free-slots

Get open booking slots for a calendar within a date range.

scope calendars.readonlyauth Location token or PIT

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.
Terminal window
curl "https://services.smbcrm.com/calendars/<calendar_id>/free-slots?startDate=1784073600000&endDate=1784419200000&timezone=America/Chicago" \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{
"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"]
}
}

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.

GET/calendars/groups

List the calendar groups in a location.

scope calendars/groups.readonlyauth Location token or PIT

locationId is required.

Terminal window
curl "https://services.smbcrm.com/calendars/groups?locationId=<location_id>" \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{
"groups": [
{
"id": "<group_id>",
"locationId": "<location_id>",
"name": "Sales Team",
"description": "Book time with our sales team",
"slug": "sales-team",
"isActive": true
}
]
}
POST/calendars/groups

Create a calendar group.

scope calendars/groups.writeauth Location token or PIT

locationId, name, description, and slug are required. isActive is optional. Check the slug with validate-slug first.

Terminal window
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"
}'
201 Created
{
"group": {
"id": "<group_id>",
"locationId": "<location_id>",
"name": "Sales Team",
"description": "Book time with our sales team",
"slug": "sales-team",
"isActive": true
}
}
POST/calendars/groups/validate-slug

Check whether a calendar group slug is available.

scope calendars/groups.writeauth Location token or PIT

Send locationId and slug. Run this before you create or rename a group.

Terminal window
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" }'
200 OK
{ "available": true }
PUT/calendars/groups/{groupId}

Update a calendar group's name, description, or slug.

scope calendars/groups.writeauth Location token or PIT

name, description, and slug are all required. The response is the updated { "group": { ... } }.

Terminal window
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"
}'
PUT/calendars/groups/{groupId}/status

Activate or deactivate a calendar group.

scope calendars/groups.writeauth Location token or PIT

isActive is required.

Terminal window
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 }'
200 OK
{ "success": true }
DELETE/calendars/groups/{groupId}

Delete a calendar group.

scope calendars/groups.writeauth Location token or PIT
Terminal window
curl -X DELETE https://services.smbcrm.com/calendars/groups/<group_id> \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{ "success": true }

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.

GET/calendars/{calendarId}/notifications

List the notification settings of a calendar.

scope calendars/events.readonlyauth Location token or PIT
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.
Terminal window
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.

POST/calendars/{calendarId}/notifications

Create one or more notification settings for a calendar.

scope calendars/events.writeauth Location token or PIT

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.
Terminal window
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" }]
}
]'
200 OK
[
{
"_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
}
]
GET/calendars/{calendarId}/notifications/{notificationId}

Fetch one notification setting.

scope calendars/events.readonlyauth Location token or PIT
Terminal window
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.

PUT/calendars/{calendarId}/notifications/{notificationId}

Update one notification setting.

scope calendars/events.writeauth Location token or PIT

The body takes the fields from the create table, all optional, plus deleted to soft-delete the setting.

Terminal window
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.

DELETE/calendars/{calendarId}/notifications/{notificationId}

Delete one notification setting.

scope calendars/events.writeauth Location token or PIT
Terminal window
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.

GET/calendars/events

List appointments and events for a calendar, user, or calendar group within a time range.

scope calendars/events.readonlyauth Location token or PIT
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.
Terminal window
curl "https://services.smbcrm.com/calendars/events?locationId=<location_id>&calendarId=<calendar_id>&startTime=1784073600000&endTime=1784678400000" \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{
"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.

GET/calendars/events/appointments/{eventId}

Fetch a single appointment by ID.

scope calendars/events.readonlyauth Location token or PIT

eventId is an event ID or the instance ID of one occurrence of a recurring appointment.

Terminal window
curl https://services.smbcrm.com/calendars/events/appointments/<event_id> \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{
"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"
}
}
POST/calendars/events/appointments

Book a new appointment on a calendar.

scope calendars/events.writeauth Location token or PIT

calendarId, locationId, contactId, and startTime are required. endTime is optional.

Terminal window
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"
}'
200 OK
{
"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.
PUT/calendars/events/appointments/{eventId}

Reschedule or update an existing appointment.

scope calendars/events.writeauth Location token or PIT

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.

Terminal window
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"
}'
200 OK
{
"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"
}
DELETE/calendars/events/{eventId}

Permanently delete an appointment or event.

scope calendars/events.writeauth Location token or PIT

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.

Terminal window
curl -X DELETE https://services.smbcrm.com/calendars/events/<event_id> \
-H "Authorization: Bearer <token>" \
-H "Version: v3" \
-H "Content-Type: application/json" \
-d '{}'
201 Created
{ "succeeded": true }

Notes attach free text to an appointment. Reading them needs calendars/events.readonly. Writing them needs calendars/events.write.

GET/calendars/appointments/{appointmentId}/notes

List the notes on an appointment.

scope calendars/events.readonlyauth Location token or PIT

limit and offset are required. When hasMore is true, request the next page with a larger offset.

Terminal window
curl "https://services.smbcrm.com/calendars/appointments/<event_id>/notes?limit=20&offset=0" \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{
"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
}
POST/calendars/appointments/{appointmentId}/notes

Add a note to an appointment.

scope calendars/events.writeauth Location token or PIT

body is required and can be up to 5,000 characters. userId is the author.

Terminal window
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>" }'
201 Created
{
"note": {
"id": "<note_id>",
"body": "Customer asked about parking",
"userId": "<user_id>",
"contactId": "<contact_id>",
"dateAdded": "2026-07-15T20:45:00.000Z"
}
}
PUT/calendars/appointments/{appointmentId}/notes/{noteId}

Update a note on an appointment.

scope calendars/events.writeauth Location token or PIT

body is required, and userId is optional. The response is the updated { "note": { ... } }.

Terminal window
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" }'
DELETE/calendars/appointments/{appointmentId}/notes/{noteId}

Delete a note from an appointment.

scope calendars/events.writeauth Location token or PIT
Terminal window
curl -X DELETE https://services.smbcrm.com/calendars/appointments/<event_id>/notes/<note_id> \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{ "success": true }

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.

GET/calendars/blocked-slots

List the blocked slots for a calendar, user, or calendar group within a time range.

scope calendars/events.readonlyauth Location token or PIT

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.

Terminal window
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.

POST/calendars/events/block-slots

Block time on a calendar.

scope calendars/events.writeauth Location token or PIT
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.
Terminal window
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"
}'
201 Created
{
"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"
}
PUT/calendars/events/block-slots/{eventId}

Update a blocked slot.

scope calendars/events.writeauth Location token or PIT

The body takes the same fields as create. The response has the same shape as the create response, with status 201.

Terminal window
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 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.

GET/calendars/services/catalog

List the services in a location.

scope calendars.readonlyauth Location token or PIT
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.
Terminal window
curl "https://services.smbcrm.com/calendars/services/catalog?locationId=<location_id>" \
-H "Authorization: Bearer <token>" \
-H "Version: v3"

The response is { "services": [...] }.

POST/calendars/services/catalog

Create a service.

scope calendars.writeauth Location token or PIT

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.
Terminal window
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": []
}'
201 Created
{
"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>" }]
}
}
GET/calendars/services/catalog/{serviceId}

Fetch a service by ID.

scope calendars.readonlyauth Location token or PIT
Terminal window
curl https://services.smbcrm.com/calendars/services/catalog/<service_id> \
-H "Authorization: Bearer <token>" \
-H "Version: v3"

The response is { "service": { ... } }.

PUT/calendars/services/catalog/{serviceId}

Update a service.

scope calendars.writeauth Location token or PIT

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.

Terminal window
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": { ... } }.

DELETE/calendars/services/catalog/{serviceId}

Delete a service.

scope calendars.writeauth Location token or PIT
Terminal window
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.

GET/calendars/services/locations

List the service locations in a location.

scope calendars.readonlyauth Location token or PIT

locationId is required.

Terminal window
curl "https://services.smbcrm.com/calendars/services/locations?locationId=<location_id>" \
-H "Authorization: Bearer <token>" \
-H "Version: v3"

The response is { "serviceLocations": [...] }.

POST/calendars/services/locations

Create a service location.

scope calendars.writeauth Location token or PIT

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.

Terminal window
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"
}'
201 Created
{
"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"
}
GET/calendars/services/locations/{serviceLocationId}

Fetch a service location by ID.

scope calendars.readonlyauth Location token or PIT
Terminal window
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.

PUT/calendars/services/locations/{serviceLocationId}

Update a service location.

scope calendars.writeauth Location token or PIT

All fields are optional: name, slug, phone, address, coverImage, and locationType.

Terminal window
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.

DELETE/calendars/services/locations/{serviceLocationId}

Delete a service location.

scope calendars.writeauth Location token or PIT
Terminal window
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.

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.

GET/calendars/services/bookings

List the service bookings in a location within a time range.

scope calendars/events.readonlyauth Location token or PIT
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.
Terminal window
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": [...] }.

POST/calendars/services/bookings

Create a service booking.

scope calendars/events.writeauth Location token or PIT

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:

  • overrideAvailability skips the time slot validation, including the checks that skipSchedulingNotice covers.
  • skipSchedulingNotice ignores the minimum scheduling notice and date range.
Terminal window
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"
}'
201 Created
{
"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.

GET/calendars/services/bookings/{bookingId}

Fetch a service booking by ID.

scope calendars/events.readonlyauth Location token or PIT
Terminal window
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.

PUT/calendars/services/bookings/{bookingId}

Update a service booking.

scope calendars/events.writeauth Location token or PIT

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.

Terminal window
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.

DELETE/calendars/services/bookings/{bookingId}

Delete a service booking.

scope calendars/events.writeauth Location token or PIT
Terminal window
curl -X DELETE https://services.smbcrm.com/calendars/services/bookings/<booking_id> \
-H "Authorization: Bearer <token>" \
-H "Version: v3"
200 OK
{ "success": true, "message": "Service booking deleted successfully" }
  • Contacts: the contactId you pass when booking an appointment.
  • Users: the userId values 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.