Create, update, organise, notify, and complete bookings across MotorDesk calendars.
Before offering a customer a time, use GET /appointments/availability to get the dates and times that can actually be booked. It uses the same engine as the dealer's own booking pages, so it accounts for opening hours, appointment duration, how many bookings are allowed per hour and per day, the minimum notice required, existing appointments, and any holiday closures.
Pass ?booking= for a single booking type (ids come from /reference/booking-types), or omit it to get every enabled type. Add ?date=YYYY-MM-DD to check one date - including a date beyond the type's normal booking window, up to 400 days ahead.
GET /2.0/appointments/availability?booking=vehicle_test_drive&date=2026-09-01
{
"success": true,
"data": [
{
"booking": "vehicle_test_drive",
"name": "Test Drive",
"calendar": "Primary Calendar",
"duration_minutes": 30,
"dates": [ { "date": "2026-09-01", "slots": ["09:00", "10:00", "14:00"] } ]
}
]
}
Slot times are returned as HH:MM and can be passed straight to POST /appointments as time. Dates with nothing available are omitted, so an empty dates array simply means there is nothing bookable in the window.
Two things are worth knowing:
POST /appointments does not require a free slot - staff can book any time from the dashboard, and the API behaves the same way. Check availability when you are offering times to a customer; skip it when you are recording an appointment a member of staff has already arranged.Generated from the OpenAPI schema. Always matches the live API.
| Attribute | Type | Required | Description | Example |
|---|---|---|---|---|
id | integer | - | Appointment id. | |
lead | integer or null | - | Associated lead id, or null. | |
contact | integer or null | - | Associated contact id, or null. | |
type | enum | - | Appointment type. | |
calendar | string | - | Calendar name the appointment is on. | |
booking | string | - | Booking type id (see /reference/booking-types). | |
date | string (date) | - | Appointment date (YYYY-MM-DD). | |
time | string | - | 24-hour start time (HH:MM:SS). | |
purpose | string | - | Short purpose/title. | |
duration | integer or null | - | Duration in the unit given by duration_type, or null. | |
duration_type | enum | - | Duration unit: m=minutes, h=hours. | |
note | string | - | Free-text note. | |
user | array of integer | - | Staff user ids the appointment is assigned to. | |
vehicle | integer or null | - | Stock vehicle id associated as the vehicle of interest (via the lead), or null. | |
persist | boolean | - | Whether the appointment stays visible after its date passes. | |
done | boolean | - | Whether the appointment is marked done. | |
created | integer | - | Unix timestamp the appointment was created. |
{
"id": 0,
"lead": 0,
"contact": 0,
"type": "appointment",
"calendar": "",
"booking": "",
"date": "2026-01-01",
"time": "",
"purpose": "",
"duration": 0,
"duration_type": "m",
"note": "",
"user": [
0
],
"vehicle": 0,
"persist": false,
"done": false,
"created": 0
}GET /2.0/appointments · scope appointments:read
List appointments with pagination and optional filters (date range, calendar, booking type, etc.).
| Parameter | In | Type | Required | Description | Example |
|---|---|---|---|---|---|
page | query | integer | - | Page number, starting at 1 (offset pagination). | |
per_page | query | integer | - | Results per page (maximum 500). | |
lead | query | integer | - | Filter by lead id. | |
contact | query | integer | - | Filter by contact id. | |
vehicle | query | integer | - | Filter to appointments associated with this vehicle id, directly or via a vehicle-associated lead. | |
type | query | enum | - | Filter by type. | |
calendar | query | string | - | Filter by calendar name, as GET /appointments/calendars lists them. "Primary Calendar" (case-insensitive) matches appointments on the primary calendar. Exact match, or use % for wildcard matching (requires search:wildcard). | Primary Calendar |
booking | query | string | - | Filter by booking. | |
user | query | string | - | Filter by user. | |
persist | query | integer | - | Filter by persistence: 0 or 1. | |
done | query | integer | - | Filter by done state: 0 or 1. | |
date | query | string (date) | - | Only records on this date (YYYY-MM-DD). | |
date_from | query | string (date) | - | Only records on or after this date (YYYY-MM-DD). | |
date_to | query | string (date) | - | Only records on or before this date (YYYY-MM-DD). | |
cursor | query | string | - | Keyset pagination cursor from a previous response's meta.pagination.next_cursor. When supplied, page/total are not returned. | |
fields | query | string | - | Comma-separated dot-paths to return only those fields, e.g. id,data.stock.price_channel. |
| Status | Description |
|---|---|
200 OK | Paginated appointments |
Failures use the standard error responses (4xx/5xx) with the shared error envelope.
{
"success": true,
"data": [
{
"id": 0,
"lead": 0,
"contact": 0,
"type": "appointment",
"calendar": "",
"booking": "",
"date": "2026-01-01",
"time": "",
"purpose": "",
"duration": 0,
"duration_type": "m",
"note": "",
"user": [
0
],
"vehicle": 0,
"persist": false,
"done": false,
"created": 0
}
],
"meta": {
"pagination": {
"page": 0,
"per_page": 0,
"total": 0,
"total_pages": 0,
"next_cursor": ""
}
}
}POST /2.0/appointments · scope appointments:write
Create an appointment for a lead or customer. date, time, purpose and user are required. An optional vehicle (or vehicle_tag) links the vehicle of interest, so the appointment becomes queryable with the vehicle filter. A lead is not required - with one the vehicle also joins that lead's vehicles of interest.
| Attribute | Type | Required | Description | Example |
|---|---|---|---|---|
type | enum | - | Appointment type. Numeric 0 (appointment) and 1 (reminder) are accepted as legacy aliases. | appointment |
lead | integer | - | Lead id the appointment belongs to. | 4009 |
contact | integer | - | Contact id the appointment belongs to. | 77684 |
date | string (date) | Yes | Appointment date (YYYY-MM-DD). | 2026-09-01 |
time | string | Yes | 24-hour start time (HH:MM). | 14:30 |
duration | integer | - | Duration, in the unit given by duration_type. | 30 |
duration_type | enum | - | Duration unit: m=minutes, h=hours. | m |
calendar | string | - | Primary Calendar, or a configured calendar from /appointments/calendars. Reads back exactly as the calendars endpoint names it, so the value round-trips. | Primary Calendar |
vehicle | integer | - | Create only: stock vehicle id of interest, making the appointment queryable with the vehicle filter. A lead is not required; when the appointment has one, the vehicle also joins that lead's vehicles of interest. Use this or vehicle_tag. | 85481 |
vehicle_tag | string | - | Create only: vehicle tag of interest, as an alternative to vehicle. | HbSDTp97 |
booking | string | - | Booking type id from /reference/booking-types, e.g. vehicle_test_drive. | vehicle_test_drive |
purpose | string | Yes | Short purpose/title for the appointment. | Test drive |
note | string | - | Free-text note. | Customer wants to view the BMW 3 Series. |
user | string | Yes | Staff user id (or array of ids) the appointment is assigned to. Each id must exist in this business (see /reference/staff-users); unknown ids are rejected. | 1 |
persist | boolean | - | Keep the appointment visible after its date passes. | false |
done | boolean | - | Whether the appointment is marked done. | false |
{
"type": "appointment",
"lead": 4009,
"contact": 77684,
"date": "2026-09-01",
"time": "14:30",
"duration": 30,
"duration_type": "m",
"calendar": "Primary Calendar",
"vehicle": 85481,
"vehicle_tag": "HbSDTp97",
"booking": "vehicle_test_drive",
"purpose": "Test drive",
"note": "Customer wants to view the BMW 3 Series.",
"user": 1,
"persist": false,
"done": false
}| Status | Description |
|---|---|
201 Created | Appointment created |
Failures use the standard error responses (4xx/5xx) with the shared error envelope.
{
"success": true,
"data": {
"id": 0,
"lead": 0,
"contact": 0,
"type": "appointment",
"calendar": "",
"booking": "",
"date": "2026-01-01",
"time": "",
"purpose": "",
"duration": 0,
"duration_type": "m",
"note": "",
"user": [
0
],
"vehicle": 0,
"persist": false,
"done": false,
"created": 0
}
}GET /2.0/appointments/{id} · scope appointments:read
Retrieve a single appointment by id.
| Parameter | In | Type | Required | Description | Example |
|---|---|---|---|---|---|
id | path | string | Yes | Resource identifier in the path. | |
fields | query | string | - | Comma-separated dot-paths to return only those fields, e.g. id,data.stock.price_channel. |
| Status | Description |
|---|---|
200 OK | Appointment |
Failures use the standard error responses (4xx/5xx) with the shared error envelope.
{
"success": true,
"data": {
"id": 0,
"lead": 0,
"contact": 0,
"type": "appointment",
"calendar": "",
"booking": "",
"date": "2026-01-01",
"time": "",
"purpose": "",
"duration": 0,
"duration_type": "m",
"note": "",
"user": [
0
],
"vehicle": 0,
"persist": false,
"done": false,
"created": 0
}
}PATCH /2.0/appointments/{id} · scope appointments:write
Update an appointment. Only the supplied fields are changed.
| Parameter | In | Type | Required | Description | Example |
|---|---|---|---|---|---|
id | path | string | Yes | Resource identifier in the path. |
| Attribute | Type | Required | Description | Example |
|---|---|---|---|---|
type | enum | - | Appointment type. Numeric 0 (appointment) and 1 (reminder) are accepted as legacy aliases. | appointment |
lead | integer | - | Lead id the appointment belongs to. | 4009 |
contact | integer | - | Contact id the appointment belongs to. | 77684 |
date | string (date) | - | Appointment date (YYYY-MM-DD). | 2026-09-01 |
time | string | - | 24-hour start time (HH:MM). | 14:30 |
duration | integer | - | Duration, in the unit given by duration_type. | 30 |
duration_type | enum | - | Duration unit: m=minutes, h=hours. | m |
calendar | string | - | Primary Calendar, or a configured calendar from /appointments/calendars. Reads back exactly as the calendars endpoint names it, so the value round-trips. | Primary Calendar |
vehicle | integer | - | Create only: stock vehicle id of interest, making the appointment queryable with the vehicle filter. A lead is not required; when the appointment has one, the vehicle also joins that lead's vehicles of interest. Use this or vehicle_tag. | 85481 |
vehicle_tag | string | - | Create only: vehicle tag of interest, as an alternative to vehicle. | HbSDTp97 |
booking | string | - | Booking type id from /reference/booking-types, e.g. vehicle_test_drive. | vehicle_test_drive |
purpose | string | - | Short purpose/title for the appointment. | Test drive |
note | string | - | Free-text note. | Customer wants to view the BMW 3 Series. |
user | string | - | Staff user id (or array of ids) the appointment is assigned to. Each id must exist in this business (see /reference/staff-users); unknown ids are rejected. | 1 |
persist | boolean | - | Keep the appointment visible after its date passes. | false |
done | boolean | - | Whether the appointment is marked done. | false |
{
"type": "appointment",
"lead": 4009,
"contact": 77684,
"date": "2026-09-01",
"time": "14:30",
"duration": 30,
"duration_type": "m",
"calendar": "Primary Calendar",
"vehicle": 85481,
"vehicle_tag": "HbSDTp97",
"booking": "vehicle_test_drive",
"purpose": "Test drive",
"note": "Customer wants to view the BMW 3 Series.",
"user": 1,
"persist": false,
"done": false
}| Status | Description |
|---|---|
200 OK | Appointment updated |
Failures use the standard error responses (4xx/5xx) with the shared error envelope.
{
"success": true,
"data": {
"id": 0,
"lead": 0,
"contact": 0,
"type": "appointment",
"calendar": "",
"booking": "",
"date": "2026-01-01",
"time": "",
"purpose": "",
"duration": 0,
"duration_type": "m",
"note": "",
"user": [
0
],
"vehicle": 0,
"persist": false,
"done": false,
"created": 0
}
}DELETE /2.0/appointments/{id} · scope appointments:delete
Delete an appointment.
| Parameter | In | Type | Required | Description | Example |
|---|---|---|---|---|---|
id | path | string | Yes | Resource identifier in the path. |
| Status | Description |
|---|---|
200 OK | Appointment deleted |
Failures use the standard error responses (4xx/5xx) with the shared error envelope.
{
"success": true,
"data": {
"id": 0,
"deleted": false
}
}GET /2.0/appointments/availability · scope appointments:read
List the bookable dates and times for each enabled booking type, from the same engine the dashboard and website booking forms use. Optional ?booking=<type> for one type and ?date=YYYY-MM-DD for one date. Slot times are returned as HH:MM and can be passed straight to POST /appointments. Availability is advisory - creating an appointment does not require a free slot, matching the dashboard.
| Parameter | In | Type | Required | Description | Example |
|---|---|---|---|---|---|
booking | query | string | - | Booking type id from GET /reference/booking-types. Omit to return every enabled booking type. | vehicle_test_drive |
date | query | string (date) | - | Restrict the result to a single date. Dates beyond the booking type's normal calendar window can be checked this way, up to 400 days ahead. | 2026-09-01 |
fields | query | string | - | Comma-separated dot-paths to return only those fields, e.g. id,data.stock.price_channel. |
| Status | Description |
|---|---|
200 OK | Bookable dates and times per booking type |
Failures use the standard error responses (4xx/5xx) with the shared error envelope.
{
"success": true,
"data": [
{
"booking": "vehicle_test_drive",
"name": "Test Drive",
"calendar": "Primary Calendar",
"duration_minutes": 30,
"dates": [
{
"date": "2026-09-01",
"slots": [
"09:00",
"10:00",
"14:00"
]
}
]
}
]
}POST /2.0/appointments/{id}/notify · scope appointments:write
Send an appointment notification to the customer. type is one of book, change, cancel, remind, or message; only the message type uses subject (optional, defaults to "Appointment Message") and message (required).
| Parameter | In | Type | Required | Description | Example |
|---|---|---|---|---|---|
id | path | string | Yes | Resource identifier in the path. |
| Attribute | Type | Required | Description | Example |
|---|---|---|---|---|
type | enum | Yes | Notification type: book (confirmation), change (rescheduled), cancel (cancellation), remind (reminder), or message (a custom message). | remind |
subject | string | - | Subject line, used only for type=message; defaults to "Appointment Message" when omitted. | Your appointment with us |
message | string | - | Message body, required for type=message and ignored otherwise. | Looking forward to seeing you on Saturday at 2:30pm. |
{
"type": "remind",
"subject": "Your appointment with us",
"message": "Looking forward to seeing you on Saturday at 2:30pm."
}| Status | Description |
|---|---|
200 OK | Appointment notification sent |
Failures use the standard error responses (4xx/5xx) with the shared error envelope.
{
"success": true,
"data": {
"id": 0,
"notified": false,
"type": "book"
}
}POST /2.0/appointments/{id}/done · scope appointments:write
Mark an appointment as done.
| Parameter | In | Type | Required | Description | Example |
|---|---|---|---|---|---|
id | path | string | Yes | Resource identifier in the path. |
| Status | Description |
|---|---|
200 OK | Appointment marked done |
Failures use the standard error responses (4xx/5xx) with the shared error envelope.
{
"success": true,
"data": {
"id": 0,
"lead": 0,
"contact": 0,
"type": "appointment",
"calendar": "",
"booking": "",
"date": "2026-01-01",
"time": "",
"purpose": "",
"duration": 0,
"duration_type": "m",
"note": "",
"user": [
0
],
"vehicle": 0,
"persist": false,
"done": false,
"created": 0
}
}GET /2.0/appointments/calendars · scope appointments:read
List the configured appointment calendars.
| Parameter | In | Type | Required | Description | Example |
|---|---|---|---|---|---|
cursor | query | string | - | Keyset pagination cursor from a previous response's meta.pagination.next_cursor. When supplied, page/total are not returned. | |
fields | query | string | - | Comma-separated dot-paths to return only those fields, e.g. id,data.stock.price_channel. |
| Status | Description |
|---|---|
200 OK | Appointment calendars |
Failures use the standard error responses (4xx/5xx) with the shared error envelope.
{
"success": true,
"data": [
{
"name": "",
"colour": "success",
"primary": false
}
],
"meta": {
"pagination": {
"page": 0,
"per_page": 0,
"total": 0,
"total_pages": 0,
"next_cursor": ""
}
}
}PUT /2.0/appointments/calendars · scope appointments:write
Replace the entire appointment calendar set. Calendars not included are removed and their appointments unassigned. Use PATCH to merge instead.
| Attribute | Type | Required | Description | Example |
|---|---|---|---|---|
calendars | array of object | Yes | The calendars to apply. |
{
"calendars": [
{
"name": "Sales",
"colour": "primary",
"previous_name": "Showroom",
"previous_key": ""
}
]
}| Status | Description |
|---|---|
200 OK | Appointment calendars updated |
Failures use the standard error responses (4xx/5xx) with the shared error envelope.
PATCH /2.0/appointments/calendars · scope appointments:write
Merge the supplied calendars into the existing set, adding, updating, or renaming them without removing the others. Use PUT to replace the whole set.
| Attribute | Type | Required | Description | Example |
|---|---|---|---|---|
calendars | array of object | Yes | The calendars to apply. |
{
"calendars": [
{
"name": "Sales",
"colour": "primary",
"previous_name": "Showroom",
"previous_key": ""
}
]
}| Status | Description |
|---|---|
200 OK | Appointment calendars updated |
Failures use the standard error responses (4xx/5xx) with the shared error envelope.