Skip to the documentation

Public API

ChiDesk API v2 reference

A REST API over your ChiDesk account. Read the diary, catalogue and sales data, create and change records, and subscribe to events. Everything is JSON over HTTPS.

Building something new? Use v2. Version 1 still works and will be retired 3 months after the 2026.8 release, so plan to move existing integrations across. The v1 reference stays available here.

Overview

Every request goes to your own account's address. The account name is the subdomain, and ChiDesk works out which account you mean from the host you called - there is no account id to pass and no way to reach another account with your credentials.

Base URL

https://[account].chidesk.com/api/v2/

So for the account acme, the sites endpoint is https://acme.chidesk.com/api/v2/sites. Paths in this reference are written from /api/v2/ onwards; put your own account's address in front of them.

What every request needs

Requirements for every API request
Requirement Detail
HTTPS All calls must be over HTTPS.
Bearer token An Authorization: Bearer <token> header on every call except the token endpoints themselves. See Authentication.
Named parameters Every query parameter an endpoint declares must appear in the URL by name, even the optional-looking ones. See Conventions - this is the single most common reason a first call fails.
JSON Request bodies are JSON with Content-Type: application/json. Responses are JSON.
UTC Every date and time, in and out, is UTC.

Verify your setup

GET /api/v2/ping is the quickest way to confirm your account address, token and network path all work before you write any real code. It returns the account ChiDesk resolved your request to, so it also catches a wrong subdomain. It is not a data endpoint and has no category of its own.

curl -X GET \
  "https://acme.chidesk.com/api/v2/ping" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
{
  "status": "ok",
  "tenant": "acme",
  "utc": "2026-07-29T08:14:22.113Z"
}

Authentication

Ask your ChiDesk administrator to create an API credential for your integration. You get a client id and a secret. Exchange those for a bearer token, then send the token on every call.

Get a token

POST /api/v2/token with the client id and secret in the JSON body. This endpoint is the one place the secret is used; it is never sent on a normal call.

curl -X POST \
  "https://acme.chidesk.com/api/v2/token" \
  -H "Content-Type: application/json" \
  -d '{
        "clientId": "your-client-id",
        "secret": "your-client-secret"
      }'
var credentials = new
{
    clientId = "your-client-id",
    secret = "your-client-secret"
};

var http = new HttpClient();
http.BaseAddress = new Uri("https://acme.chidesk.com/");

var response = await http.PostAsync(
    "api/v2/token",
    new StringContent(
        JsonConvert.SerializeObject(credentials),
        Encoding.UTF8,
        "application/json"));

response.EnsureSuccessStatusCode();

var token = JObject.Parse(await response.Content.ReadAsStringAsync());
var accessToken = token["access_token"].ToString();

// Every sample in this reference assumes an http client set up like this.
http.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", accessToken);

A successful call returns:

Response
{
  "access_token": "zTn7Kq...redacted...",
  "token_type": "Bearer",
  "expires_in": 15768000,
  "expires_utc": "2027-01-29T08:14:22.113Z"
}
Token response fields
Field Type Description
access_token String The token to send on every subsequent call. Returned once and never again - ChiDesk stores only a hash of it, so if you lose it you must get a new one.
token_type String Always Bearer.
expires_in Int32 Seconds until the token expires.
expires_utc DateTime The moment it expires, UTC.

Tokens last 6 months. They are opaque - do not try to read anything out of them. Store the token, reuse it, and get a new one when it expires; do not call the token endpoint before every request.

Use the token

Send it as a bearer token on every call to every other endpoint.

curl -X GET \
  "https://acme.chidesk.com/api/v2/sites?state=1" \
  -H "Authorization: Bearer zTn7Kq...redacted..."
http.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", accessToken);

Refresh and rotate

Four endpoints manage the credential itself. All four authenticate with the client id and secret in the body, exactly like the token endpoint, and none of them takes a bearer token.

Credential lifecycle endpoints
Endpoint What it does
POST /api/v2/token Issues a token. Existing tokens keep working.
POST /api/v2/token/refresh Revokes the credential's active tokens and issues one fresh token. Use this to roll a token deliberately, or if you think one has leaked. Anything still using the old token starts failing immediately.
POST /api/v2/token/rotate-secret Issues a new secret and revokes active tokens. The new secret is returned once, in client_secret.
POST /api/v2/token/rotate-clientid Issues a new client id, returned in client_id.
curl -X POST \
  "https://acme.chidesk.com/api/v2/token/refresh" \
  -H "Content-Type: application/json" \
  -d '{
        "clientId": "your-client-id",
        "secret": "your-client-secret"
      }'
var response = await http.PostAsync(
    "api/v2/token/refresh",
    new StringContent(
        JsonConvert.SerializeObject(credentials),
        Encoding.UTF8,
        "application/json"));

// Any token issued earlier stops working the moment this returns.
response.EnsureSuccessStatusCode();

Allow-lists on a credential

A credential can be limited to certain endpoints, certain IP addresses, or both. Both lists are optional: an empty list means no restriction. Both are also matched loosely, and it is worth understanding exactly how before you rely on either as a security control.

Endpoint allow-list

One path per line. A request is allowed when the path it called ends with one of the lines - it is a suffix test, not an exact match. Two consequences matter in practice:

  • A line for a collection does not cover paths beneath it. api/v2/clients allows /api/v2/clients but not /api/v2/clients/contacts, because that path ends in contacts. Sub-routes need their own lines.
  • Routes that end in a record id cannot be listed, since the path ends with the id itself. A credential with a non-empty endpoint allow-list will be refused GET /api/v2/appointments/{appointmentId} even though api/v2/appointments is on the list. If your integration reads or writes individual records by id, leave the endpoint allow-list empty.
  • A blank line allows everything. The list is split on line breaks and empty entries are kept, and every path "ends with" an empty string - so one stray blank line, or a trailing newline that leaves an empty last entry, turns the whole allow-list off without any visible change to it. If you are relying on this list, check there are no empty lines in it.

Each category below lists the exact lines to add for the endpoints it documents.

IP allow-list

Security warning

The IP allow-list is matched as a substring of the stored list, not as a list of exact addresses. An entry of 10.0.0.1 therefore also admits 110.0.0.12, 210.0.0.19 and any other address that happens to contain those characters. Treat it as a coarse filter that narrows who can call you, not as a boundary that proves who did. Keep the secret secret and rotate it if in doubt - that is the control that actually holds.

Conventions

Read this one first

Every query parameter an endpoint declares must be present in the URL by name - including the nullable and optional-looking ones. ChiDesk picks the code that handles your request by matching the parameter names you sent. Leave one out and nothing matches - and you get a routing error rather than a message telling you what was missing. Send the name with an empty value when you do not want to filter on it: &stateId=&find=&clientId=.

Which routing error you see depends on the path, not on what you did wrong. If another method is registered on the same path - POST /api/v2/appointments alongside the GET, for instance - the path still exists once your request is ruled out, so the answer is 405. Where the path has only the one action, such as GET /api/v2/packages, you get 404. Treat 404 and 405 as the same signal: a parameter name is missing.

cURL
# Resolves. Every parameter is present by name, three of them empty.
curl "https://acme.chidesk.com/api/v2/appointments?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&stateId=&start=2026-08-01%2000:00&end=2026-08-08%2000:00&find=&clientId=" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"

# 405. stateId, find and clientId are missing, so this action does not match. The path
# itself still exists for POST, which is why the server answers 405 and not 404.
curl "https://acme.chidesk.com/api/v2/appointments?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&start=2026-08-01%2000:00&end=2026-08-08%2000:00" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"

Each endpoint's parameter table marks which parameters this applies to. Where a parameter is listed as Optional it carries a default and really can be left out entirely - that is the exception, and it is called out where it is true.

Where the record id goes

The shipped API is not consistent about this: some update endpoints take the record id in the path, others take it as a query parameter. There is no rule to infer - check the endpoint. We have documented it as it behaves rather than quietly changing it, because changing it would break the integrations already calling it.

Where the record id is supplied on update endpoints
Endpoint Id goes in
PUT /api/v2/appointments/{appointmentId} route
PUT /api/v2/vouchers/{voucherId} route
PUT /api/v2/accountingdocuments/{accountingDocumentId} route
PUT /api/v2/accountingentries/{accountingEntryId} route
PATCH /api/v2/classpass/{partner_id}/venues/{venue_id}/appointments/{appointment_id} route
PUT /api/v2/clients query clientId
PUT /api/v2/classes query classId
PUT /api/v2/classes/classbookings query classBookingId

Account-wide and site-scoped endpoints

A ChiDesk account can have several sites, and nearly every collection endpoint is scoped to one of them with a siteId. Two are account-wide and take no siteId: GET /api/v2/sites, which is how you discover the ids in the first place, and GET /api/v2/reports/sales, where siteId is an optional filter rather than a requirement. Start by listing your sites and keep the ids.

Endpoints that name a single record - /clients/{id}, /vouchers/{voucherId}, /accountingdocuments/{accountingDocumentId} and the rest - take no siteId either, and they are not restricted to one site: an id is unique across the whole account, so any record on the account can be read or changed through them. Restricting an integration to a single site's data is therefore not something these endpoints do for you. The same is true of PUT /api/v2/clients and PUT /api/v2/classes/classbookings, which identify the record by id and take no site at all.

Dates, ids and field names

Value conventions
Convention Detail
Time zone Every date and time is UTC, both in requests and in responses. Convert in your own code, using the site's time zone from GET /api/v2/sites/{id} if you need local times.
Date format yyyy-MM-dd HH:mm. Remember to URL-encode the space in a query string.
New records Use an empty Guid - 00000000-0000-0000-0000-000000000000 - for the primary key of anything you are creating.
Array parameters Repeat the name once per value: services=<guid>&services=<guid>.
Field names JSON fields are PascalCase, matching the object schemas. The token endpoints, the WebHook register body and the whole ClassPass category are the exceptions - they use snake_case, and are marked where they appear.
itemState is not a wildcard Wherever a list endpoint takes itemState (or state, or classType), the value is matched exactly against the record. So Active returns active records and Inactive returns inactive ones - but All does not return both. It looks for a state of 0, which no record has, and returns an empty list with a 200. Ask for one state at a time, and make two calls if you need both.

Errors

There are two different error shapes in this API, and which one you get depends on which endpoint you called. They are not interchangeable - if your error handling only understands one of them it will misread the other.

Authorization failures: 403 with a plain-text body

It is 403, not 401

Every authorization failure on a business endpoint returns 403 Forbidden with a plain-text body - even a missing or expired token, where most APIs would return 401. There is no WWW-Authenticate header and no JSON. Client libraries that watch for a 401 to trigger a re-authentication will never fire, and ones that expect JSON will fail to parse the body. Handle 403.

Response
HTTP/1.1 403 Forbidden
Content-Type: text/plain; charset=utf-8

Unauthorized - Invalid or expired token.

The body is the word Unauthorized, then a dash, then the reason. The reasons are fixed strings, so you can match on them:

Reasons returned with a 403
Body What went wrong
Unauthorized - No bearer token sent in request header. The Authorization header was missing or empty.
Unauthorized - Invalid or expired token. The token is not recognised, has been revoked by a refresh or rotate, or has passed its expiry. Get a new one.
Unauthorized - Invalid client IP address. The credential has an IP allow-list and your address did not match it.
Unauthorized - Invalid end-point. The credential has an endpoint allow-list and the path you called did not match any line. Check the suffix-matching rules in Allow-lists on a credential - this is the usual cause when a call to a record id is refused.
Unauthorized No reason given. Something failed while checking the credential.

Token endpoints: JSON with an error code

The token and rotate endpoints do not use the shape above. They return JSON with a single error field, and they do use 401.

Response
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8

{ "error": "invalid_client" }
Errors returned by the token endpoints
Status Body What went wrong
400 { "error": "invalid_request" } No body was sent, or it could not be read as JSON.
401 { "error": "invalid_client" } The client id or secret is wrong, or the credential is not active. The response deliberately does not say which.
500 { "error": "server_error" } Something failed while issuing the token. Nothing about the credential is ever echoed back in an error.

Validation and other statuses

Other statuses returned by business endpoints
Status When
400 A body was required and not sent, a field failed validation, or a documented limit was exceeded - for example a date range wider than the endpoint allows. The body carries the reason, and validation messages are in the account's own language, so match on the status rather than the text.
404 Usually not a missing record - it means no endpoint matched the request. Nine times out of ten a query parameter was left out; see Conventions.
405 The same problem as 404, on a path that also serves another method. Your request did not match an action, but the path itself still exists - so read it as a missing query parameter, not as "this endpoint does not accept this method". See Conventions.
409 The record already exists. Only POST /api/v2/webhooks uses this.
429 You are being throttled. See Rate limits.

A 200 does not always mean it worked

Several endpoints report failure, or do nothing at all, inside a 200. Each one is called out where it appears, but they are worth knowing about before you write your error handling:

Rate limits

The API allows 4 requests per second. The request that goes over the line is rejected with 429 Too Many Requests and starts a two-minute lockout, during which every further call to that endpoint is rejected immediately. Going over the limit therefore costs you two minutes, not one request - this is the part that catches people out, and V1 never mentioned it.

Rate limit values
Setting Value
Requests allowed 4 per 1 second
Lockout once exceeded 120 seconds
Counted per Endpoint, per calling IP address. Different endpoints have separate budgets, so a burst against one does not lock you out of another.
Window Fixed, not sliding. The one-second window opens on your first request and closes a second later regardless of what happens in between.
Response
HTTP/1.1 429 Too Many Requests
Content-Type: application/json; charset=utf-8

"Your request has been throttled. Please try again in 2 minutes."

Changed from v1

The v1 page published 8 requests per second. That is not the figure the API enforces - it belongs to a different throttle. Size your integration for 4 per second.

In practice: pull in batches rather than record by record, put a small delay between calls in any loop, and back off for two minutes rather than retrying immediately when you do see a 429. Where an endpoint accepts a date range or a record count, ask for a wide one once instead of many narrow ones.

Appointments

Read the diary, find bookable times, and create, re-state or cancel appointments. This is the largest category in the API and the one most integrations start with.

Endpoint allow-list lines for this category

If the credential you are using has a non-empty endpoint allow-list, these are the lines that reach the endpoints below. One per line, and remember that routes ending in a record id cannot be listed at all - see Allow-lists on a credential.

  • api/v2/appointments
  • api/v2/appointments/availabletimes
  • api/v2/appointments/availabledates
  • api/v2/appointments/validate
  • api/v2/appointments/setstate

GET /api/v2/appointments #

List appointments in a date range.

All six query parameters must appear in the URL by name, including the ones that look optional. Send them empty (stateId=&find=&clientId=) when you do not want to filter on them - omitting a name entirely means the request never reaches this action. Verified against a running server: you get a 405, not a 404, because POST /api/v2/appointments is registered on the same path, so the path still exists for another method once the GET is ruled out.

The range is capped at 90 days. A wider range returns 400 with the message "Maximum date range is 90 days".

Parameters

Parameters for GET /api/v2/appointments
Name Type In Required Description
siteId Guid query Required The site whose diary to read.
stateId Guid query Required Return only appointments in this state. Send empty for every state.
start DateTime query Required Start of the range, UTC.
end DateTime query Required End of the range, UTC. At most 90 days after start.
find String query Required Free-text filter on client or service name. Send empty for no filter.
clientId Guid query Required Return only this client's appointments. Send empty for every client.

Responses

Responses for GET /api/v2/appointments
Status Meaning
200 An array of AppointmentList objects. See the fields
400 The range exceeds 90 days.
403 Authorization failed. See Errors.
405 A query parameter was left out of the URL, so this action did not match. The path survives for POST, so the failure surfaces as 405 rather than 404.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/appointments?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&stateId=&start=2026-08-01%2000:00&end=2026-08-08%2000:00&find=&clientId=" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
// http is the HttpClient configured in Authentication.
var url = "api/v2/appointments"
        + "?siteId=" + siteId
        + "&stateId="
        + "&start=2026-08-01 00:00"
        + "&end=2026-08-08 00:00"
        + "&find="
        + "&clientId=";

var response = await http.GetAsync(url);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

GET /api/v2/appointments/{appointmentId} #

Fetch one appointment, including its services, employees and resources.

Parameters

Parameters for GET /api/v2/appointments/{appointmentId}
Name Type In Required Description
appointmentId Guid route Required The appointment to fetch.

Responses

Responses for GET /api/v2/appointments/{appointmentId}
Status Meaning
200 A single Appointment object. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/appointments/2c8f4a1e-77b9-4d3a-8e15-9f0a1b2c3d4e" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var response = await http.GetAsync("api/v2/appointments/" + appointmentId);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

GET /api/v2/appointments/availabletimes #

Find bookable start times on a given day for a selection of services or a package.

Array parameters repeat the name once per value: services=<guid>&services=<guid>.

Every parameter below must be present by name. Send empty values for the ones you are not using.

Parameters

Parameters for GET /api/v2/appointments/availabletimes
Name Type In Required Description
siteId Guid query Required The site to search.
day DateTime query Required The day to search, UTC.
services Guid array query Required Services to be booked together. Repeat the parameter per service.
packageId Guid query Required Search for a package instead of loose services. Send empty when using services.
employeeIds Guid array query Required Restrict to these employees. Repeat the parameter per employee; send none for any available employee.
clientId Guid query Required The client the booking is for. Send empty for an anonymous search.
serviceSequence Byte query Required How multiple services are laid out: 0 = none, 1 = sequential, 2 = same start time.

Responses

Responses for GET /api/v2/appointments/availabletimes
Status Meaning
200 An array of AvailableTime objects, each carrying the start time plus the employees and resources that would be assigned. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/appointments/availabletimes?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&day=2026-08-03%2000:00&services=9d7e6f50-1a2b-3c4d-5e6f-708192a3b4c5&packageId=&employeeIds=&clientId=&serviceSequence=1" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var url = "api/v2/appointments/availabletimes"
        + "?siteId=" + siteId
        + "&day=2026-08-03 00:00"
        + "&services=" + serviceId
        + "&packageId="
        + "&employeeIds="
        + "&clientId="
        + "&serviceSequence=1";

var response = await http.GetAsync(url);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

POST /api/v2/appointments/availabletimes #

Find bookable start times while taking an existing cart into account.

Same query parameters as the GET form. This variant exists only because the cart has to travel in the request body, which a GET cannot carry.

The body is a bare JSON array of AppointmentService objects - not an object wrapping the array.

Parameters

Parameters for POST /api/v2/appointments/availabletimes
Name Type In Required Description
siteId Guid query Required The site to search.
day DateTime query Required The day to search, UTC.
services Guid array query Required Services to be booked together. Repeat the parameter per service.
packageId Guid query Required Search for a package instead of loose services. Send empty when using services.
employeeIds Guid array query Required Restrict to these employees. Repeat the parameter per employee.
clientId Guid query Required The client the booking is for. Send empty for an anonymous search.
serviceSequence Byte query Required How multiple services are laid out: 0 = none, 1 = sequential, 2 = same start time.
cartItems AppointmentService array body Required Items already in the cart, so their time is treated as taken while searching.

Responses

Responses for POST /api/v2/appointments/availabletimes
Status Meaning
200 An array of AvailableTime objects. See the fields
403 Authorization failed. See Errors.

Example

curl -X POST \
  "https://acme.chidesk.com/api/v2/appointments/availabletimes?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&day=2026-08-03%2000:00&services=9d7e6f50-1a2b-3c4d-5e6f-708192a3b4c5&packageId=&employeeIds=&clientId=&serviceSequence=1" \
  -H "Authorization: Bearer $CHIDESK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[
        {
          "ServiceId": "4b5c6d7e-8f90-41a2-b3c4-d5e6f7081920",
          "Start": "2026-08-03 09:00",
          "Description": "Swedish Massage 60min"
        }
      ]'
var cart = new[]
{
    new
    {
        ServiceId = "4b5c6d7e-8f90-41a2-b3c4-d5e6f7081920",
        Start = "2026-08-03 09:00",
        Description = "Swedish Massage 60min"
    }
};

var url = "api/v2/appointments/availabletimes"
        + "?siteId=" + siteId
        + "&day=2026-08-03 00:00"
        + "&services=" + serviceId
        + "&packageId=&employeeIds=&clientId=&serviceSequence=1";

var content = new StringContent(
    JsonConvert.SerializeObject(cart), Encoding.UTF8, "application/json");

var response = await http.PostAsync(url, content);
response.EnsureSuccessStatusCode();

POST /api/v2/appointments/validate #

Re-check that every item in a cart is still available. Call this immediately before creating an appointment.

A cart that is no longer bookable still returns 200 - not a 4xx. Success is an empty response body; a failure is a 200 whose body is a comma-separated list of the items that went away, for example "Swedish Massage 60min is no longer available". Treat any non-empty body as a failure.

The body is a bare JSON array of AppointmentService objects.

Parameters

Parameters for POST /api/v2/appointments/validate
Name Type In Required Description
siteId Guid query Required The site the cart belongs to.
cartItems AppointmentService array body Required The cart to validate. Each item carries its Start, ServiceId or PackageId, and its chosen Employees and Resources.

Responses

Responses for POST /api/v2/appointments/validate
Status Meaning
200 Empty body - every item is still available. Non-empty body - the listed items are gone.
400 No cart items were sent.
403 Authorization failed. See Errors.

Example

curl -X POST \
  "https://acme.chidesk.com/api/v2/appointments/validate?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70" \
  -H "Authorization: Bearer $CHIDESK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[
        {
          "ServiceId": "4b5c6d7e-8f90-41a2-b3c4-d5e6f7081920",
          "Start": "2026-08-03 09:00",
          "Description": "Swedish Massage 60min",
          "Employees": [ { "EmployeeId": "a1b2c3d4-e5f6-4708-9a1b-2c3d4e5f6071" } ],
          "Resources": []
        }
      ]'
var content = new StringContent(
    JsonConvert.SerializeObject(cart), Encoding.UTF8, "application/json");

var response = await http.PostAsync(
    "api/v2/appointments/validate?siteId=" + siteId, content);

response.EnsureSuccessStatusCode();

// A 200 does not mean the cart is bookable - check the body.
var message = await response.Content.ReadAsStringAsync();
if (!String.IsNullOrWhiteSpace(message))
{
    // message lists the items that are no longer available.
}

GET /api/v2/appointments/availabledates #

List the days that have at least one bookable time for the given services or packages.

Use this to drive a calendar picker, then call availabletimes for the day the client chooses.

Parameters

Parameters for GET /api/v2/appointments/availabledates
Name Type In Required Description
siteId Guid query Required The site to search.
services Guid array query Required Services to look for. Repeat the parameter per service; send empty when searching packages.
packages Guid array query Required Packages to look for. Repeat the parameter per package; send empty when searching services.

Responses

Responses for GET /api/v2/appointments/availabledates
Status Meaning
200 An array of dates.
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/appointments/availabledates?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&services=9d7e6f50-1a2b-3c4d-5e6f-708192a3b4c5&packages=" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var url = "api/v2/appointments/availabledates"
        + "?siteId=" + siteId
        + "&services=" + serviceId
        + "&packages=";

var response = await http.GetAsync(url);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

POST /api/v2/appointments #

Create an appointment.

siteId travels in the query string; the appointment itself is the JSON body.

Use an empty Guid (00000000-0000-0000-0000-000000000000) for the primary keys of anything you are creating.

Parameters

Parameters for POST /api/v2/appointments
Name Type In Required Description
siteId Guid query Required The site to create the appointment at.
appointment Appointment body Required The appointment to create, including its AppointmentServices.

Responses

Responses for POST /api/v2/appointments
Status Meaning
200 An object of the form { "Message": "AppointmentId", "Value": "<guid>" } carrying the new appointment's id.
400 No body was sent, or the body failed validation. The response carries the model-state errors.
403 Authorization failed. See Errors.

Example

curl -X POST \
  "https://acme.chidesk.com/api/v2/appointments?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70" \
  -H "Authorization: Bearer $CHIDESK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "AppointmentId": "00000000-0000-0000-0000-000000000000",
        "ClientId": "7e8f9012-3a4b-4c5d-6e7f-8091a2b3c4d5",
        "Start": "2026-08-03 09:00",
        "AppointmentServices": [
          {
            "ServiceId": "4b5c6d7e-8f90-41a2-b3c4-d5e6f7081920",
            "Start": "2026-08-03 09:00",
            "Employees": [ { "EmployeeId": "a1b2c3d4-e5f6-4708-9a1b-2c3d4e5f6071" } ]
          }
        ]
      }'
var content = new StringContent(
    JsonConvert.SerializeObject(appointment), Encoding.UTF8, "application/json");

var response = await http.PostAsync(
    "api/v2/appointments?siteId=" + siteId, content);

response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();
// { "Message": "AppointmentId", "Value": "..." }

PUT /api/v2/appointments/{appointmentId} #

Update an existing appointment.

Send AppointmentId in the body as well as in the route. The record that gets loaded and saved is the one named by the body's AppointmentId, while the response echoes the id from the route - so if the two disagree you will update one appointment and be told about another. Keep them identical.

This is one of the endpoints that takes its id in the route. Several others take it in the query string instead; see Conventions.

Parameters

Parameters for PUT /api/v2/appointments/{appointmentId}
Name Type In Required Description
appointmentId Guid route Required The appointment to update. Must match AppointmentId in the body.
appointment Appointment body Required The full appointment. Fields you send overwrite the stored ones.

Responses

Responses for PUT /api/v2/appointments/{appointmentId}
Status Meaning
200 An object of the form { "Message": "AppointmentId", "Value": "<guid>" }.
400 No body was sent, or the body failed validation.
403 Authorization failed. See Errors.

Example

curl -X PUT \
  "https://acme.chidesk.com/api/v2/appointments/2c8f4a1e-77b9-4d3a-8e15-9f0a1b2c3d4e" \
  -H "Authorization: Bearer $CHIDESK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "AppointmentId": "2c8f4a1e-77b9-4d3a-8e15-9f0a1b2c3d4e",
        "Start": "2026-08-03 10:00"
      }'
// The body's AppointmentId decides which record is saved - keep it equal to the route id.
var content = new StringContent(
    JsonConvert.SerializeObject(appointment), Encoding.UTF8, "application/json");

var response = await http.PutAsync(
    "api/v2/appointments/" + appointment.AppointmentId, content);

response.EnsureSuccessStatusCode();

POST /api/v2/appointments/setstate #

Move an appointment to a different state, for example from Booked to Arrived.

Both ids travel in the query string; there is no request body.

State ids come from the Appointment states category.

Parameters

Parameters for POST /api/v2/appointments/setstate
Name Type In Required Description
appointmentId Guid query Required The appointment to re-state.
appointmentStateId Guid query Required The state to move it to.

Responses

Responses for POST /api/v2/appointments/setstate
Status Meaning
200 An object of the form { "Message": "AppointmentId", "Value": "<guid>" }.
403 Authorization failed. See Errors.

Example

curl -X POST \
  "https://acme.chidesk.com/api/v2/appointments/setstate?appointmentId=2c8f4a1e-77b9-4d3a-8e15-9f0a1b2c3d4e&appointmentStateId=b7c8d9e0-1f2a-4b3c-8d4e-5f6071829304" \
  -H "Authorization: Bearer $CHIDESK_TOKEN" \
  -H "Content-Length: 0"
var url = "api/v2/appointments/setstate"
        + "?appointmentId=" + appointmentId
        + "&appointmentStateId=" + appointmentStateId;

var response = await http.PostAsync(url, null);
response.EnsureSuccessStatusCode();

DELETE /api/v2/appointments/{appointmentId} #

Cancel and remove an appointment.

Parameters

Parameters for DELETE /api/v2/appointments/{appointmentId}
Name Type In Required Description
appointmentId Guid route Required The appointment to remove.

Responses

Responses for DELETE /api/v2/appointments/{appointmentId}
Status Meaning
200 The appointment was removed.
400 No appointment exists with that id - the body reads "Record does not exist."
403 Authorization failed. See Errors.

Example

curl -X DELETE \
  "https://acme.chidesk.com/api/v2/appointments/2c8f4a1e-77b9-4d3a-8e15-9f0a1b2c3d4e" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var response = await http.DeleteAsync(
    "api/v2/appointments/" + appointmentId);

response.EnsureSuccessStatusCode();

Clients

Read, search, create and update client records, check whether a client already exists before creating a duplicate, pull a client's appointment history, and verify a client's own account password.

A client whose record is not tied to a single site belongs to the whole account, and is returned by every site's list. So the same client can appear under more than one siteId - de-duplicate on ClientId, not on the pair.

Endpoint allow-list lines for this category

If the credential you are using has a non-empty endpoint allow-list, these are the lines that reach the endpoints below. One per line, and remember that routes ending in a record id cannot be listed at all - see Allow-lists on a credential.

  • api/v2/clients
  • api/v2/clients/emailexists
  • api/v2/clients/authenticate
  • api/v2/clients/mobileexists
  • api/v2/clients/contacts
  • api/v2/clients/appointmenthistory

GET /api/v2/clients #

List and search clients at a site.

Only siteId is required. The other three carry defaults and may be left out of the URL altogether - unlike GET /api/v2/appointments, where every name has to be present.

find is a starts-with match, not a contains match, and it is tried against first name, last name, "first last", client code, e-mail, mobile number and membership number. Searching for "smith" finds Smith but not Smithers-Jones under a first name of Anne; searching for "ann" finds Anne.

creationDate reads as a single day but behaves as a from-date: clients created on or after that moment are returned. Use it to poll for new clients since your last sync.

itemState is not a wildcard - see Conventions. Ask for Active or Inactive; All returns nothing.

Parameters

Parameters for GET /api/v2/clients
Name Type In Required Description
siteId Guid query Required The site whose clients to read. Account-wide clients are included as well.
itemState ItemStates query Optional Active (1, the default) or Inactive (2). Names and numbers are both accepted.
find String query Optional Starts-with filter over name, code, e-mail, mobile and membership number. Omit for no filter.
creationDate DateTime query Optional Return only clients created on or after this moment, UTC.

Responses

Responses for GET /api/v2/clients
Status Meaning
200 An array of ClientList objects. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/clients?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&itemState=Active&creationDate=2026-07-01%2000:00" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
// Poll for clients created since the last successful sync.
var url = "api/v2/clients"
        + "?siteId=" + siteId
        + "&itemState=Active"
        + "&creationDate=" + lastSyncUtc.ToString("yyyy-MM-dd HH:mm");

var response = await http.GetAsync(url);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

GET /api/v2/clients/{id} #

Fetch one client in full, including addresses, notes and account details.

This route takes no siteId. The client is found by its id alone, so a client at any site on the account can be read here - a list restricted to one site does not restrict this.

Parameters

Parameters for GET /api/v2/clients/{id}
Name Type In Required Description
id Guid route Required The client to fetch.

Responses

Responses for GET /api/v2/clients/{id}
Status Meaning
200 A single Client object. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/clients/7e8f9012-3a4b-4c5d-6e7f-8091a2b3c4d5" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var response = await http.GetAsync("api/v2/clients/" + clientId);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

GET /api/v2/clients/emailexists #

Look up a client by e-mail address. Call this before creating a client so you do not make a duplicate.

The response body is the matching client's id on its own, or the literal null - not an object, and not a 404. Read it as a nullable Guid.

The comparison is case-insensitive and surrounding whitespace is trimmed. Only Active clients are considered, and only ones with an e-mail address recorded.

If two clients share an address you get the first one, with no indication that there was more than one.

Parameters

Parameters for GET /api/v2/clients/emailexists
Name Type In Required Description
siteId Guid query Required The site to search, plus the account-wide clients.
email String query Required The e-mail address to look for.

Responses

Responses for GET /api/v2/clients/emailexists
Status Meaning
200 The client's id as a bare JSON string, or null when no Active client has that address.
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/clients/emailexists?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&email=jo%40example.com" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"

# 200 "7e8f9012-3a4b-4c5d-6e7f-8091a2b3c4d5"   - found
# 200 null                                     - no such client
var url = "api/v2/clients/emailexists"
        + "?siteId=" + siteId
        + "&email=" + Uri.EscapeDataString(email);

var response = await http.GetAsync(url);
response.EnsureSuccessStatusCode();

// The body is a bare guid or the literal null.
var body = await response.Content.ReadAsStringAsync();
Guid? existingClientId = JsonConvert.DeserializeObject<Guid?>(body);

GET /api/v2/clients/authenticate #

Check a client's own account password, for signing a client in to a portal or app you have built.

Security warning

This endpoint takes the client's password as a query-string parameter on a GET. Query strings are written to web-server logs, reverse-proxy and load-balancer logs, browser history and Referer headers, none of which are places a password may be stored - and the password is not hashed or encoded in transit beyond the TLS connection itself. Never call it from a browser or a mobile app directly; call it from your own server, over HTTPS only, and make sure your own logging does not record the URL. It is documented here because integrations already depend on it, not because it is a good way to sign a client in. If you are building something new, sign clients in against your own identity provider and use the client id from GET /api/v2/clients/emailexists to link the account.

The client is resolved by clientId AND email together - both must match the same Active client, and that client must have an e-mail address recorded. This verifies a password for a client you have already identified; it does not look a client up. Get the id from GET /api/v2/clients/emailexists first.

Success is 200 with an empty body. Failure is 401 with an empty body, and every kind of failure looks the same - wrong password, wrong e-mail for that id, unknown id, or an inactive client. That is deliberate; do not try to tell them apart.

This 401 is the one place in the API where 401 means something other than a credential problem. Your own bearer token is still fine - an authorization failure on this endpoint would have been a 403. Do not let a shared HTTP handler treat this 401 as "my token expired" and re-authenticate.

Leading and trailing whitespace is stripped from both the e-mail and the password before checking.

Passwords are held as PBKDF2 hashes. A client still on the older hash is migrated to PBKDF2 the first time they sign in successfully, so the first successful call for such a client also writes to their record.

Parameters

Parameters for GET /api/v2/clients/authenticate
Name Type In Required Description
clientId Guid query Required The client whose password is being checked.
email String query Required That same client's e-mail address. Must match the record.
password String query Required The password to check, in plain text. See the warning above.

Responses

Responses for GET /api/v2/clients/authenticate
Status Meaning
200 Empty body. The password is correct for that client.
401 Empty body. The credentials are not valid - which part was wrong is not reported. This is not a problem with your bearer token.
403 Authorization failed - your API credential, not the client's password. See Errors.

Example

# Server-side only. Never put this URL anywhere that logs it.
curl -s -o /dev/null -w "%{http_code}" -X GET \
  "https://acme.chidesk.com/api/v2/clients/authenticate?clientId=7e8f9012-3a4b-4c5d-6e7f-8091a2b3c4d5&email=jo%40example.com&password=$CLIENT_PASSWORD" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"

# 200 = valid, 401 = not valid
// Both values must be URL-encoded, and neither should be logged.
var url = "api/v2/clients/authenticate"
        + "?clientId=" + clientId
        + "&email=" + Uri.EscapeDataString(email)
        + "&password=" + Uri.EscapeDataString(password);

var response = await http.GetAsync(url);

if (response.StatusCode == HttpStatusCode.Unauthorized)
{
    // Wrong password, wrong e-mail for this id, or an inactive client.
    // This does NOT mean the bearer token is stale - that would be a 403.
    return false;
}

response.EnsureSuccessStatusCode();
return true;

GET /api/v2/clients/mobileexists #

Look up a client by mobile number, the same way emailexists looks one up by address.

The body is the client's id on its own, or the literal null.

The number is compared as text after trimming, so it has to be stored in the same format you send. There is no normalisation of country codes, spaces or leading zeros - +27821234567 does not match 0821234567.

Only Active clients with a mobile number recorded are considered.

Parameters

Parameters for GET /api/v2/clients/mobileexists
Name Type In Required Description
siteId Guid query Required The site to search, plus the account-wide clients.
mobile String query Required The mobile number to look for, in the same format it is stored in.

Responses

Responses for GET /api/v2/clients/mobileexists
Status Meaning
200 The client's id as a bare JSON string, or null.
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/clients/mobileexists?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&mobile=%2B27821234567" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var url = "api/v2/clients/mobileexists"
        + "?siteId=" + siteId
        + "&mobile=" + Uri.EscapeDataString(mobile);

var response = await http.GetAsync(url);
response.EnsureSuccessStatusCode();

var body = await response.Content.ReadAsStringAsync();
Guid? existingClientId = JsonConvert.DeserializeObject<Guid?>(body);

GET /api/v2/clients/contacts #

Build a marketing list: the clients who have agreed to be contacted, filtered by the channels they accepted.

This is not the client list with extra columns. Every client who has not agreed to receive marketing messages is excluded, always, whatever you pass - there is no parameter that turns that off. Use it as your marketing list and you inherit the account's consent record; use GET /api/v2/clients if you want every client.

The three channel filters are three-state. Leave one out to ignore that channel; send true for clients who accepted it; send false for clients who explicitly declined it. false is not the same as leaving it out.

find here is a contains match, not the starts-with match used by GET /api/v2/clients. The same term can return more rows here than there.

Ordered by last name.

Parameters

Parameters for GET /api/v2/clients/contacts
Name Type In Required Description
siteId Guid query Required The site whose clients to read, plus the account-wide clients.
itemState ItemStates query Optional Active (1, the default) or Inactive (2).
find String query Optional Contains filter over name, code, e-mail, mobile and membership number.
contactEmail Boolean query Optional true for clients who accepted e-mail, false for clients who declined it. Omit to ignore the channel.
contactSms Boolean query Optional true for clients who accepted SMS, false for clients who declined it. Omit to ignore the channel.
contactPhone Boolean query Optional true for clients who accepted phone calls, false for clients who declined them. Omit to ignore the channel.

Responses

Responses for GET /api/v2/clients/contacts
Status Meaning
200 An array of ClientContact objects - marketing opt-ins only. See the fields
403 Authorization failed. See Errors.

Example

# Everyone at this site who agreed to marketing and accepted e-mail.
curl -X GET \
  "https://acme.chidesk.com/api/v2/clients/contacts?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&contactEmail=true" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var url = "api/v2/clients/contacts"
        + "?siteId=" + siteId
        + "&contactEmail=true";

var response = await http.GetAsync(url);
response.EnsureSuccessStatusCode();

// Already filtered to marketing opt-ins - do not re-broaden this list.
var json = await response.Content.ReadAsStringAsync();

GET /api/v2/clients/appointmenthistory #

List one client's past and future appointments at a site, with the invoice each one was billed on.

The range is capped at 730 days - two years, not the 90 days most date-ranged endpoints allow. A wider range returns 400 with "Maximum date range is 730 days".

An appointment is returned only when it falls entirely inside the range: it must start at or after start and end at or before end. One that straddles either edge is left out, so widen the range slightly rather than aligning it exactly to a month boundary.

Scoped to the site as well as the client, so a client seen at two sites needs one call per site.

Parameters

Parameters for GET /api/v2/clients/appointmenthistory
Name Type In Required Description
clientId Guid query Required The client whose history to read.
siteId Guid query Required The site to read it at.
start DateTime query Required Start of the range, UTC.
end DateTime query Required End of the range, UTC. At most 730 days after start.

Responses

Responses for GET /api/v2/clients/appointmenthistory
Status Meaning
200 An array of AppointmentHistory objects. See the fields
400 The range exceeds 730 days.
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/clients/appointmenthistory?clientId=7e8f9012-3a4b-4c5d-6e7f-8091a2b3c4d5&siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&start=2025-01-01%2000:00&end=2026-08-01%2000:00" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var url = "api/v2/clients/appointmenthistory"
        + "?clientId=" + clientId
        + "&siteId=" + siteId
        + "&start=2025-01-01 00:00"
        + "&end=2026-08-01 00:00";

var response = await http.GetAsync(url);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

POST /api/v2/clients #

Create a client.

siteId travels in the query string; the client is the JSON body. Use an empty Guid for ClientId.

To give the client a portal password, send it in AccountPassword. ChiDesk hashes it and stores only the hash - the plain value is never written to the client record. Leave the field out and the client simply has no password.

On update the field is called NewAccountPassword instead. Sending AccountPassword to the PUT does not change the password.

Check for an existing client with GET /api/v2/clients/emailexists or /mobileexists first. Nothing here stops you creating a second record for the same person.

Parameters

Parameters for POST /api/v2/clients
Name Type In Required Description
siteId Guid query Required The site to create the client at.
client Client body Required The client to create. Set ClientId to an empty Guid.

Responses

Responses for POST /api/v2/clients
Status Meaning
200 An object of the form { "Message": "ClientId", "Value": "<guid>" } carrying the new client's id.
400 No body was sent, or the body failed validation. The response carries the model-state errors.
403 Authorization failed. See Errors.

Example

curl -X POST \
  "https://acme.chidesk.com/api/v2/clients?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70" \
  -H "Authorization: Bearer $CHIDESK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "ClientId": "00000000-0000-0000-0000-000000000000",
        "FirstName": "Jo",
        "LastName": "Naidoo",
        "Email": "jo@example.com",
        "MobilePhone": "+27821234567",
        "AccountPassword": "the-clients-own-password"
      }'
var content = new StringContent(
    JsonConvert.SerializeObject(client), Encoding.UTF8, "application/json");

var response = await http.PostAsync(
    "api/v2/clients?siteId=" + siteId, content);

response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();
// { "Message": "ClientId", "Value": "..." }

PUT /api/v2/clients #

Update an existing client.

The id goes in the query string as clientId, not in the path - see Where the record id goes. There is no PUT /api/v2/clients/{id}.

The record that is loaded and saved is the one named by the query's clientId. The body's ClientId is not what selects it.

To change the password, send the new value in NewAccountPassword - not AccountPassword, which is the create-time field: a value sent there on a PUT is written to the record as sent and does not change the password. Leave NewAccountPassword out to keep the existing password.

Fetch the client first and send it back with your changes applied. Fields you send overwrite the stored ones.

Parameters

Parameters for PUT /api/v2/clients
Name Type In Required Description
clientId Guid query Required The client to update.
client Client body Required The client, with your changes applied. NewAccountPassword sets a new portal password.

Responses

Responses for PUT /api/v2/clients
Status Meaning
200 An object of the form { "Message": "ClientId", "Value": "<guid>" }.
400 No body was sent, or the body failed validation.
403 Authorization failed. See Errors.

Example

curl -X PUT \
  "https://acme.chidesk.com/api/v2/clients?clientId=7e8f9012-3a4b-4c5d-6e7f-8091a2b3c4d5" \
  -H "Authorization: Bearer $CHIDESK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "ClientId": "7e8f9012-3a4b-4c5d-6e7f-8091a2b3c4d5",
        "FirstName": "Jo",
        "LastName": "Naidoo-Smith",
        "NewAccountPassword": "a-replacement-password"
      }'
// NewAccountPassword on update; AccountPassword only works on create.
var content = new StringContent(
    JsonConvert.SerializeObject(client), Encoding.UTF8, "application/json");

var response = await http.PutAsync(
    "api/v2/clients?clientId=" + clientId, content);

response.EnsureSuccessStatusCode();

Client custom forms

Read the answers clients have given on the account's custom forms - consultation cards, intake questionnaires, consent forms. V1 published this as Custom Forms.

Endpoint allow-list lines for this category

If the credential you are using has a non-empty endpoint allow-list, these are the lines that reach the endpoints below. One per line, and remember that routes ending in a record id cannot be listed at all - see Allow-lists on a credential.

  • api/v2/clientcustomforms

GET /api/v2/clientcustomforms #

List completed form submissions captured in a date range, with the answers on each.

The range is matched against when the form was captured, not against the date of any appointment it belongs to.

The end of the range is widened by one day before the query runs, so a form captured during the end date itself is included. Passing the same value for start and end returns that whole day rather than nothing.

There is no cap on the range here, and no filter beyond the dates - a wide range on a busy account returns a lot of data. Page it yourself by asking for shorter periods.

All three parameters are required by name: none of them carries a default.

Parameters

Parameters for GET /api/v2/clientcustomforms
Name Type In Required Description
siteId Guid query Required The site whose submissions to read.
start DateTime query Required Start of the capture-date range, UTC.
end DateTime query Required End of the capture-date range, UTC. Inclusive of the whole day.

Responses

Responses for GET /api/v2/clientcustomforms
Status Meaning
200 An array of ClientCustomForm objects, each carrying its answers. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/clientcustomforms?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&start=2026-07-01%2000:00&end=2026-07-31%2000:00" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var url = "api/v2/clientcustomforms"
        + "?siteId=" + siteId
        + "&start=2026-07-01 00:00"
        + "&end=2026-07-31 00:00";

var response = await http.GetAsync(url);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

Employees

List the staff at a site and read their rosters - shifts, leave and other time off. Employee ids are what you pass when you want an appointment with a particular person.

Endpoint allow-list lines for this category

If the credential you are using has a non-empty endpoint allow-list, these are the lines that reach the endpoints below. One per line, and remember that routes ending in a record id cannot be listed at all - see Allow-lists on a credential.

  • api/v2/employees
  • api/v2/employees/schedules

GET /api/v2/employees #

List the employees at a site.

Only siteId is required; itemState and find carry defaults and may be omitted.

This is every employee, not only the ones who can be booked. Whether a given employee can perform a given service is decided when you search for available times.

Parameters

Parameters for GET /api/v2/employees
Name Type In Required Description
siteId Guid query Required The site whose employees to read.
itemState ItemStates query Optional Active (1, the default) or Inactive (2).
find String query Optional Name filter. Omit for no filter.

Responses

Responses for GET /api/v2/employees
Status Meaning
200 An array of EmployeeList objects. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/employees?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&itemState=Active" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var response = await http.GetAsync(
    "api/v2/employees?siteId=" + siteId + "&itemState=Active");

response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

GET /api/v2/employees/schedules #

Read the roster for a site over a date range: who is on shift, and who is away.

The range is capped at 90 days. A wider range returns 400 with "Maximum date range is 90 days".

A schedule is returned when it overlaps the range at all, not only when it sits inside it - so a shift that started before start and ends inside it is included, and its own Start is before the range you asked for. Clip the times yourself if you are drawing a calendar.

This returns both working time and time off. Read the schedule type on each row rather than assuming a row means the employee is available.

includeClasses carries a default and may be omitted. Leave it out and class time is not part of the roster.

Parameters

Parameters for GET /api/v2/employees/schedules
Name Type In Required Description
siteId Guid query Required The site whose roster to read.
start DateTime query Required Start of the range, UTC.
end DateTime query Required End of the range, UTC. At most 90 days after start.
includeClasses Boolean query Optional Set true to include the time employees are committed to classes. Defaults to false.

Responses

Responses for GET /api/v2/employees/schedules
Status Meaning
200 An array of EmployeeSchedule objects. See the fields
400 The range exceeds 90 days.
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/employees/schedules?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&start=2026-08-01%2000:00&end=2026-08-31%2000:00&includeClasses=true" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var url = "api/v2/employees/schedules"
        + "?siteId=" + siteId
        + "&start=2026-08-01 00:00"
        + "&end=2026-08-31 00:00"
        + "&includeClasses=true";

var response = await http.GetAsync(url);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

Sites

List the branches on the account and read one in detail. Start here: almost every other endpoint needs a siteId, and this is the only way to discover them.

Endpoint allow-list lines for this category

If the credential you are using has a non-empty endpoint allow-list, these are the lines that reach the endpoints below. One per line, and remember that routes ending in a record id cannot be listed at all - see Allow-lists on a credential.

  • api/v2/sites

GET /api/v2/sites #

List the sites on the account.

One of only two endpoints in the API that are account-wide rather than site-scoped - it takes no siteId. The other is GET /api/v2/reports/sales.

state is genuinely optional here and may be omitted: it carries a default, so the request still resolves without it.

state is not a wildcard - see Conventions. Ask for Active or Inactive; All returns an empty list rather than every site.

Parameters

Parameters for GET /api/v2/sites
Name Type In Required Description
state ItemStates query Optional Which sites to return: Active (1, the default) or Inactive (2). Names and numbers are both accepted. All (0) returns nothing - it is not a wildcard.

Responses

Responses for GET /api/v2/sites
Status Meaning
200 An array of SiteList objects. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/sites?state=1" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var response = await http.GetAsync("api/v2/sites?state=1");
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

GET /api/v2/sites/{id} #

Fetch one site, including its address, contact details and trading hours.

Parameters

Parameters for GET /api/v2/sites/{id}
Name Type In Required Description
id Guid route Required The site to fetch.

Responses

Responses for GET /api/v2/sites/{id}
Status Meaning
200 A single Site object. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/sites/6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var response = await http.GetAsync("api/v2/sites/" + siteId);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

Time records

Read clock-in and clock-out records for staff, for payroll and attendance. This is what was actually worked, as distinct from the roster in Employees.

Endpoint allow-list lines for this category

If the credential you are using has a non-empty endpoint allow-list, these are the lines that reach the endpoints below. One per line, and remember that routes ending in a record id cannot be listed at all - see Allow-lists on a credential.

  • api/v2/timerecords

GET /api/v2/timerecords #

List clock-in and clock-out records in a date range.

The range is capped at 90 days. A wider range returns 400 with "Maximum date range is 90 days".

A record is returned when either its clock-in or its clock-out falls in the range, so a shift that ran over midnight at the edge of the range appears once, from the side that fell inside it.

The end of the range is widened by one day before the query runs, so records on the end date itself are included.

find is a contains match on the employee's first or last name.

find carries a default and may be omitted; siteId, start and end may not.

Records not tied to a single site are included whatever siteId you pass.

Ordered by clock-in time.

Parameters

Parameters for GET /api/v2/timerecords
Name Type In Required Description
siteId Guid query Required The site whose records to read.
start DateTime query Required Start of the range, UTC.
end DateTime query Required End of the range, UTC. At most 90 days after start.
find String query Optional Contains filter on the employee's first or last name.

Responses

Responses for GET /api/v2/timerecords
Status Meaning
200 An array of TimeRecordList objects. See the fields
400 The range exceeds 90 days.
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/timerecords?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&start=2026-07-01%2000:00&end=2026-07-31%2000:00" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var url = "api/v2/timerecords"
        + "?siteId=" + siteId
        + "&start=2026-07-01 00:00"
        + "&end=2026-07-31 00:00";

var response = await http.GetAsync(url);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

Services

The treatments a site sells, with their duration and price. Service ids are what you pass to the appointment search and to the appointment itself, so this is usually the second call an integration makes after listing sites.

Endpoint allow-list lines for this category

If the credential you are using has a non-empty endpoint allow-list, these are the lines that reach the endpoints below. One per line, and remember that routes ending in a record id cannot be listed at all - see Allow-lists on a credential.

  • api/v2/services

GET /api/v2/services #

List the services a site sells.

Only siteId is required. itemState, find and showPackageServices all carry defaults and may be left out.

Services that are components of a package - the individual treatments inside it, which are not sold on their own - are left out by default. Pass showPackageServices=true to see them as well.

A service with no site restriction on it belongs to the whole account and is returned for every siteId.

find is a contains match.

itemState is not a wildcard - see Conventions.

Parameters

Parameters for GET /api/v2/services
Name Type In Required Description
siteId Guid query Required The site whose services to read.
itemState ItemStates query Optional Active (1, the default) or Inactive (2).
find String query Optional Contains filter over the service's name, code and category.
showPackageServices Boolean query Optional Set true to include services that only exist inside a package. Defaults to false.

Responses

Responses for GET /api/v2/services
Status Meaning
200 An array of ServiceList objects, ordered by name. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/services?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&itemState=Active" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var response = await http.GetAsync(
    "api/v2/services?siteId=" + siteId + "&itemState=Active");

response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

Products

The retail stock a site carries, with cost, price and quantity on hand.

Endpoint allow-list lines for this category

If the credential you are using has a non-empty endpoint allow-list, these are the lines that reach the endpoints below. One per line, and remember that routes ending in a record id cannot be listed at all - see Allow-lists on a credential.

  • api/v2/products

GET /api/v2/products #

List the products a site carries.

Read the allowSale note carefully - the default is not "no filter". Leaving it out returns only the products that are NOT flagged for sale, which for most accounts is a short and surprising list. If you want the sellable retail range, pass allowSale=true explicitly. There is no way to ask for both in one call; make two.

Only siteId is required. itemState, find and allowSale all carry defaults and may be left out.

A product with no site restriction on it belongs to the whole account and is returned for every siteId.

Cost and quantity are read per site, so the same product returns different figures at different sites.

itemState is not a wildcard - see Conventions.

Parameters

Parameters for GET /api/v2/products
Name Type In Required Description
siteId Guid query Required The site whose stock to read.
itemState ItemStates query Optional Active (1, the default) or Inactive (2).
find String query Optional Filter over the product's name, code and barcode.
allowSale Boolean query Optional true for products flagged for sale, false for products not flagged for sale. Defaults to false, which does filter - it is not "either".

Responses

Responses for GET /api/v2/products
Status Meaning
200 An array of ProductList objects. See the fields
403 Authorization failed. See Errors.

Example

# The sellable retail range. Without allowSale=true you get the opposite set.
curl -X GET \
  "https://acme.chidesk.com/api/v2/products?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&itemState=Active&allowSale=true" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var url = "api/v2/products"
        + "?siteId=" + siteId
        + "&itemState=Active"
        + "&allowSale=true";   // omit this and you get the NOT-for-sale products

var response = await http.GetAsync(url);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

Packages

Bundles of services sold as one item - a spa day, a course of treatments. A package id can be booked in place of a list of services when searching for available times.

Endpoint allow-list lines for this category

If the credential you are using has a non-empty endpoint allow-list, these are the lines that reach the endpoints below. One per line, and remember that routes ending in a record id cannot be listed at all - see Allow-lists on a credential.

  • api/v2/packages

GET /api/v2/packages #

List the packages a site sells.

All three parameters are required by name here, including find. This is the one catalogue endpoint that declares no defaults, so unlike Services, Products, Courses and Memberships you cannot call it with siteId alone - that returns 404, not the list. Send itemState and find even when you do not want to filter: ...&itemState=Active&find=.

A package with no site restriction on it belongs to the whole account and is returned for every siteId.

find is a contains match over name, code and barcode.

itemState is not a wildcard - see Conventions.

Parameters

Parameters for GET /api/v2/packages
Name Type In Required Description
siteId Guid query Required The site whose packages to read.
itemState ItemStates query Required Active (1) or Inactive (2). Required - there is no default.
find String query Required Contains filter over name, code and barcode. Required by name; send it empty for no filter.

Responses

Responses for GET /api/v2/packages
Status Meaning
200 An array of PackageList objects, ordered by name. See the fields
403 Authorization failed. See Errors.
404 A parameter was missing from the URL, so no endpoint matched. All three are required by name.

Example

# All three names present. find is empty but must be there.
curl -X GET \
  "https://acme.chidesk.com/api/v2/packages?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&itemState=Active&find=" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
// Unlike /services and /products, none of these may be dropped.
var url = "api/v2/packages"
        + "?siteId=" + siteId
        + "&itemState=Active"
        + "&find=";

var response = await http.GetAsync(url);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

Courses

Pre-paid blocks of sessions a client buys up front and draws down over time - ten massages, six laser treatments.

Endpoint allow-list lines for this category

If the credential you are using has a non-empty endpoint allow-list, these are the lines that reach the endpoints below. One per line, and remember that routes ending in a record id cannot be listed at all - see Allow-lists on a credential.

  • api/v2/courses

GET /api/v2/courses #

List the courses a site sells.

Only siteId is required; itemState and find carry defaults and may be omitted.

This lists the courses on the price list, not the courses a particular client has bought or how much of one is left.

A course with no site restriction on it belongs to the whole account and is returned for every siteId.

find is a contains match over name, code, category and barcode.

itemState is not a wildcard - see Conventions.

Parameters

Parameters for GET /api/v2/courses
Name Type In Required Description
siteId Guid query Required The site whose courses to read.
itemState ItemStates query Optional Active (1, the default) or Inactive (2).
find String query Optional Contains filter over name, code, category and barcode.

Responses

Responses for GET /api/v2/courses
Status Meaning
200 An array of CourseList objects. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/courses?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&itemState=Active" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var response = await http.GetAsync(
    "api/v2/courses?siteId=" + siteId + "&itemState=Active");

response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

Classes

Group sessions and events: the class definitions, the scheduled instances of them, and the bookings clients hold on those instances.

Three resources live under this one stem, and each needs its own allow-list line. A credential allowed only api/v2/classes cannot read schedules or bookings.

The three are layered: a class is the definition, a class schedule is one dated instance of it, and a class booking is one client's place on one schedule. Work down that order - a booking needs a ClassScheduleId, which comes from the schedules list.

Endpoint allow-list lines for this category

If the credential you are using has a non-empty endpoint allow-list, these are the lines that reach the endpoints below. One per line, and remember that routes ending in a record id cannot be listed at all - see Allow-lists on a credential.

  • api/v2/classes
  • api/v2/classes/classschedules
  • api/v2/classes/classbookings

GET /api/v2/classes #

List the class definitions at a site.

Both parameters are required by name; neither carries a default.

start is not a range and not a filter on when the class runs - it drops classes that had already finished by that moment. Pass the current time to get the classes still on offer, or an earlier date to include ones that have since ended.

Only Active classes are returned. There is no itemState here.

Parameters

Parameters for GET /api/v2/classes
Name Type In Required Description
siteId Guid query Required The site whose classes to read.
start DateTime query Required Exclude classes that had already ended before this moment, UTC.

Responses

Responses for GET /api/v2/classes
Status Meaning
200 An array of ClassList objects, ordered by name. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/classes?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&start=2026-08-01%2000:00" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var url = "api/v2/classes"
        + "?siteId=" + siteId
        + "&start=" + DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm");

var response = await http.GetAsync(url);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

GET /api/v2/classes/classschedules #

List the scheduled instances of classes in a date range - the timetable a client would book from.

bookOnline defaults to true, and that default filters: leave it out and you only see schedules whose class is flagged as bookable online. Pass bookOnline=false to see the whole timetable including the internal-only sessions.

Only Active schedules are returned; cancelled and inactive ones are not, and there is no parameter to include them.

classType picks Class (1) or Event (2). The value All (0) is not a wildcard - it matches a class type of 0, which nothing has. Make one call per type if you need both.

siteId, classType, start and end are required by name. find, employeeId and bookOnline carry defaults and may be omitted.

There is no cap on the date range here.

Parameters

Parameters for GET /api/v2/classes/classschedules
Name Type In Required Description
siteId Guid query Required The site whose timetable to read.
classType ClassTypes query Required Class (1) or Event (2). Not a wildcard - All (0) returns nothing.
start DateTime query Required Start of the range, UTC.
end DateTime query Required End of the range, UTC.
find String query Optional Name filter. Omit for no filter.
employeeId Guid query Optional Only the schedules this employee is taking. Omit for all employees.
bookOnline Boolean query Optional Defaults to true, which returns only classes flagged bookable online. Pass false for the whole timetable.

Responses

Responses for GET /api/v2/classes/classschedules
Status Meaning
200 An array of ClassScheduleList objects. See the fields
403 Authorization failed. See Errors.

Example

# The public timetable for August.
curl -X GET \
  "https://acme.chidesk.com/api/v2/classes/classschedules?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&classType=Class&start=2026-08-01%2000:00&end=2026-08-31%2000:00" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"

# Everything, including sessions not published for online booking.
curl -X GET \
  "https://acme.chidesk.com/api/v2/classes/classschedules?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&classType=Class&start=2026-08-01%2000:00&end=2026-08-31%2000:00&bookOnline=false" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var url = "api/v2/classes/classschedules"
        + "?siteId=" + siteId
        + "&classType=Class"
        + "&start=2026-08-01 00:00"
        + "&end=2026-08-31 00:00"
        + "&bookOnline=false";   // omit to see only the online-bookable ones

var response = await http.GetAsync(url);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

POST /api/v2/classes #

Create a class definition.

siteId travels in the query string; the class is the JSON body. Use an empty Guid for ClassId.

This creates the definition, not a dated session of it.

Parameters

Parameters for POST /api/v2/classes
Name Type In Required Description
siteId Guid query Required The site to create the class at.
classModel Class body Required The class to create. Set ClassId to an empty Guid.

Responses

Responses for POST /api/v2/classes
Status Meaning
200 An object of the form { "Message": "ClassId", "Value": "<guid>" } carrying the new class's id.
400 No body was sent, or the body failed validation.
403 Authorization failed. See Errors.

Example

curl -X POST \
  "https://acme.chidesk.com/api/v2/classes?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70" \
  -H "Authorization: Bearer $CHIDESK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "ClassId": "00000000-0000-0000-0000-000000000000",
        "SiteId": "6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70",
        "Name": "Sunrise Yoga"
      }'
var content = new StringContent(
    JsonConvert.SerializeObject(classModel), Encoding.UTF8, "application/json");

var response = await http.PostAsync(
    "api/v2/classes?siteId=" + siteId, content);

response.EnsureSuccessStatusCode();

PUT /api/v2/classes #

Update a class definition.

The id goes in the query string as classId, not in the path - see Where the record id goes. There is no PUT /api/v2/classes/{id}.

The record loaded is the one named by the query's classId, but the site it is saved against comes from the body's SiteId. Always send SiteId in the body; leave it out and the class is saved against an empty site id, which is not recoverable through the API.

Fetch the class first and send it back with your changes applied.

Parameters

Parameters for PUT /api/v2/classes
Name Type In Required Description
classId Guid query Required The class to update.
classModel Class body Required The class, with your changes applied. SiteId is required in the body - it decides the site the record is saved against.

Responses

Responses for PUT /api/v2/classes
Status Meaning
200 An object of the form { "Message": "ClassId", "Value": "<guid>" }.
400 No body was sent, or the body failed validation.
403 Authorization failed. See Errors.

Example

curl -X PUT \
  "https://acme.chidesk.com/api/v2/classes?classId=3d4e5f60-7182-4394-a5b6-c7d8e9f00112" \
  -H "Authorization: Bearer $CHIDESK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "ClassId": "3d4e5f60-7182-4394-a5b6-c7d8e9f00112",
        "SiteId": "6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70",
        "Name": "Sunrise Yoga (60 min)"
      }'
// SiteId in the body is not optional - it is the site the record is saved against.
var content = new StringContent(
    JsonConvert.SerializeObject(classModel), Encoding.UTF8, "application/json");

var response = await http.PutAsync(
    "api/v2/classes?classId=" + classModel.ClassId, content);

response.EnsureSuccessStatusCode();

GET /api/v2/classes/classbookings #

List the client bookings held on classes in a date range.

All four parameters are required by name; none carries a default.

classType picks Class (1) or Event (2), and All (0) is not a wildcard here either.

There is no cap on the date range.

Parameters

Parameters for GET /api/v2/classes/classbookings
Name Type In Required Description
siteId Guid query Required The site whose bookings to read.
classType ClassTypes query Required Class (1) or Event (2).
start DateTime query Required Start of the range, UTC.
end DateTime query Required End of the range, UTC.

Responses

Responses for GET /api/v2/classes/classbookings
Status Meaning
200 An array of ClassBooking objects. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/classes/classbookings?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&classType=Class&start=2026-08-01%2000:00&end=2026-08-31%2000:00" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var url = "api/v2/classes/classbookings"
        + "?siteId=" + siteId
        + "&classType=Class"
        + "&start=2026-08-01 00:00"
        + "&end=2026-08-31 00:00";

var response = await http.GetAsync(url);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

POST /api/v2/classes/classbookings #

Book a client onto a scheduled class.

This can accept your booking and write nothing

Two things about this endpoint will silently do nothing if you get them wrong, and both still return 200. First, you must set ObjectState to 1 (Created) in the body: a booking is only written when its ObjectState says it changed, and the default of 0 (Unchanged) means the request is accepted and discarded. Second, ChiDesk does not generate the booking id - you generate the Guid, send it as ClassBookingId, and it is echoed straight back to you. Sending an empty Guid gets you an empty Guid in the response. Always read back the schedule or the bookings list to confirm the booking landed; do not treat the 200 as proof.

siteId travels in the query string; the booking is the JSON body.

ClassScheduleId in the body says which dated session the client is booking - it is read from the body, not the query string, and the whole schedule is loaded and re-saved around your booking.

The response Value is whatever ClassBookingId you sent, not a server-assigned id.

Parameters

Parameters for POST /api/v2/classes/classbookings
Name Type In Required Description
siteId Guid query Required The site the class runs at.
classBooking ClassBooking body Required The booking. Requires ClassBookingId (a Guid you generate), ClassScheduleId, the client, and ObjectState set to 1.

Responses

Responses for POST /api/v2/classes/classbookings
Status Meaning
200 An object of the form { "Message": "ClassBookingId", "Value": "<guid>" }, echoing the id you sent. A 200 does not prove the booking was written - check ObjectState.
400 No body was sent, or the body failed validation.
403 Authorization failed. See Errors.

Example

curl -X POST \
  "https://acme.chidesk.com/api/v2/classes/classbookings?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70" \
  -H "Authorization: Bearer $CHIDESK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "ClassBookingId": "e4f50617-2839-4a5b-8c6d-7e8f90a1b2c3",
        "ClassScheduleId": "b2c3d4e5-f607-4819-a2b3-c4d5e6f70819",
        "ClientId": "7e8f9012-3a4b-4c5d-6e7f-8091a2b3c4d5",
        "ObjectState": 1
      }'
var booking = new
{
    ClassBookingId = Guid.NewGuid(),   // you generate this, not ChiDesk
    ClassScheduleId = classScheduleId,
    ClientId = clientId,
    ObjectState = 1                    // 1 = Created. Without this nothing is written.
};

var content = new StringContent(
    JsonConvert.SerializeObject(booking), Encoding.UTF8, "application/json");

var response = await http.PostAsync(
    "api/v2/classes/classbookings?siteId=" + siteId, content);

response.EnsureSuccessStatusCode();

// The response echoes the id you sent. Read the bookings list back to confirm.

PUT /api/v2/classes/classbookings #

Change an existing class booking - move it, or cancel a client's place.

The id in the URL is ignored

The classBookingId query parameter is required for the request to reach this endpoint at all, and is then never read. The booking that is actually loaded and saved is the one named by ClassBookingId in the body. Send the same value in both places: if they differ, you will change the booking named in the body while the URL and the response both name the other one. As on create, a booking whose ObjectState is left at 0 is accepted and not written - set 2 (Updated) to change one, or 3 (Deleted) to remove it.

No siteId is needed: the site is looked up from the booking itself.

The response Value is the body's ClassBookingId, not the query parameter's.

ClassScheduleId in the body says which session the booking belongs to after the change, so moving a client to another session means changing it.

Parameters

Parameters for PUT /api/v2/classes/classbookings
Name Type In Required Description
classBookingId Guid query Required Required for the endpoint to be matched, then ignored. Keep it equal to the body's ClassBookingId.
classBooking ClassBooking body Required The booking. ClassBookingId here is what selects the record. Set ObjectState to 2 to update or 3 to remove.

Responses

Responses for PUT /api/v2/classes/classbookings
Status Meaning
200 An object of the form { "Message": "ClassBookingId", "Value": "<guid>" }, taken from the body. Not proof that anything changed - check ObjectState.
400 No body was sent, or the body failed validation.
403 Authorization failed. See Errors.

Example

# Cancel a client's place. Note the id appears twice, identically.
curl -X PUT \
  "https://acme.chidesk.com/api/v2/classes/classbookings?classBookingId=e4f50617-2839-4a5b-8c6d-7e8f90a1b2c3" \
  -H "Authorization: Bearer $CHIDESK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "ClassBookingId": "e4f50617-2839-4a5b-8c6d-7e8f90a1b2c3",
        "ClassScheduleId": "b2c3d4e5-f607-4819-a2b3-c4d5e6f70819",
        "ObjectState": 3
      }'
// The body's ClassBookingId is what gets acted on - keep the query value equal to it.
var content = new StringContent(
    JsonConvert.SerializeObject(booking), Encoding.UTF8, "application/json");

var response = await http.PutAsync(
    "api/v2/classes/classbookings?classBookingId=" + booking.ClassBookingId, content);

response.EnsureSuccessStatusCode();

Memberships

The membership plans a site sells - the recurring schemes that give clients preferential rates or included sessions.

Endpoint allow-list lines for this category

If the credential you are using has a non-empty endpoint allow-list, these are the lines that reach the endpoints below. One per line, and remember that routes ending in a record id cannot be listed at all - see Allow-lists on a credential.

  • api/v2/memberships

GET /api/v2/memberships #

List the membership plans a site sells.

Only siteId is required; itemState and find carry defaults and may be omitted.

These are the plans on offer, not the clients who hold one. A client's own membership is on their Client record.

A plan with no site restriction on it belongs to the whole account and is returned for every siteId.

find is a contains match over name and code.

itemState is not a wildcard - see Conventions.

Parameters

Parameters for GET /api/v2/memberships
Name Type In Required Description
siteId Guid query Required The site whose membership plans to read.
itemState ItemStates query Optional Active (1, the default) or Inactive (2).
find String query Optional Contains filter over name and code.

Responses

Responses for GET /api/v2/memberships
Status Meaning
200 An array of MembershipList objects, ordered by name. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/memberships?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&itemState=Active" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var response = await http.GetAsync(
    "api/v2/memberships?siteId=" + siteId + "&itemState=Active");

response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

Sales

Invoices, credit notes, quotations and the purchasing side - orders, supplier invoices, goods received and stock transfers. Every one of these is an accounting document, and they all live under /api/v2/accountingdocuments. V1 published this category as Sales.

Money moving is two separate records in ChiDesk: the document that says what was owed (here), and the entry that says what was paid (Receipts). A paid invoice is an accounting document with an accounting entry against it - reconcile across both categories, not one.

Endpoint allow-list lines for this category

If the credential you are using has a non-empty endpoint allow-list, these are the lines that reach the endpoints below. One per line, and remember that routes ending in a record id cannot be listed at all - see Allow-lists on a credential.

  • api/v2/accountingdocuments

GET /api/v2/accountingdocuments #

List accounting documents in a date range, either the client-facing ones or the supplier-facing ones.

All five parameters are required by name, including find. Send it empty when you do not want to filter: ...&find=. Leaving a name out does not return the list - verified against a running server, it returns 405, because POST /api/v2/accountingdocuments shares this path and keeps it alive for another method once the GET is ruled out.

clientDocuments is a switch between two fixed sets, not a filter you can widen. true returns client invoices, credit notes and quotations. false returns debit notes, goods received notes, purchase orders, supplier invoices and stock transfers. There is no value that returns both, so a full picture is two calls.

Neither side returns the two recurring document types - recurring client invoices and recurring supplier invoices are absent from this list whatever you pass, with no error. If you reconcile invoices from this endpoint alone you will be short by exactly the recurring ones. Subscribe to the NewRecurringClientInvoice and NewRecurringSupplierInvoice WebHook events to catch them.

The end of the range is widened by one day before the query runs, so documents dated on the end date itself are included - and so are any dated in the first moments of the following day. Do not use this endpoint to build non-overlapping day buckets without allowing for that.

find is a starts-with match on the client or supplier name, or on the document number.

There is no cap on the date range here, unlike the appointment and roster endpoints.

Ordered by document date. Each row also carries a fiscal status where the account is fiscalised.

Parameters

Parameters for GET /api/v2/accountingdocuments
Name Type In Required Description
siteId Guid query Required The site whose documents to read.
clientDocuments Boolean query Required true for invoices, credit notes and quotations; false for the purchasing documents.
start DateTime query Required Start of the document-date range, UTC.
end DateTime query Required End of the document-date range, UTC. Widened by a day internally.
find String query Required Starts-with filter on client or supplier name, or document number. Required by name; send it empty for no filter.

Responses

Responses for GET /api/v2/accountingdocuments
Status Meaning
200 An array of AccountingDocumentList objects, ordered by document date. See the fields
403 Authorization failed. See Errors.
405 A parameter was missing from the URL, so this action did not match. The path survives for POST, so the failure surfaces as 405 rather than 404.

Example

# Client-facing documents for July.
curl -X GET \
  "https://acme.chidesk.com/api/v2/accountingdocuments?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&clientDocuments=true&start=2026-07-01%2000:00&end=2026-07-31%2000:00&find=" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"

# The purchasing side is the same call with clientDocuments=false.
curl -X GET \
  "https://acme.chidesk.com/api/v2/accountingdocuments?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&clientDocuments=false&start=2026-07-01%2000:00&end=2026-07-31%2000:00&find=" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
// Two calls, because clientDocuments has no "both" value.
async Task<string> Documents(bool clientDocuments)
{
    var url = "api/v2/accountingdocuments"
            + "?siteId=" + siteId
            + "&clientDocuments=" + (clientDocuments ? "true" : "false")
            + "&start=2026-07-01 00:00"
            + "&end=2026-07-31 00:00"
            + "&find=";

    var response = await http.GetAsync(url);
    response.EnsureSuccessStatusCode();

    return await response.Content.ReadAsStringAsync();
}

var clientSide = await Documents(true);
var supplierSide = await Documents(false);
// Recurring invoices are in neither - pick those up from the WebHook events.

GET /api/v2/accountingdocuments/{accountingDocumentId} #

Fetch one accounting document in full, including its line items.

This route takes no siteId, and no clientDocuments switch - any document type can be fetched by its id, including the recurring ones the list will not show you.

The id must be a well-formed Guid. Anything else does not match the route and returns 404.

Parameters

Parameters for GET /api/v2/accountingdocuments/{accountingDocumentId}
Name Type In Required Description
accountingDocumentId Guid route Required The document to fetch.

Responses

Responses for GET /api/v2/accountingdocuments/{accountingDocumentId}
Status Meaning
200 A single AccountingDocument object. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/accountingdocuments/5a6b7c8d-9e0f-4112-a334-556677889900" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var response = await http.GetAsync(
    "api/v2/accountingdocuments/" + accountingDocumentId);

response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

POST /api/v2/accountingdocuments #

Create an accounting document - raise an invoice, a credit note, a quotation or a purchase order.

AccountingDocumentTypeId in the body decides what kind of document this is, and there is no default: 1 client invoice, 2 quotation, 3 credit note, 9 purchase order, 10 supplier invoice, 11 debit note, 12 goods received note, 13 stock transfer. Get it wrong and you get a valid document of the wrong type, not an error.

Send SiteId in the body as well as siteId in the query string, and make them the same site. The new document is set up from the body's SiteId and then saved against the query's - they are read from different places, so a mismatch is not something the API will tell you about.

Use an empty Guid for AccountingDocumentId. LinkedAccountingDocumentId ties a credit note back to the invoice it credits.

Creating a document does not record a payment against it. That is a separate accounting entry - see Receipts.

Parameters

Parameters for POST /api/v2/accountingdocuments
Name Type In Required Description
siteId Guid query Required The site to create the document at. Keep it equal to the body's SiteId.
accountingDocument AccountingDocument body Required The document. Requires AccountingDocumentTypeId, SiteId and the line items.

Responses

Responses for POST /api/v2/accountingdocuments
Status Meaning
200 An object of the form { "Message": "AccountingDocumentId", "Value": "<guid>" } carrying the new document's id.
400 No body was sent, or the body failed validation. The response carries the model-state errors.
403 Authorization failed. See Errors.

Example

curl -X POST \
  "https://acme.chidesk.com/api/v2/accountingdocuments?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70" \
  -H "Authorization: Bearer $CHIDESK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "AccountingDocumentId": "00000000-0000-0000-0000-000000000000",
        "AccountingDocumentTypeId": 1,
        "SiteId": "6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70",
        "ClientId": "7e8f9012-3a4b-4c5d-6e7f-8091a2b3c4d5",
        "DocumentDate": "2026-08-03 09:00",
        "Reference": "Web order 1042"
      }'
// AccountingDocumentTypeId 1 = client invoice. The body's SiteId must match
// the siteId in the query string.
var content = new StringContent(
    JsonConvert.SerializeObject(document), Encoding.UTF8, "application/json");

var response = await http.PostAsync(
    "api/v2/accountingdocuments?siteId=" + siteId, content);

response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();
// { "Message": "AccountingDocumentId", "Value": "..." }

PUT /api/v2/accountingdocuments/{accountingDocumentId} #

Update an existing accounting document.

The document that is loaded is the one named in the route - unlike the appointments PUT, which loads the one named in the body.

The site it is saved against comes from the body's SiteId. Send it, and send the document's real site; leave it out and the document is saved against an empty site id.

Fetch the document first and send it back with your changes applied. Fields you send overwrite the stored ones.

A document that has already been posted or fiscalised should not be edited through here - correct it with a credit note instead.

Parameters

Parameters for PUT /api/v2/accountingdocuments/{accountingDocumentId}
Name Type In Required Description
accountingDocumentId Guid route Required The document to update. This is what selects the record.
accountingDocument AccountingDocument body Required The document, with your changes applied. SiteId is required - it decides the site the record is saved against.

Responses

Responses for PUT /api/v2/accountingdocuments/{accountingDocumentId}
Status Meaning
200 An object of the form { "Message": "AccountingDocumentId", "Value": "<guid>" } echoing the route id.
400 No body was sent, or the body failed validation.
403 Authorization failed. See Errors.

Example

curl -X PUT \
  "https://acme.chidesk.com/api/v2/accountingdocuments/5a6b7c8d-9e0f-4112-a334-556677889900" \
  -H "Authorization: Bearer $CHIDESK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "AccountingDocumentId": "5a6b7c8d-9e0f-4112-a334-556677889900",
        "SiteId": "6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70",
        "Reference": "Web order 1042 (amended)"
      }'
var content = new StringContent(
    JsonConvert.SerializeObject(document), Encoding.UTF8, "application/json");

var response = await http.PutAsync(
    "api/v2/accountingdocuments/" + accountingDocumentId, content);

response.EnsureSuccessStatusCode();

DELETE /api/v2/accountingdocuments/{accountingDocumentId} #

Remove an accounting document.

A document that does not exist comes back as 400 with the body "Record does not exist." - not 404. A 404 from this path means the id was not a well-formed Guid.

This removes the document rather than reversing it. On a posted or fiscalised document that is not what your accountant wants - raise a credit note instead.

Parameters

Parameters for DELETE /api/v2/accountingdocuments/{accountingDocumentId}
Name Type In Required Description
accountingDocumentId Guid route Required The document to remove.

Responses

Responses for DELETE /api/v2/accountingdocuments/{accountingDocumentId}
Status Meaning
200 The document was removed.
400 No document exists with that id - the body reads "Record does not exist."
403 Authorization failed. See Errors.

Example

curl -X DELETE \
  "https://acme.chidesk.com/api/v2/accountingdocuments/5a6b7c8d-9e0f-4112-a334-556677889900" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var response = await http.DeleteAsync(
    "api/v2/accountingdocuments/" + accountingDocumentId);

// A missing document is a 400, not a 404.
response.EnsureSuccessStatusCode();

Receipts

Money in and money out: the receipts taken from clients and the payments made to suppliers, including the recurring ones. These are accounting entries and live under /api/v2/accountingentries. V1 published this category as Receipts.

Endpoint allow-list lines for this category

If the credential you are using has a non-empty endpoint allow-list, these are the lines that reach the endpoints below. One per line, and remember that routes ending in a record id cannot be listed at all - see Allow-lists on a credential.

  • api/v2/accountingentries

GET /api/v2/accountingentries #

List accounting entries of one type in a date range.

All five parameters are required by name, including find. Send it empty when you do not want to filter. Leaving a name out returns 405 - verified against a running server - because POST /api/v2/accountingentries shares this path and keeps it alive for another method once the GET is ruled out.

entryType matches exactly one type per call: Receipt (1), Payment (2), RecurringReceipt (10) or RecurringPayment (11). There is no "all" value, so a full day's cash movement is up to four calls. Both names and numbers are accepted.

Unlike the Sales list, the recurring types are reachable here - ask for them by name.

The end of the range is widened by one day before the query runs, so entries dated on the end date itself are included, along with any in the first moments of the following day.

There is no cap on the date range.

Parameters

Parameters for GET /api/v2/accountingentries
Name Type In Required Description
siteId Guid query Required The site whose entries to read.
entryType AccountingEntryTypes query Required Exactly one of Receipt (1), Payment (2), RecurringReceipt (10) or RecurringPayment (11).
start DateTime query Required Start of the entry-date range, UTC.
end DateTime query Required End of the entry-date range, UTC. Widened by a day internally.
find String query Required Filter on the client or supplier name, or the entry number. Required by name; send it empty for no filter.

Responses

Responses for GET /api/v2/accountingentries
Status Meaning
200 An array of AccountingEntryList objects. See the fields
403 Authorization failed. See Errors.
405 A parameter was missing from the URL, so this action did not match. The path survives for POST, so the failure surfaces as 405 rather than 404.

Example

# Receipts for July. Payments are the same call with entryType=Payment.
curl -X GET \
  "https://acme.chidesk.com/api/v2/accountingentries?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&entryType=Receipt&start=2026-07-01%2000:00&end=2026-07-31%2000:00&find=" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
// One call per type - entryType has no wildcard.
var types = new[] { "Receipt", "Payment", "RecurringReceipt", "RecurringPayment" };

foreach (var entryType in types)
{
    var url = "api/v2/accountingentries"
            + "?siteId=" + siteId
            + "&entryType=" + entryType
            + "&start=2026-07-01 00:00"
            + "&end=2026-07-31 00:00"
            + "&find=";

    var response = await http.GetAsync(url);
    response.EnsureSuccessStatusCode();
}

GET /api/v2/accountingentries/{accountingEntryId} #

Fetch one accounting entry in full, including what it was allocated against.

This route takes no siteId and no entryType - any entry can be fetched by its id.

The id must be a well-formed Guid, or the route does not match and you get 404.

Parameters

Parameters for GET /api/v2/accountingentries/{accountingEntryId}
Name Type In Required Description
accountingEntryId Guid route Required The entry to fetch.

Responses

Responses for GET /api/v2/accountingentries/{accountingEntryId}
Status Meaning
200 A single AccountingEntry object. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/accountingentries/8c9d0e1f-2a3b-44c5-96d7-e8f90a1b2c3d" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var response = await http.GetAsync(
    "api/v2/accountingentries/" + accountingEntryId);

response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

POST /api/v2/accountingentries #

Record a receipt or a payment.

AccountingEntryTypeId in the body decides what this entry is: 1 receipt, 2 payment, 10 recurring receipt, 11 recurring payment. There is no default.

Send SiteId in the body as well as siteId in the query string, and make them the same site - the entry is set up from the body's SiteId and then saved against the query's.

Use an empty Guid for AccountingEntryId.

To mark an invoice as paid, create the entry here and allocate it against the document; the document itself is not edited.

Parameters

Parameters for POST /api/v2/accountingentries
Name Type In Required Description
siteId Guid query Required The site to record the entry at. Keep it equal to the body's SiteId.
accountingEntry AccountingEntry body Required The entry. Requires AccountingEntryTypeId, SiteId, the amount and the payment type.

Responses

Responses for POST /api/v2/accountingentries
Status Meaning
200 An object of the form { "Message": "AccountingEntryId", "Value": "<guid>" } carrying the new entry's id.
400 No body was sent, or the body failed validation. The response carries the model-state errors.
403 Authorization failed. See Errors.

Example

curl -X POST \
  "https://acme.chidesk.com/api/v2/accountingentries?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70" \
  -H "Authorization: Bearer $CHIDESK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "AccountingEntryId": "00000000-0000-0000-0000-000000000000",
        "AccountingEntryTypeId": 1,
        "SiteId": "6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70",
        "ClientId": "7e8f9012-3a4b-4c5d-6e7f-8091a2b3c4d5",
        "EntryDate": "2026-08-03 09:20",
        "Total": 450.00
      }'
// AccountingEntryTypeId 1 = receipt.
var content = new StringContent(
    JsonConvert.SerializeObject(entry), Encoding.UTF8, "application/json");

var response = await http.PostAsync(
    "api/v2/accountingentries?siteId=" + siteId, content);

response.EnsureSuccessStatusCode();

PUT /api/v2/accountingentries/{accountingEntryId} #

Update an existing accounting entry.

The entry that is loaded is the one named in the route.

The site it is saved against comes from the body's SiteId, so send it and send the right one.

Fetch the entry first and send it back with your changes applied.

Parameters

Parameters for PUT /api/v2/accountingentries/{accountingEntryId}
Name Type In Required Description
accountingEntryId Guid route Required The entry to update. This is what selects the record.
accountingEntry AccountingEntry body Required The entry, with your changes applied. SiteId is required.

Responses

Responses for PUT /api/v2/accountingentries/{accountingEntryId}
Status Meaning
200 An object of the form { "Message": "AccountingEntryId", "Value": "<guid>" } echoing the route id.
400 No body was sent, or the body failed validation.
403 Authorization failed. See Errors.

Example

curl -X PUT \
  "https://acme.chidesk.com/api/v2/accountingentries/8c9d0e1f-2a3b-44c5-96d7-e8f90a1b2c3d" \
  -H "Authorization: Bearer $CHIDESK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "AccountingEntryId": "8c9d0e1f-2a3b-44c5-96d7-e8f90a1b2c3d",
        "SiteId": "6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70",
        "Reference": "Card settlement 03/08"
      }'
var content = new StringContent(
    JsonConvert.SerializeObject(entry), Encoding.UTF8, "application/json");

var response = await http.PutAsync(
    "api/v2/accountingentries/" + accountingEntryId, content);

response.EnsureSuccessStatusCode();

DELETE /api/v2/accountingentries/{accountingEntryId} #

Remove an accounting entry.

An entry that does not exist comes back as 400 with the body "Record does not exist." - not 404.

Removing a receipt leaves the document it was allocated against unpaid again. Check what depends on the entry before you delete it.

Parameters

Parameters for DELETE /api/v2/accountingentries/{accountingEntryId}
Name Type In Required Description
accountingEntryId Guid route Required The entry to remove.

Responses

Responses for DELETE /api/v2/accountingentries/{accountingEntryId}
Status Meaning
200 The entry was removed.
400 No entry exists with that id - the body reads "Record does not exist."
403 Authorization failed. See Errors.

Example

curl -X DELETE \
  "https://acme.chidesk.com/api/v2/accountingentries/8c9d0e1f-2a3b-44c5-96d7-e8f90a1b2c3d" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var response = await http.DeleteAsync(
    "api/v2/accountingentries/" + accountingEntryId);

response.EnsureSuccessStatusCode();

Vouchers

Gift vouchers: list and search them, read one, issue a new one, and update an existing one. Voucher numbers have to be unique across the site.

Both write endpoints take the site from the body's SiteId, not only from the query string. Always send SiteId in the body - on the update it is dereferenced without a null check, so omitting it fails with a 500 rather than a helpful 400.

Endpoint allow-list lines for this category

If the credential you are using has a non-empty endpoint allow-list, these are the lines that reach the endpoints below. One per line, and remember that routes ending in a record id cannot be listed at all - see Allow-lists on a credential.

  • api/v2/vouchers

GET /api/v2/vouchers #

List and search vouchers at a site.

Only siteId is required; itemState and find carry defaults and may be omitted.

find is a starts-with match over the voucher number, the recipient and the barcode. To look a voucher up from a barcode scan, send the scanned value as find.

Vouchers not tied to a single site are included whatever siteId you pass.

itemState is not a wildcard - see Conventions.

Parameters

Parameters for GET /api/v2/vouchers
Name Type In Required Description
siteId Guid query Required The site whose vouchers to read.
itemState ItemStates query Optional Active (1, the default) or Inactive (2).
find String query Optional Starts-with filter over voucher number, recipient and barcode.

Responses

Responses for GET /api/v2/vouchers
Status Meaning
200 An array of VoucherList objects. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/vouchers?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70&find=GV1042" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var url = "api/v2/vouchers"
        + "?siteId=" + siteId
        + "&find=" + Uri.EscapeDataString(scannedBarcode);

var response = await http.GetAsync(url);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

GET /api/v2/vouchers/{voucherId} #

Fetch one voucher, including its balance and what it has been redeemed against.

Takes no siteId - the voucher is found by its id alone.

The id must be a well-formed Guid, or the route does not match and you get 404.

Parameters

Parameters for GET /api/v2/vouchers/{voucherId}
Name Type In Required Description
voucherId Guid route Required The voucher to fetch.

Responses

Responses for GET /api/v2/vouchers/{voucherId}
Status Meaning
200 A single Voucher object. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/vouchers/1a2b3c4d-5e6f-4708-9910-a1b2c3d4e5f6" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var response = await http.GetAsync("api/v2/vouchers/" + voucherId);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

POST /api/v2/vouchers #

Issue a voucher.

VoucherNumber has to be unique. If it is already in use the call returns 400 with a model-state error on VoucherNumber reading "A voucher with this number already exists." - the message is in the account's language, so match on the field name rather than the text.

The uniqueness check runs against the body's SiteId, while the voucher is created at the query string's siteId. Send the same site in both, or you can be told a number is free at one site and have the voucher created at another.

A blank or missing VoucherNumber skips the uniqueness check altogether - it does not fail validation here.

Use an empty Guid for VoucherId. Send SiteId in the body.

Parameters

Parameters for POST /api/v2/vouchers
Name Type In Required Description
siteId Guid query Required The site to issue the voucher at. Keep it equal to the body's SiteId.
voucher Voucher body Required The voucher. Requires SiteId, a unique VoucherNumber and the value.

Responses

Responses for POST /api/v2/vouchers
Status Meaning
200 An object of the form { "Message": "VoucherId", "Value": "<guid>" } carrying the new voucher's id.
400 The voucher number is already in use, no body was sent, or the body failed validation.
403 Authorization failed. See Errors.

Example

curl -X POST \
  "https://acme.chidesk.com/api/v2/vouchers?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70" \
  -H "Authorization: Bearer $CHIDESK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "VoucherId": "00000000-0000-0000-0000-000000000000",
        "SiteId": "6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70",
        "VoucherNumber": "GV1042",
        "Recipient": "Jo Naidoo",
        "Value": 500.00
      }'
var content = new StringContent(
    JsonConvert.SerializeObject(voucher), Encoding.UTF8, "application/json");

var response = await http.PostAsync(
    "api/v2/vouchers?siteId=" + siteId, content);

if (response.StatusCode == HttpStatusCode.BadRequest)
{
    // Check the model state for VoucherNumber - the number may already exist.
    var errors = await response.Content.ReadAsStringAsync();
}

PUT /api/v2/vouchers/{voucherId} #

Update an existing voucher.

The voucher that is loaded is the one named in the route.

SiteId in the body is mandatory in practice even though nothing says so: it is used to save the record and is read without a null check, so a body without it fails with a 500. Send the voucher's real site.

The uniqueness rule is not applied on update - changing VoucherNumber to one that already exists is not rejected here. Check it yourself with the list endpoint first.

Fetch the voucher first and send it back with your changes applied.

Parameters

Parameters for PUT /api/v2/vouchers/{voucherId}
Name Type In Required Description
voucherId Guid route Required The voucher to update. This is what selects the record.
voucher Voucher body Required The voucher, with your changes applied. SiteId is required - omitting it causes a 500.

Responses

Responses for PUT /api/v2/vouchers/{voucherId}
Status Meaning
200 An object of the form { "Message": "VoucherId", "Value": "<guid>" } echoing the route id.
400 No body was sent, or the body failed validation.
403 Authorization failed. See Errors.
500 SiteId was missing from the body.

Example

curl -X PUT \
  "https://acme.chidesk.com/api/v2/vouchers/1a2b3c4d-5e6f-4708-9910-a1b2c3d4e5f6" \
  -H "Authorization: Bearer $CHIDESK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "VoucherId": "1a2b3c4d-5e6f-4708-9910-a1b2c3d4e5f6",
        "SiteId": "6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70",
        "Recipient": "Jo Naidoo-Smith"
      }'
// SiteId must be present in the body or the request fails with a 500.
var content = new StringContent(
    JsonConvert.SerializeObject(voucher), Encoding.UTF8, "application/json");

var response = await http.PutAsync(
    "api/v2/vouchers/" + voucherId, content);

response.EnsureSuccessStatusCode();

Reports

The sales report: takings over a period, broken down by employee and including voucher redemptions. One endpoint, and the only one that can span the whole account in a single call.

Endpoint allow-list lines for this category

If the credential you are using has a non-empty endpoint allow-list, these are the lines that reach the endpoints below. One per line, and remember that routes ending in a record id cannot be listed at all - see Allow-lists on a credential.

  • api/v2/reports/sales

GET /api/v2/reports/sales #

Run the sales report over a date range, for one site or for the whole account.

The range is capped at 365 days here, not the 90 days that appointments and rosters allow. A wider range returns 400 with "Maximum date range is 365 days".

siteId is genuinely optional and may be left out entirely. Leave it out and the report covers every Active site on the account - inactive sites are not included even though their history exists. Supply it and the report is scoped to that one site, active or not.

Employee names and voucher redemptions are always included; there is no parameter to turn them off.

This is the most expensive endpoint in the API. It reads a year of transactions across every site if you let it, and the rate limit still applies - ask for one wide range rather than looping over weeks.

Parameters

Parameters for GET /api/v2/reports/sales
Name Type In Required Description
start DateTime query Required Start of the range, UTC.
end DateTime query Required End of the range, UTC. At most 365 days after start.
siteId Guid query Optional One site to report on. Omit for every Active site on the account.

Responses

Responses for GET /api/v2/reports/sales
Status Meaning
200 An array of Sales objects, one per site included in the report. See the fields
400 The range exceeds 365 days.
403 Authorization failed. See Errors.

Example

# Whole account for July - no siteId at all.
curl -X GET \
  "https://acme.chidesk.com/api/v2/reports/sales?start=2026-07-01%2000:00&end=2026-07-31%2000:00" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"

# One site.
curl -X GET \
  "https://acme.chidesk.com/api/v2/reports/sales?start=2026-07-01%2000:00&end=2026-07-31%2000:00&siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
// Omitting siteId is what makes this account-wide - it is not a required blank.
var url = "api/v2/reports/sales"
        + "?start=2026-07-01 00:00"
        + "&end=2026-07-31 00:00";

var response = await http.GetAsync(url);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

WebHooks

Register a URL and ChiDesk will POST to it when the event you asked for happens, so you do not have to poll. The polling endpoint is here to help you build and test your handler before you go live.

The two allow-list keys are independent. A credential allowed only api/v2/webhooks/poll cannot register or unregister hooks, and one allowed only api/v2/webhooks cannot poll.

Endpoint allow-list lines for this category

If the credential you are using has a non-empty endpoint allow-list, these are the lines that reach the endpoints below. One per line, and remember that routes ending in a record id cannot be listed at all - see Allow-lists on a credential.

  • api/v2/webhooks
  • api/v2/webhooks/poll

POST /api/v2/webhooks #

Register a URL to be called when an event happens.

The two body fields are snake_case - target_url and event - unlike the PascalCase fields everywhere else in the API. They are read straight off the raw JSON body.

Both fields are required, and neither is validated. If either is missing the request fails with a 500 rather than a 400 with a helpful message.

A URL may be registered once. Registering a target_url that already exists returns 409 Conflict and changes nothing - to point an existing hook at a different event, unregister it first.

ChiDesk records the client_id of the credential that registered the hook, so you can see which integration owns it.

Parameters

Parameters for POST /api/v2/webhooks
Name Type In Required Description
target_url String body Required The absolute HTTPS URL ChiDesk should POST to.
event String body Required Which event to subscribe to. Use one of the event names listed below.

Responses

Responses for POST /api/v2/webhooks
Status Meaning
201 The hook was created. The body is { "id": <int> }.
409 That target_url is already registered.
403 Authorization failed. See Errors.

Example

curl -X POST \
  "https://acme.chidesk.com/api/v2/webhooks" \
  -H "Authorization: Bearer $CHIDESK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "target_url": "https://hooks.example.com/chidesk/appointments",
        "event": "NewAppointment"
      }'
var hook = new
{
    target_url = "https://hooks.example.com/chidesk/appointments",
    @event = "NewAppointment"   // event is a C# keyword - the wire name is still "event"
};

var content = new StringContent(
    JsonConvert.SerializeObject(hook), Encoding.UTF8, "application/json");

var response = await http.PostAsync("api/v2/webhooks", content);

if (response.StatusCode == HttpStatusCode.Conflict)
{
    // This target_url is already registered.
}

DELETE /api/v2/webhooks #

Unregister a hook by the URL it points at.

This DELETE carries a request body, which is unusual - some HTTP clients will not send one on a DELETE without being told to. In .NET, build an HttpRequestMessage and set its Content rather than calling DeleteAsync.

The hook is identified by target_url, not by the id returned when it was created.

Unregistering a URL that was never registered still returns 200.

Parameters

Parameters for DELETE /api/v2/webhooks
Name Type In Required Description
target_url String body Required The registered URL to remove.

Responses

Responses for DELETE /api/v2/webhooks
Status Meaning
200 The hook is gone, or was never there.
403 Authorization failed. See Errors.

Example

curl -X DELETE \
  "https://acme.chidesk.com/api/v2/webhooks" \
  -H "Authorization: Bearer $CHIDESK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "target_url": "https://hooks.example.com/chidesk/appointments" }'
// DeleteAsync cannot carry a body - build the request by hand.
var request = new HttpRequestMessage(HttpMethod.Delete, "api/v2/webhooks")
{
    Content = new StringContent(
        "{ \"target_url\": \"https://hooks.example.com/chidesk/appointments\" }",
        Encoding.UTF8,
        "application/json")
};

var response = await http.SendAsync(request);
response.EnsureSuccessStatusCode();

GET /api/v2/webhooks/poll #

Fetch the most recent records for an event, so you can build a handler against real payloads before registering a hook.

This endpoint is a development aid, not a substitute for registering a hook. It returns the latest records for the event rather than only ones you have not seen.

An event name that is not recognised returns 200 with an empty body rather than an error - so a typo looks like "no data". Check the name against the table below.

siteId and records are genuinely optional and may be omitted. When siteId is left out ChiDesk uses the account's main site.

Parameters

Parameters for GET /api/v2/webhooks/poll
Name Type In Required Description
targetEvent String query Required Which event's payload to return. One of the event names below.
siteId Guid query Optional The site to read from. Defaults to the account's main site.
records Int32 query Optional How many recent records to return. Defaults to 1.

Responses

Responses for GET /api/v2/webhooks/poll
Status Meaning
200 An array of the object type that belongs to the event - see the table below. Empty when the event name is not recognised.
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/webhooks/poll?targetEvent=NewAppointment&records=5" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var response = await http.GetAsync(
    "api/v2/webhooks/poll?targetEvent=NewAppointment&records=5");

response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

Event names

The name you register a hook with, and the name you pass to targetEvent when polling. Each one delivers the object in the second column. Names are case-sensitive, and an unrecognised name is not reported as an error - it simply returns nothing.

WebHook event names and their payloads
Event Payload Fires when
NewAppointment Appointment An appointment was booked.
NewClient Client A client record was created.
NewClientInvoice AccountingDocument A client was invoiced.
NewCreditNote AccountingDocument A credit note was raised.
NewQuotation AccountingDocument A quotation was issued.
NewReceipt AccountingEntry Money was received.
NewPayment AccountingEntry A payment was made.
NewSupplierInvoice AccountingDocument A supplier invoice was captured.
NewPurchaseOrder AccountingDocument A purchase order was raised.
NewGoodsReceivedNote AccountingDocument Stock was received.
NewDebitNote AccountingDocument A debit note was raised.
NewStockTransfer AccountingDocument Stock moved between sites.
NewRecurringClientInvoice AccountingDocument A recurring client invoice was generated.
NewRecurringSupplierInvoice AccountingDocument A recurring supplier invoice was generated.
NewRecurringReceipt AccountingEntry A recurring receipt was generated.
NewRecurringPayment AccountingEntry A recurring payment was generated.

Alerts

The alert definitions configured on the account - the flags that can be raised against a client or an appointment. Read-only: alerts are set up in the back office, not through the API.

Endpoint allow-list lines for this category

If the credential you are using has a non-empty endpoint allow-list, these are the lines that reach the endpoints below. One per line, and remember that routes ending in a record id cannot be listed at all - see Allow-lists on a credential.

  • api/v2/alerts

GET /api/v2/alerts #

List the alert definitions available at a site.

siteId is the only parameter, and it is required by name.

Alerts not tied to a single site belong to the whole account and are returned for every siteId.

Ordered by category, then name - use the category to group them in your own UI.

Every row comes back with its selected flag false. That field is for the back-office editor; it does not tell you which alerts are set on a given client.

Parameters

Parameters for GET /api/v2/alerts
Name Type In Required Description
siteId Guid query Required The site whose alerts to read.

Responses

Responses for GET /api/v2/alerts
Status Meaning
200 An array of Alert objects, ordered by category then name. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/alerts?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var response = await http.GetAsync("api/v2/alerts?siteId=" + siteId);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

ClassPass

ChiDesk's implementation of the ClassPass marketplace partner API. ClassPass calls these endpoints to publish a site's practitioners and availability, to book appointments on a member's behalf, and to reconcile attendance afterwards. If you are not ClassPass, you almost certainly want the Appointments category instead.

This category follows the ClassPass specification, not ChiDesk's own conventions. Field names are snake_case, ids are Guids with the hyphens stripped out, dates are ISO 8601, and times in the availability and appointment payloads are the site's LOCAL times without a zone designator - not UTC like the rest of this reference. Read the note on each endpoint.

A venue_id is a ChiDesk siteId with the hyphens removed, and a service_id or bookable_id is the corresponding ChiDesk id with the hyphens removed. Both forms are accepted on the way in.

The partner_id segment in every path is ignored - the account is resolved from the host name you called, the same as everywhere else in the API. Any value there returns your own account's data, and there is no way to reach another account's.

Leave the endpoint allow-list empty on a ClassPass credential. Almost every path here ends in an id, and paths ending in an id cannot be matched by the suffix test - see Allow-lists on a credential.

Endpoint allow-list lines for this category

If the credential you are using has a non-empty endpoint allow-list, these are the lines that reach the endpoints below. One per line, and remember that routes ending in a record id cannot be listed at all - see Allow-lists on a credential.

  • api/v2/classpass

GET /api/v2/classpass #

List partners. Always exactly one - the account you authenticated against.

A ChiDesk account is one ClassPass partner, so this always returns a single partner and a pagination block that says page 1 of 1. page and page_size are accepted and have no effect on what comes back.

last_updated is generated at request time as "yesterday", not read from the record. Do not use it to detect changes.

This is the one path in the category that ends in a literal segment, so api/v2/classpass is the only usable endpoint allow-list line here.

Parameters

Parameters for GET /api/v2/classpass
Name Type In Required Description
page Int32 query Optional Accepted for spec compliance. Defaults to 1 and has no effect.
page_size Int32 query Optional Accepted for spec compliance. Defaults to 10 and is echoed back in the pagination block.

Responses

Responses for GET /api/v2/classpass
Status Meaning
200 A partner_list object: one partner, plus a pagination block that always reads page 1 of 1. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/classpass" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
{
  "partners": [
    {
      "partner_id": "acme",
      "partner_name": "Acme Day Spa",
      "last_updated": "2026-07-28T08:14:22.1130000Z"
    }
  ],
  "pagination": { "page": 1, "page_size": 10, "total_pages": 1 }
}

GET /api/v2/classpass/{partner_id} #

Fetch the partner record for the account.

The partner_id in the path is not checked against anything. Whatever you put there, you get the account resolved from the host name - so this returns the same record as the list above, wrapped in a partner_item instead of an array.

last_updated is generated at request time, not stored.

Parameters

Parameters for GET /api/v2/classpass/{partner_id}
Name Type In Required Description
partner_id String route Required Required by the route, then ignored. The account comes from the host name.

Responses

Responses for GET /api/v2/classpass/{partner_id}
Status Meaning
200 A partner_item object. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/classpass/acme" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var response = await http.GetAsync("api/v2/classpass/" + partnerId);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();

GET /api/v2/classpass/{partner_id}/venues #

List the account's venues - one per Active ChiDesk site.

Do not drive paging from the pagination block

The pagination block on this response is not the pagination of this response. The venues array is paged by the page and page_size you sent, but the pagination object beside it is overwritten just before the response is built and always reads page 1, page_size 10, total_pages 1. Drive a paging loop from it and you will stop after the first page and miss every venue after the tenth. Page until you get a short or empty venues array instead, or ask for a page_size larger than the number of sites and read it once.

Only Active sites are published. An inactive site is not a venue.

venue_id is the ChiDesk siteId with its hyphens removed.

The country in the address is translated from the site's stored country name to a two-letter code where ChiDesk recognises the name. A country it does not recognise is passed through as the stored text, so handle both.

phone is always null on this endpoint.

last_updated is generated at request time, not read from the site.

Parameters

Parameters for GET /api/v2/classpass/{partner_id}/venues
Name Type In Required Description
partner_id String route Required Required by the route, then ignored.
page Int32 query Optional Which page of venues to return. Defaults to 1. The venues array honours this; the pagination block does not.
page_size Int32 query Optional How many venues per page. Defaults to 10.

Responses

Responses for GET /api/v2/classpass/{partner_id}/venues
Status Meaning
200 A venue_list object. The venues array is paged; the pagination block always reads page 1 of 1. See the fields
403 Authorization failed. See Errors.

Example

# Ask for more than you have, so the broken pagination block cannot mislead you.
curl -X GET \
  "https://acme.chidesk.com/api/v2/classpass/acme/venues?page=1&page_size=200" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
// Do NOT loop on pagination.total_pages here - it is hardcoded to 1.
var page = 1;
var pageSize = 50;
var venues = new List<JToken>();

while (true)
{
    var url = "api/v2/classpass/" + partnerId + "/venues"
            + "?page=" + page + "&page_size=" + pageSize;

    var response = await http.GetAsync(url);
    response.EnsureSuccessStatusCode();

    var body = JObject.Parse(await response.Content.ReadAsStringAsync());
    var batch = body["venues"].ToList();

    venues.AddRange(batch);

    if (batch.Count < pageSize) break;   // short page = last page
    page++;
}

GET /api/v2/classpass/{partner_id}/venues/{venue_id} #

Fetch one venue.

venue_id is a ChiDesk siteId, with or without hyphens. A value that is not a Guid in either form fails with a 500 rather than a 404.

Unlike the list, this reads the site's full address record. The same country-code translation applies.

phone is always null.

Parameters

Parameters for GET /api/v2/classpass/{partner_id}/venues/{venue_id}
Name Type In Required Description
partner_id String route Required Required by the route, then ignored.
venue_id String route Required The ChiDesk siteId, hyphens optional.

Responses

Responses for GET /api/v2/classpass/{partner_id}/venues/{venue_id}
Status Meaning
200 A venue_item object. See the fields
403 Authorization failed. See Errors.
500 venue_id was not a Guid in any recognised form.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/classpass/acme/venues/6f1b8c023a4d4e5f9a012b3c4d5e6f70" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var venueId = siteId.ToString("N");   // hyphens stripped

var response = await http.GetAsync(
    "api/v2/classpass/" + partnerId + "/venues/" + venueId);

response.EnsureSuccessStatusCode();

GET /api/v2/classpass/{partner_id}/venues/{venue_id}/services #

List the bookable services at a venue, with duration and cancellation window.

Only Active services are published, and services that exist only inside a package are left out.

duration and late_cancel_window are XML Schema durations - PT1H for an hour, PT30M for thirty minutes - not minutes and not ISO timestamps.

start_interval is the site's booking time scale in minutes: the granularity that start times are offered on.

late_cancel_window comes from the site's cancellation-period setting, so it is the same for every service at the venue.

Paging works correctly on this endpoint - the pagination block matches what you asked for. That is not true of the venues endpoint.

An allow-list line of api/v2/services does NOT reach this path, and a bare line of services would match both this and the ordinary service list. Leave the allow-list empty for ClassPass credentials.

Parameters

Parameters for GET /api/v2/classpass/{partner_id}/venues/{venue_id}/services
Name Type In Required Description
partner_id String route Required Required by the route, then ignored.
venue_id String route Required The ChiDesk siteId, hyphens optional.
page Int32 query Optional Which page to return. Defaults to 1.
page_size Int32 query Optional How many per page. Defaults to 10.

Responses

Responses for GET /api/v2/classpass/{partner_id}/venues/{venue_id}/services
Status Meaning
200 A service_list object with a correct pagination block. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/classpass/acme/venues/6f1b8c023a4d4e5f9a012b3c4d5e6f70/services?page=1&page_size=50" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
{
  "services": [
    {
      "service_id": "4b5c6d7e8f9041a2b3c4d5e6f7081920",
      "name": "Swedish Massage 60min",
      "description": "Full body relaxation massage.",
      "duration": "PT1H",
      "start_interval": 15,
      "late_cancel_window": "PT4H",
      "last_updated": "2026-07-28T08:14:22.1130000Z"
    }
  ],
  "pagination": { "page": 1, "page_size": 50, "total_pages": 1 }
}

GET /api/v2/classpass/{partner_id}/venues/{venue_id}/bookables #

List the practitioners who can be booked at a venue, and which services each one performs.

type is always PRACTITIONER. Booking a room or a piece of equipment rather than a person is not supported through this integration.

services on each bookable is the list of service ids that practitioner can perform, hyphens stripped. Cross-reference it with the services endpoint.

gender is reported as Man, Woman or "Not specified", which is the ClassPass vocabulary rather than ChiDesk's own.

Only employees who can take appointments are listed, so this is narrower than GET /api/v2/employees.

Paging works correctly on this endpoint.

Parameters

Parameters for GET /api/v2/classpass/{partner_id}/venues/{venue_id}/bookables
Name Type In Required Description
partner_id String route Required Required by the route, then ignored.
venue_id String route Required The ChiDesk siteId, hyphens optional.
page Int32 query Optional Which page to return. Defaults to 1.
page_size Int32 query Optional How many per page. Defaults to 10.

Responses

Responses for GET /api/v2/classpass/{partner_id}/venues/{venue_id}/bookables
Status Meaning
200 A bookable_list object with a correct pagination block. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/classpass/acme/venues/6f1b8c023a4d4e5f9a012b3c4d5e6f70/bookables" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
{
  "bookables": [
    {
      "type": "PRACTITIONER",
      "bookable_id": "a1b2c3d4e5f647089a1b2c3d4e5f6071",
      "first_name": "Thandi",
      "last_name": "Mokoena",
      "services": [ "4b5c6d7e8f9041a2b3c4d5e6f7081920" ],
      "gender": "Woman",
      "last_updated": "2026-07-28T08:14:22.1130000Z"
    }
  ],
  "pagination": { "page": 1, "page_size": 10, "total_pages": 1 }
}

GET /api/v2/classpass/{partner_id}/venues/{venue_id}/availability #

List each practitioner's free windows at a venue over a date range.

start_date and end_date are required, and they are the venue's LOCAL dates, not UTC. The windows that come back are local times in ISO 8601 with no zone designator - so 2026-08-03T09:00:00 means nine in the morning at that venue.

end_date behaves as exclusive: the days walked are start_date up to but not including end_date, because the count is the whole-day difference between them. Ask for one day past the last day you want.

The result is cached for five minutes against the exact combination of partner, venue, start_date and end_date - which is what makes paging through a large range cheap, and also means a booking made in the last five minutes may still show as free. Change the range or wait it out if you need it fresh.

A practitioner with no free windows in the range is left out of the response entirely rather than returned with an empty array.

Windows are computed from shifts (or the site's trading hours, for full-time staff), minus existing appointments and time off, and closed days are skipped altogether.

The pagination block is correct here, but note that total_pages is computed before the page is sliced.

Parameters

Parameters for GET /api/v2/classpass/{partner_id}/venues/{venue_id}/availability
Name Type In Required Description
partner_id String route Required Required by the route, then ignored.
venue_id String route Required The ChiDesk siteId, hyphens optional.
start_date String query Required First local date to search, ISO 8601.
end_date String query Required Exclusive end of the search, ISO 8601. Pass one day past the last day you want.
page Int32 query Optional Which page to return. Defaults to 1.
page_size Int32 query Optional How many per page. Defaults to 10.

Responses

Responses for GET /api/v2/classpass/{partner_id}/venues/{venue_id}/availability
Status Meaning
200 An availability_list object. Practitioners with no free time are omitted. See the fields
403 Authorization failed. See Errors.
500 venue_id was not a Guid, or a date could not be parsed.

Example

# Monday to Friday: end_date is exclusive, so ask through Saturday.
curl -X GET \
  "https://acme.chidesk.com/api/v2/classpass/acme/venues/6f1b8c023a4d4e5f9a012b3c4d5e6f70/availability?start_date=2026-08-03&end_date=2026-08-08" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
{
  "availabilities": [
    {
      "type": "PRACTITIONER",
      "bookable_id": "a1b2c3d4e5f647089a1b2c3d4e5f6071",
      "availability_windows": [
        { "start_datetime": "2026-08-03T09:00:00", "end_datetime": "2026-08-03T12:30:00" },
        { "start_datetime": "2026-08-03T13:30:00", "end_datetime": "2026-08-03T17:00:00" }
      ],
      "last_updated": "2026-07-29T07:59:22.1130000Z"
    }
  ],
  "pagination": { "page": 1, "page_size": 10, "total_pages": 1 }
}

POST /api/v2/classpass/{partner_id}/venues/{venue_id}/appointments #

Book an appointment for a ClassPass member.

ChiDesk creates the client for you when it has never seen the member's e-mail address before, using the name, e-mail, phone, gender and country from the user object. If the address is already on file the existing client is reused, so repeat members do not accumulate duplicates.

The placeholder mobile number +10000000000 is treated as no number and stored blank.

appointment_id is the id ClassPass assigns. ChiDesk converts it into a Guid and uses it as the appointment's service id, which is what makes the later status and attendance calls work - so it has to be the same value you send back on those.

Only the first entry in bookables is used. Sending several does not book several practitioners.

start_datetime is the venue's local time and is converted to UTC on the way in.

Where the site has a source named CLASSPASS, the appointment and any client created here are attributed to it. If no such source exists the booking still succeeds, unattributed - so create one if you want ClassPass business to show up separately in reporting.

Errors on this endpoint come back as 500 with the underlying message in the body, including ones that are really bad input - an unparseable start_datetime returns 500 with "Could not parse start date: ...", not a 400. Read the message, but do not retry a 500 from here without changing the request.

The response echoes the ClassPass appointment_id you sent. It does not return the ChiDesk appointment id.

Parameters

Parameters for POST /api/v2/classpass/{partner_id}/venues/{venue_id}/appointments
Name Type In Required Description
partner_id String route Required Required by the route, then ignored.
venue_id String route Required The ChiDesk siteId, hyphens optional.
appointment appointment body Required The ClassPass appointment: appointment_id, service_id, start_datetime, bookables and the user block.

Responses

Responses for POST /api/v2/classpass/{partner_id}/venues/{venue_id}/appointments
Status Meaning
200 { "appointment_id": "<the id you sent>" }.
400 No body was sent, or the body failed validation.
403 Authorization failed. See Errors.
500 Anything else went wrong, including a start_datetime that could not be parsed. The body carries the message.

Example

curl -X POST \
  "https://acme.chidesk.com/api/v2/classpass/acme/venues/6f1b8c023a4d4e5f9a012b3c4d5e6f70/appointments" \
  -H "Authorization: Bearer $CHIDESK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "appointment_id": "cp-9f0a1b2c3d4e5f60",
        "service_id": "4b5c6d7e8f9041a2b3c4d5e6f7081920",
        "start_datetime": "2026-08-03T09:00:00",
        "bookables": [ { "bookable_id": "a1b2c3d4e5f647089a1b2c3d4e5f6071" } ],
        "user": {
          "first_name": "Jo",
          "last_name": "Naidoo",
          "user_email": "jo@example.com",
          "phone": "+27821234567",
          "gender": "Female",
          "address": { "country": "ZA" }
        }
      }'
var content = new StringContent(
    JsonConvert.SerializeObject(appointment), Encoding.UTF8, "application/json");

var response = await http.PostAsync(
    "api/v2/classpass/" + partnerId + "/venues/" + venueId + "/appointments",
    content);

if ((int)response.StatusCode == 500)
{
    // Bad input arrives here too - read the message rather than retrying.
    var message = await response.Content.ReadAsStringAsync();
}

response.EnsureSuccessStatusCode();
// { "appointment_id": "cp-9f0a1b2c3d4e5f60" } - the id you sent, not ChiDesk's.

PATCH /api/v2/classpass/{partner_id}/venues/{venue_id}/appointments/{appointment_id} #

Cancel a ClassPass appointment.

This is the only PATCH in the whole API. A client library that only speaks GET, POST, PUT and DELETE will not reach it.

Only two status values do anything: CANCELLED moves the appointment to the site's cancelled state, and LATE CANCELLED does the same but prefers an inactive state named "Late Cancelled" if the site has one. Both are matched exactly, in upper case.

Any other status value is accepted and changes nothing, and the response is still 200. There is no validation of the value and no way to tell from the response that it was ignored.

An appointment_id that does not resolve returns 404 with "Appointment not found: <id>" - one of very few genuine 404s in this API.

Send the same appointment_id you sent when creating the appointment. ChiDesk converts it back to find the record.

A request with no body fails with a 500, not a 400 - the status is read without a null check.

Parameters

Parameters for PATCH /api/v2/classpass/{partner_id}/venues/{venue_id}/appointments/{appointment_id}
Name Type In Required Description
partner_id String route Required Required by the route, then ignored.
venue_id String route Required The ChiDesk siteId, hyphens optional. Used to resolve the site's cancelled state.
appointment_id String route Required The ClassPass appointment id used when the appointment was created.
status appointment_status body Required An object with a status field. Only CANCELLED and LATE CANCELLED have any effect.

Responses

Responses for PATCH /api/v2/classpass/{partner_id}/venues/{venue_id}/appointments/{appointment_id}
Status Meaning
200 The appointment was found. It was cancelled if the status was one of the two recognised values; otherwise nothing changed.
403 Authorization failed. See Errors.
404 No appointment matches that id - the body reads "Appointment not found: <id>".
500 No body was sent.

Example

curl -X PATCH \
  "https://acme.chidesk.com/api/v2/classpass/acme/venues/6f1b8c023a4d4e5f9a012b3c4d5e6f70/appointments/cp-9f0a1b2c3d4e5f60" \
  -H "Authorization: Bearer $CHIDESK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "status": "LATE CANCELLED" }'
// HttpClient has no PatchAsync on the older frameworks - build the request.
var request = new HttpRequestMessage(
    new HttpMethod("PATCH"),
    "api/v2/classpass/" + partnerId + "/venues/" + venueId
        + "/appointments/" + classPassAppointmentId)
{
    Content = new StringContent(
        "{ \"status\": \"CANCELLED\" }", Encoding.UTF8, "application/json")
};

var response = await http.SendAsync(request);

if (response.StatusCode == HttpStatusCode.NotFound)
{
    // The appointment id did not resolve.
}

GET /api/v2/classpass/{partner_id}/venues/{venue_id}/appointments/{appointment_id}/attendance #

Report whether a ClassPass member attended, for reconciliation after the fact.

Two outcomes, and a missing appointment reads as one of them

There are only two outcomes and no error case. The status is CANCELLED when the appointment is in the site's cancelled state, when it is in a "Late Cancelled" state - and also when no appointment with that id exists at all. Everything else reports ATTENDED, including an appointment that has not happened yet. So a typo in the id reads as a cancellation, and a booking asked about too early reads as attended. Call it only after the appointment should have happened, and confirm the id exists first.

There is no "not found", no "no show" and no "pending" - the two values are the whole vocabulary.

cp_user_id always comes back empty.

appointment_id is echoed back exactly as you sent it.

last_updated is generated at request time, not read from the appointment.

Parameters

Parameters for GET /api/v2/classpass/{partner_id}/venues/{venue_id}/appointments/{appointment_id}/attendance
Name Type In Required Description
partner_id String route Required Required by the route, then ignored.
venue_id String route Required The ChiDesk siteId, hyphens optional.
appointment_id String route Required The ClassPass appointment id used when the appointment was created.

Responses

Responses for GET /api/v2/classpass/{partner_id}/venues/{venue_id}/appointments/{appointment_id}/attendance
Status Meaning
200 An attendance_list object holding one attendance record, whose status is either ATTENDED or CANCELLED. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/classpass/acme/venues/6f1b8c023a4d4e5f9a012b3c4d5e6f70/appointments/cp-9f0a1b2c3d4e5f60/attendance" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
{
  "attendance": [
    {
      "cp_user_id": "",
      "appointment_id": "cp-9f0a1b2c3d4e5f60",
      "status": "ATTENDED",
      "last_updated": "2026-07-28T08:14:22.1130000Z"
    }
  ]
}

Appointment states

The states an appointment can be in - Booked, Arrived, Completed, Cancelled and whatever else the account has defined. Read this to get the state ids you pass to POST /api/v2/appointments/setstate, and to turn a state id in an appointment back into a name you can show.

Endpoint allow-list lines for this category

If the credential you are using has a non-empty endpoint allow-list, these are the lines that reach the endpoints below. One per line, and remember that routes ending in a record id cannot be listed at all - see Allow-lists on a credential.

  • api/v2/appointmentstates

GET /api/v2/appointmentstates #

List the appointment states configured at a site.

siteId is the only parameter, and it is required by name.

State names are per account and can be renamed, so never match on the name. Read the ids once at start-up and cache them.

Every state is returned, whether or not it counts as live. The Active flag on each row is what tells you which is which: a state with Active true is one where the appointment is still going to happen, and false covers the closed-out ones such as cancellations and no-shows.

States not tied to a single site belong to the whole account and are returned for every siteId.

Ordered by the sort order set in the back office, which is the order the account expects to see them in.

The list is cached per account and refreshed when the states change, so it is cheap to call - but do not poll it to detect configuration changes.

Parameters

Parameters for GET /api/v2/appointmentstates
Name Type In Required Description
siteId Guid query Required The site whose states to read.

Responses

Responses for GET /api/v2/appointmentstates
Status Meaning
200 An array of AppointmentState objects, ordered by sort order. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/appointmentstates?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
// Read once at start-up; the ids are stable, the names are not.
var response = await http.GetAsync(
    "api/v2/appointmentstates?siteId=" + siteId);

response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();
// Feed an AppointmentStateId to POST api/v2/appointments/setstate.

Sources

Where business came from - Walk-in, Website, Google, ClassPass, a referral scheme. Put a SourceId on a client or an appointment you create and it shows up in the account's own reporting under that heading.

Endpoint allow-list lines for this category

If the credential you are using has a non-empty endpoint allow-list, these are the lines that reach the endpoints below. One per line, and remember that routes ending in a record id cannot be listed at all - see Allow-lists on a credential.

  • api/v2/sources

GET /api/v2/sources #

List the active sources at a site.

siteId is the only parameter, and it is required by name.

Only Active sources are returned, and there is no parameter to change that - an inactive source cannot be listed here even though appointments may still reference it. If you meet a SourceId this endpoint does not explain, that is why.

Sources not tied to a single site belong to the whole account and are returned for every siteId.

Ordered by name.

The list is cached per account and refreshed when the sources change, so it is cheap to call.

Ask the account to add a source for your integration and tag what you create with it. That is what makes your bookings separable in their reporting - and it is how the ClassPass integration attributes its own.

Parameters

Parameters for GET /api/v2/sources
Name Type In Required Description
siteId Guid query Required The site whose sources to read.

Responses

Responses for GET /api/v2/sources
Status Meaning
200 An array of SourceList objects, ordered by name. See the fields
403 Authorization failed. See Errors.

Example

curl -X GET \
  "https://acme.chidesk.com/api/v2/sources?siteId=6f1b8c02-3a4d-4e5f-9a01-2b3c4d5e6f70" \
  -H "Authorization: Bearer $CHIDESK_TOKEN"
var response = await http.GetAsync("api/v2/sources?siteId=" + siteId);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();
// Put the SourceId on the clients and appointments you create.

Object schemas

The shape of every object the API returns or accepts. Each endpoint's Responses table links to the schema it returns, so you can go from a call to the fields it gives you without leaving the page.

These tables list what the JSON actually carries. A few properties exist in ChiDesk's own code but are never serialized, and those are called out on the schemas that have them rather than listed as fields you can read. Where a field's type is another object documented here, the type links to it instead of repeating the table - the object graph contains cycles, so it cannot be expanded inline.

AppointmentList #

One row of the diary. Returned by GET /api/v2/appointments - note that this is one row per booked service, not one per appointment, so an appointment with three services appears three times sharing an AppointmentId.

This type carries a TotalInvoicesNumeric property in C# that never appears in the JSON - it has no [DataMember] and the type is opt-in. Read TotalInvoices and treat null as zero yourself.

Fields of the AppointmentList object
Field Type Null Description
AppointmentId Guid No The appointment this row belongs to. Repeats across every service row of the same appointment.
AppointmentStateId Guid No Current state. Resolve against the Appointment states reference.
AppointmentState String Yes The state's display name, in the account's language.
AppointmentStateColour String Yes The state's diary colour as a hex string.
SiteId Guid No The site whose diary this row came from.
ClientId Guid Yes The client the appointment is billed to. Null for a walk-in with no client record.
ClientName String Yes Billed client's display name.
Phone String Yes Billed client's phone number.
Email String Yes Billed client's email address.
AppointmentClientId Guid Yes The client actually receiving the service, where that differs from the billed client - a booking made on someone else's behalf.
AppointmentClientName String Yes Display name of the receiving client.
AppointmentPhone String Yes Phone number of the receiving client.
AppointmentNumber String Yes The human-readable appointment reference shown in the back office.
CreationDate DateTime No When the appointment was created, UTC.
StartTime DateTime Yes Start of this service, UTC.
EndTime DateTime Yes End of this service, UTC.
AppointmentServiceId Guid No Identifies this specific service row - the value to use when you need to act on one service rather than the whole appointment.
Service String Yes Name of the booked service.
ServiceType String Yes The service's type/category name.
UserName String Yes The back-office user who created the appointment.
VIP Boolean Yes Whether the client is flagged VIP. Null where no client is attached.
Notes String Yes Free-text notes on the appointment.
RatingScore Int32 Yes The client's rating of this appointment, where one was captured.
TotalInvoices Decimal Yes Invoiced value against the appointment. Null where nothing has been invoiced - not zero.
TotalReceipts Decimal No Value received against the appointment.
EmployeeNames String[] Yes Employees assigned to this service, one entry each.
Employees String Yes The same employees pre-joined into a single display string.
ResourceNames String[] Yes Rooms or equipment assigned to this service.
Resources String Yes The same resources pre-joined into a single display string.
TotalServices Decimal No Value of the services on the appointment.
StartEndString String Yes Pre-formatted "09:00-10:30" for display. Empty string when either time is null.
StartString String Yes Pre-formatted start date, "Mon, 3 Aug 2026".
CreationDateString String Yes Pre-formatted creation date and time.
StartDateString String Yes Pre-formatted long start date, "Mon, 03 August 2026".
StartDate DateTime Yes Start time with the time component stripped - the date the appointment falls on.

Appointment #

A whole appointment and the services booked on it. Returned by GET /api/v2/appointments/{appointmentId}, and the shape POST and PUT /api/v2/appointments accept.

This is the nested shape: one appointment carrying its AppointmentServices. GET /api/v2/appointments returns something different - AppointmentList, one flat row per booked service. Do not expect the two to have the same fields. Note also that Rating here is a String, while AppointmentService.Rating is an Int32.

Fields of the Appointment object
Field Type Null Description
AppointmentId Guid No The appointment's id. Send an empty Guid when creating.
SiteId Guid No The site the appointment belongs to.
ClientId Guid Yes The client the appointment is billed to. Null for a walk-in with no client record.
ClientEmail String Yes The client's email address, carried on the appointment itself.
Room String Yes Hotel room number, on accounts integrated with a PMS.
ClientTypeId Guid Yes The client type the appointment was booked under.
AppointmentNumber String Yes The human-readable appointment reference. ChiDesk assigns this - do not invent one when creating.
AppointmentStateId Guid No Current state. Resolve against GET /api/v2/appointmentstates.
Notes String Yes Free-text notes on the appointment.
Rating String Yes The client's free-text feedback. A String here, unlike AppointmentService.Rating.
RatingScore Int32 Yes The client's numeric rating, where one was captured.
SourceId Guid Yes How the booking originated.
UserId Guid Yes Back-office user who created the appointment.
VIP Boolean Yes Whether the client is flagged VIP. Null where no client is attached.
SeriesId Guid Yes Groups appointments booked as one recurring series. Null on a one-off.
TotalInvoices Decimal No Invoiced value against the appointment. Zero rather than null on this shape, unlike AppointmentList where it is nullable.
TotalReceipts Decimal No Value received against the appointment.
AppointmentServices AppointmentService[] Yes The booked services. Returned sorted by Start.

AppointmentService #

One service booked on an appointment - who is doing it, where, and when. Carried inside Appointment, and the shape the cartItems body takes on POST /api/v2/appointments/validate and availabletimes.

IsPreferredEmployee is always false on an API response. It is computed only by the back-office diary query, not by the fetch this endpoint uses, and its setter is internal so sending it has no effect either. Treat it as absent. Note also that the four Prep and Recovery fields are why a service's Duration and the diary block it occupies are different numbers - that is by design, not a bug.

Fields of the AppointmentService object
Field Type Null Description
AppointmentServiceId Guid No This service row's id - the value to use when acting on one service rather than the whole appointment.
AppointmentId Guid No The appointment this service belongs to.
AppointmentStateId Guid No State of this service row. A service can be in a different state from its appointment.
AppointmentUserId Guid Yes Back-office user who booked this service.
LabelId Int32 No The appointment state's SortOrder, copied here for the diary's label colour. Not an id despite the name - resolve state through AppointmentStateId.
StatusId Int32 No The calendar colour's SortOrder, copied here for the diary. Not an id either.
ClientId Guid Yes The client receiving this service, where it differs from the billed client.
ClientName String Yes That client's display name. Populated by the fetch, not stored.
ServiceId Guid Yes The service booked. Null when this row is part of a package.
PackageId Guid Yes The package booked, where the row came from one.
PackageGroupId Guid Yes Groups the service rows that came from the same package purchase.
Start DateTime No Start of this service, UTC.
End DateTime No End of this service, UTC. Covers the treatment time only - see the Prep and Recovery fields.
Description String Yes The service's description as it appears on the diary block.
Rating Int32 Yes The client's numeric rating of this service. An Int32 here, unlike Appointment.Rating which is a String.
CalendarColour String Yes Diary colour for this row as a hex string.
AppointmentNotes String Yes Notes on this service row, distinct from the appointment's own Notes.
VIP Boolean No Whether the receiving client is flagged VIP.
Member Boolean No Whether the receiving client is on a membership.
TotalInvoices Decimal No Invoiced value against this service.
TotalReceipts Decimal No Value received against this service.
Invoiced Boolean No Whether this service has been invoiced. Check this rather than comparing TotalInvoices to zero - a zero-value service can still be invoiced.
ResourceIds String Yes The assigned resource ids as a single delimited string, for the back-office editor. Read AppointmentResourceIds instead.
AppointmentResourceIds Guid[] Yes The assigned resource ids as an array. This is the machine-readable form.
EmployeeNames String Yes Assigned employees as "First Last" joined with ", ". Built by the fetch from the Employees list.
ResourceNames String Yes Assigned resource names joined with ", ". Built by the fetch from the Resources list.
Employees Employee[] Yes The assigned employees as full Employee objects - a larger shape than EmployeeList, carrying the employee's own working hours and sites. This is where the object graph becomes cyclic, so it is not expanded here.
Resources Resource[] Yes The assigned rooms or equipment. Each Resource carries ResourceId, SiteId, Name, State, Description, DefaultEmployeeId, ShowOnCalendar, ShowOnClasses, CalendarSortOrder, MaxPax and MaxPaxPerAppointment.
EmployeePrep Int32 No Minutes an employee is reserved before the service starts.
EmployeeRecovery Int32 No Minutes an employee is reserved after it ends.
ResourcePrep Int32 No Minutes the room or equipment is reserved beforehand - cleaning, setup.
ResourceRecovery Int32 No Minutes it is reserved afterwards.
IsPreferredEmployee Boolean No Always false here. See the note above.

AvailableTime #

One slot a booking could be made in, with the staff and rooms that would be assigned to it. Returned by GET and POST /api/v2/appointments/availabletimes.

EmployeesString and ResourcesString are the Employees and Resources arrays already run through a JSON serializer, so they arrive as a STRING containing JSON - you would have to parse them a second time. They exist for the back-office booking screen. Read the Employees and Resources arrays instead and ignore both string fields.

Fields of the AvailableTime object
Field Type Null Description
Start DateTime No Start of the available slot, UTC. This is the value to send back when creating the appointment.
StartTime String Yes Start pre-formatted for display. Built with a 24-hour hour token followed by an AM/PM marker, so it reads like "14:30 PM" in the afternoon - display it as-is at your own risk, and format Start yourself instead.
End DateTime No End of the slot, UTC.
EndTime String Yes End pre-formatted the same way, with the same quirk.
Employees AvailableEmployee[] Yes Which employees would be assigned. Each entry carries EmployeeId, PackageItemId (set when the slot is for one item of a package) and ServiceIndex (which service in the requested sequence it applies to).
Resources AvailableResource[] Yes Which rooms or equipment would be assigned, in the same shape: ResourceId, PackageItemId and ServiceIndex.
EmployeesString String Yes The Employees array as a JSON string. See the note above.
ResourcesString String Yes The Resources array as a JSON string. See the note above.

EmployeeList #

A member of staff. Returned by GET /api/v2/employees.

Fields of the EmployeeList object
Field Type Null Description
EmployeeId Guid Yes The employee's id.
Name String Yes Full display name.
FirstName String Yes First name.
LastName String Yes Last name.
Email String Yes Email address.
HomePhone String Yes Home phone number.
MobilePhone String Yes Mobile phone number.
Position String Yes Job title.
Category String Yes The employee category they belong to.
WorkType Byte No Employment type as a number: 0 Shift, 1 Locum, 2 Full time. WorkTypeName carries the same value already resolved to text.
MaxPax Int32 No How many clients the employee can handle at once.
MaxPaxPerAppointment Boolean No Whether MaxPax is counted per appointment rather than across the whole time slot.
Gender Byte Yes Gender code, where recorded.
CalendarSortOrder Int32 No Position of this employee's column in the diary.
ShowOnCalendar Boolean No Whether the employee appears in the diary.
ShowOnClasses Boolean No Whether the employee can be assigned to classes.
Image Byte[] Yes Profile photo as raw bytes, base64-encoded in the JSON. Null where none is set - expect this field to dominate the response size when it is populated.
WorkTypeName String Yes WorkType resolved to text in the account's language - "Shift", "Locum" or "Full time". Match on WorkType, not on this.
Initials String Yes First and last initials, as shown on diary blocks.

EmployeeSchedule #

One block of an employee's roster - a shift, a break, leave, or a class they are taking. Returned by GET /api/v2/employees/schedules.

The text form of the type is spelled SchedulerTypeName on the wire, not ScheduleTypeName - the extra "r" is in ChiDesk's own property name and is what the JSON carries. Match on the numeric ScheduleType instead: 0 Shift, 1 Unavailable, 2 SickLeave, 3 Leave, 4 FamilyLeave, 5 MaternityLeave, 6 UnpaidLeave, 7 Off, 8 PublicHoliday, 9 Break, 10 Training, 99 Teaching. Note that Teaching is 99, not 11.

Fields of the EmployeeSchedule object
Field Type Null Description
EmployeeScheduleId Guid No The schedule block's id.
EmployeeId Guid No The employee this block belongs to.
SiteId Guid No The site the block was rostered at.
Notes String Yes Free-text notes on the block.
Start DateTime No Start of the block, UTC.
End DateTime No End of the block, UTC.
ScheduleType Byte No What kind of block this is, as a number. See the note above for the values - this is the field to match on.
SchedulerTypeName String Yes ScheduleType resolved to text in the account's language. Empty string, not null, for a value the switch does not recognise. Note the spelling.
Label Int32 No ScheduleType again, widened to an integer. Carries no information ScheduleType does not - it exists for the back-office scheduler control.
GroupId Guid Yes Groups blocks that were created together as one recurring pattern. Null on a one-off block.
ResourceId Guid Yes The room or equipment tied to the block, where one is. Populated on class blocks pulled in by includeClasses=true.

TimeRecordList #

One clock-in/clock-out pair for a member of staff. Returned by GET /api/v2/timerecords.

This is the list shape, so the employee arrives as a name string only - there is no EmployeeId on it. Match staff up by name, or read the roster from GET /api/v2/employees/schedules instead, which does carry ids.

Fields of the TimeRecordList object
Field Type Null Description
TimeRecordId Guid No The time record's id.
Employee String Yes The employee's display name. No id is returned on this shape.
StartTime DateTime No When the employee clocked in, UTC.
EndTime DateTime Yes When they clocked out, UTC. Null while they are still clocked in - so a null here means an open shift, not missing data.
Notes String Yes Free-text notes captured against the record.

SiteList #

One location on the account. Returned by GET /api/v2/sites - the endpoint that gives you the siteId nearly every other call needs.

Fields of the SiteList object
Field Type Null Description
SiteId Guid Yes The site's id. This is the value to pass as siteId elsewhere in the API.
Name String Yes Site name.
TimeZone String Yes Windows time-zone id for the site. Appointment times are UTC, so this is what you convert with for display.
Street String Yes Street address.
City String Yes City.
State String Yes Province or state. This is the address field, not a status - the site's active/inactive flag is not returned here.
Code String Yes Postal code.
Country String Yes Country.
Phone String Yes Contact phone number.
Email String Yes Contact email address.
Web String Yes Website address.
MainSite Boolean No True on the account's main site. Endpoints that default a missing siteId use this one.

Site #

The full record for one location, including how it formats money, dates and numbers. Returned by GET /api/v2/sites/{siteId}.

Two differences from SiteList worth planning for. The address arrives here as a nested Address object rather than flattened into Street/City/Country fields, so the same data sits at a different path. And this shape carries the site's currency, date, time and number formats, which the list does not - fetch a site once at startup if you need to render its values the way the back office does.

Fields of the Site object
Field Type Null Description
SiteId Guid No The site's id.
State Byte No Item state as a number - 1 Active, 2 Inactive. SiteList does not return this.
Name String Yes Site name.
Code String Yes The account's own code for the site.
CurrencyCode String Yes Currency the site trades in, e.g. "ZAR". Every money field on every endpoint scoped to this site is in this currency - the API does not tag amounts individually.
DateFormat String Yes How the site formats dates in the back office. Does not affect the API, which is ISO 8601 throughout.
TimeFormat String Yes How the site formats times in the back office.
NumberFormat String Yes How the site formats numbers in the back office.
TimeZone String Yes Windows time-zone id. Appointment and document times are UTC, so this is what you convert with for display.
TaxRegistered Boolean No Whether the site is registered for tax. Where false, the tax figures on documents are zero by definition rather than by omission.
TaxNumber String Yes The site's tax or VAT number.
RegistrationNumber String Yes Company registration number.
Email String Yes Contact email address.
Phone String Yes Contact phone number.
Fax String Yes Fax number.
Web String Yes Website address.
MainSite Boolean No True on the account's main site. Endpoints that default a missing siteId use this one.
ContactDetails String Yes Free-text contact block as printed on documents.
BankingDetails String Yes Free-text banking block as printed on invoices.
MedicalDetails String Yes Free-text practice details, for accounts billing medical aids.
Address Address Yes The site's address as a nested object, not flattened as in SiteList.
Language String Yes The site's language. This is what decides the language of every name the API resolves to text.

ClientList #

One client as it appears in a list or search result. Returned by GET /api/v2/clients.

This is the list shape, not the full record - it carries no addresses, no notes, no consent flags and no custom fields. Fetch GET /api/v2/clients/{clientId} for those. Note also that Image is on this shape: a list of clients with photos is a large response, and there is no parameter to suppress it.

Fields of the ClientList object
Field Type Null Description
ClientId Guid Yes The client's id. De-duplicate on this - an account-wide client is returned by every site's list.
SiteId Guid Yes The site the client belongs to. Null on an account-wide client.
Name String Yes Full display name, first and last joined.
FirstName String Yes First name.
LastName String Yes Last name. The list comes back ordered by this.
Code String Yes The account's own code for the client.
Email String Yes Email address.
Gender Byte Yes Gender code, where recorded.
BirthMonth Int32 Yes Month of birth, 1-12. Held separately from the day and year because a client may give only part of a birthday.
BirthDay Int32 Yes Day of the month of birth.
BirthYear Int32 Yes Year of birth. Commonly null even where month and day are set - do not compute an age without checking.
Category String Yes Client category name.
HomePhone String Yes Home phone number.
MobilePhone String Yes Mobile phone number.
BusinessPhone String Yes Business phone number.
MembershipId Guid Yes The membership the client is on, where they have one.
Membership String Yes That membership's display name.
MembershipNumber String Yes The client's membership number.
MembershipEnd DateTime Yes When the membership runs to. Null on an open-ended membership.
AccountBalance Decimal No The client's account balance.
LoyaltyPoints Decimal No Loyalty points the client currently holds.
Country String Yes Country from the client's address.
CreationDate DateTime No When the client record was created, UTC. This is what the creationDate query parameter filters from.
Image Byte[] Yes Profile photo as raw bytes, base64-encoded in the JSON. Null where none is set.
Initials String Yes First and last initials, derived from the name.

Client #

The full client record. Returned by GET /api/v2/clients/{clientId}, and the shape POST and PUT /api/v2/clients accept.

Two rows to read before you size a client response: ClientDocuments carries whole files inline, which is what makes this response large, and ClientCourses is already filtered to unused sessions rather than being a full history. To check a client's password, use GET /api/v2/clients/authenticate.

Fields of the Client object
Field Type Null Description
ClientId Guid No The client's id. Send an empty Guid when creating.
State Byte No Item state as a number - 1 Active, 2 Inactive.
SiteId Guid Yes The site the client belongs to. Null makes the client account-wide.
CreationDate DateTime No When the record was created, UTC.
Title String Yes Salutation, e.g. "Mrs".
FirstName String Yes First name.
LastName String Yes Last name.
Code String Yes The account's own code for the client.
Category String Yes Client category name.
MembershipId Guid Yes The membership the client is on.
MembershipStart DateTime Yes When the membership started.
MembershipEnd DateTime Yes When it runs to. Null on an open-ended membership.
MembershipNumber String Yes The client's membership number.
HomePhone String Yes Home phone number.
BusinessPhone String Yes Business phone number.
MobilePhone String Yes Mobile phone number. This is what reminders are sent to.
Email String Yes Email address. Also the client's portal username.
Fax String Yes Fax number.
WebAddress String Yes Website address.
TaxNumber String Yes Tax or VAT number, for a client invoiced as a business.
IDNumber String Yes National identity number, where the account captures one.
Company String Yes Company name as free text. Distinct from CompanyClientId, which points at another client record.
Occupation String Yes Occupation.
Industry String Yes Industry.
BirthMonth Int32 Yes Month of birth, 1-12. Held apart from day and year because a client may give only part of a birthday.
BirthDay Int32 Yes Day of the month of birth.
BirthYear Int32 Yes Year of birth. Commonly null even where month and day are set - do not compute an age without checking.
DueDate DateTime Yes Expected due date, for accounts offering pregnancy treatments.
Gender Byte Yes The client's gender code.
GenderPreference Byte Yes The gender of therapist the client prefers. A preference about staff, not about the client.
Room String Yes Hotel room number, on accounts integrated with a PMS. A non-null value here changes which client a sale is invoiced to.
SourceOther String Yes Free-text referral source, used when the source is not one of the configured ones.
CompanyClientId Guid Yes Another client record that represents this client's company.
InvoiceCompany Boolean No Whether sales are invoiced to CompanyClientId instead of this client.
AccountPassword String Yes Create-time only: send a portal password here on POST to give the client one, and ChiDesk stores only a hash of it. Leave it out and the client has no password. On update use NewAccountPassword instead - a value sent here on a PUT is written to the record as sent, and does not change the portal password.
NewAccountPassword String Yes Write-only in practice: set this on a PUT to change the portal password. Null on a read.
AccountVerified Boolean No Whether the client has verified their email address.
EmployeeId Guid Yes The employee the client prefers to be seen by.
MaritalStatus Byte Yes Marital status code.
Children Int32 Yes Number of children.
VIP Boolean No Whether the client is flagged VIP.
Blocked Boolean No Whether the client is blocked from booking.
Language String Yes The client's preferred language, used for messages sent to them.
MedicalAidId Guid Yes The medical aid scheme the client is on.
MedicalAidNumber String Yes Their medical aid number.
MedicalAidPlan String Yes Their plan on that scheme.
EarnLoyalty Boolean No Whether the client accrues loyalty points.
LoyaltyPoints Decimal No Points currently held.
ReferralClientId Guid Yes The client who referred this one.
SourceId Guid Yes The configured referral source the client came from.
PhysicalAddress Address Yes Where the client lives.
BillingAddress Address Yes Where invoices are addressed.
PostalAddress Address Yes Where post is sent.
ReceiveMarketingMessages Boolean No Marketing consent. GET /api/v2/clients/contacts returns only clients where this is true, and no parameter overrides that.
ReceiveReminders Boolean No Whether the client wants appointment reminders. Separate from marketing consent.
ReceiveCourseNotifications Boolean No Whether the client wants course-related notifications.
ContactSms Boolean No Whether the client accepted SMS. What contactSms filters on.
ContactEmail Boolean No Whether the client accepted email. What contactEmail filters on.
ContactTelephone Boolean No Whether the client accepted phone calls. What contactPhone filters on - note the parameter is named contactPhone but the field is ContactTelephone.
AccountBalance Decimal No The client's account balance.
TermsAccepted DateTime Yes When the client accepted the account's terms. Null where they have not.
OTPExpiry DateTime Yes When the client's current one-time code expires. Null where no code is outstanding.
Image Byte[] Yes Profile photo as raw bytes, base64-encoded in the JSON.
ClientNotes ClientNote[] Yes Notes on the client. Each entry carries ClientNoteId, ClientId, CreationDate, ClientNoteType, Body, Category, UserId, Encrypted and ObjectState. Where Encrypted is true the Body is ciphertext, not readable text.
ClientDocuments ClientDocument[] Yes Files attached to the client. Each entry carries a Contents byte array holding the WHOLE file inline, base64-encoded - this is what makes a client response large. Also Name, Category, Filename, Url, Notes and ObjectState.
CustomForms ClientCustomForm[] Yes Custom forms the client has completed.
ClientCourses ClientCourse[] Yes The client's course sessions, ALREADY FILTERED to those not yet redeemed - a used session is absent, so treat this as the remaining balance rather than a history. Each entry carries ClientCourseId, CourseId, CourseServiceId, CourseServiceName, CourseNumber, the buying and redeeming document ids and numbers, ExpiryDate and ObjectState.
ClientCustomFieldValues ClientCustomFieldValue[] Yes Values for the account's own client fields. Each entry carries ClientId, CustomFieldId and Value - resolve the field ids from the back office.
Alerts Alert[] Yes Alerts raised against this client. Unlike GET /api/v2/alerts, Selected is meaningful here.

ClientContact #

A marketing-list row: a client who has agreed to be contacted. Returned by GET /api/v2/clients/contacts.

The consent flags themselves are NOT on this shape. The endpoint filters by channel, but the rows carry no ContactEmail, ContactSms or ContactTelephone, so a response cannot tell you which channel a given client accepted - only that they passed the filter you asked for. Query one channel at a time if you need to know, or read the flags from the full Client record.

Fields of the ClientContact object
Field Type Null Description
ClientId Guid Yes The client's id.
Name String Yes Full display name.
Title String Yes Salutation, for addressing the message.
FirstName String Yes First name.
LastName String Yes Last name. The list comes back ordered by this.
Code String Yes The account's own code for the client.
Email String Yes Email address. Null where the client has none on file, even when they accepted email.
Category String Yes Client category name - useful for segmenting the list.
HomePhone String Yes Home phone number.
MobilePhone String Yes Mobile phone number. This is the SMS destination.
BusinessPhone String Yes Business phone number.
Country String Yes Country from the client's address.
MembershipId Guid Yes The membership the client is on.
Membership String Yes That membership's display name.
MembershipNumber String Yes The client's membership number.
AccountBalance Decimal No The client's account balance.
LoyaltyPoints Decimal No Loyalty points the client currently holds.

AppointmentHistory #

One past service a client received, for building a treatment history. Returned by GET /api/v2/clients/appointmenthistory - one row per service, so a visit with three services appears three times sharing an AppointmentId.

This type carries an AccountingDocumentId property in C# that never appears in the JSON - it has no [DataMember] and the type is opt-in. You get InvoiceNumber but not the invoice's id, so to reach the document itself match the number against GET /api/v2/accountingdocuments rather than expecting an id here.

Fields of the AppointmentHistory object
Field Type Null Description
AppointmentId Guid No The appointment this service belonged to. Repeats across every service row of the same appointment.
AppointmentNumber String Yes The human-readable appointment reference.
AppointmentDate DateTime No When the service was delivered, UTC.
ServiceName String Yes Name of the service received.
Duration Int32 Yes Treatment time in minutes. Null where the service no longer carries one.
AppointmentStateName String Yes The appointment's state as text, in the account's language. Only the name is returned here - no state id and no colour, unlike AppointmentList.
InvoiceNumber String Yes Number of the invoice the service was billed on. Null where it was never invoiced.
EmployeeNames String[] Yes Employees who delivered the service, one entry each.
Employees String Yes The same employees pre-joined into a single display string.

ClientCustomForm #

One completed custom form - a consultation card, intake questionnaire or consent form - with the client's answers on it. Returned by GET /api/v2/clientcustomforms.

The answers are keyed by CustomFormFieldId, and this endpoint does not return the form definition those ids belong to. You need the field ids from the back office before the values mean anything. Each answer carries both Value and Values - single-choice and free-text answers populate Value, multi-select answers populate Values, and the other one is null.

Fields of the ClientCustomForm object
Field Type Null Description
ClientCustomFormId Guid No This submission's id.
ClientId Guid Yes The client who completed the form. Nullable on the type, though the back office requires it.
SiteId Guid No The site the form was captured at.
UserId Guid Yes Back-office user who captured it. Null where the client completed it themselves.
CustomFormNumber String Yes The human-readable reference for this submission.
CustomFormId Guid No Which form definition was filled in. The same value across every submission of that form - this is not the submission's own id.
Signature String Yes The client's captured signature, as a data string. Null where the form was not signed - expect this field to be large when it is populated.
ClientCustomFormValues ClientCustomFormValue[] Yes The answers. Each entry carries ClientCustomFormId, CustomFormFieldId, Value (String) and Values (String array). See the note above.

ServiceList #

A bookable service as it appears in the catalogue. Returned by GET /api/v2/services.

SpecialValidity exists on this class in C# but is never serialized - it has no [DataMember] and the type is opt-in. Do not expect it in the response.

Fields of the ServiceList object
Field Type Null Description
ServiceId Guid Yes The service's id.
Name String Yes Display name.
Category String Yes Catalogue category name.
Code String Yes The account's own service code.
BarCode String Yes Barcode, where one is set.
Duration Int32 No Client-facing treatment time in minutes. This is not the diary block - the diary also reserves preparation and recovery time, so a service shown here as 60 can occupy more of the diary.
StartTime DateTime Yes Fixed start time, for services that only run at a set time.
ServiceTypeId Guid No The service type this belongs to.
ServiceTypeName String Yes The service type's display name.
ServiceTypeAccount String Yes Ledger account the service type posts to.
Cost Decimal No Cost price.
SellInclusive Decimal Yes Selling price including tax.
Commission Decimal Yes Commission value or percentage, per the account's commission setup.
LoyaltyPoints Decimal No Loyalty points earned on this service.
Description String Yes Long description, as shown to clients online.
CalendarColour String Yes Diary colour as a hex string.
SellOnline Boolean No Whether the service is bookable through online booking.
SellVoucher Boolean No Whether the service can be sold as a voucher.
Special Boolean No Whether the service is currently on special.
PackageService Boolean No Whether this service exists only as part of a package. GET /api/v2/services excludes these unless showPackageServices=true.
RequiredEmployees Int32 No How many employees the service needs booked against it.
RequiredResources Int32 No How many resources (rooms, equipment) the service needs.

ProductList #

A stock item. Returned by GET /api/v2/products.

SpecialValidity exists on this class in C# but is never serialized - it has no [DataMember] and the type is opt-in.

Fields of the ProductList object
Field Type Null Description
ProductId Guid Yes The product's id.
ProductTypeId Guid No The product type this belongs to.
ProductTypeName String Yes The product type's display name.
AllowSale Boolean No Whether the product may be sold. The allowSale query parameter filters on this - omit it and you get the NOT-for-sale products.
Name String Yes Display name.
SupplierId Guid Yes The supplier this product is bought from.
Supplier String Yes The supplier's display name.
Category String Yes Catalogue category name.
Code String Yes The account's own product code.
Brand String Yes Brand name.
ShelfLocation String Yes Where the stock physically sits.
BarCode String Yes Barcode, where one is set.
Cost Decimal No Cost price.
Quantity Decimal No Stock on hand at the site the call was scoped to.
ReorderLevel Decimal No Stock level at which to reorder.
ReorderPoint Decimal No Quantity to order when reordering.
MinOrder Decimal No Minimum order quantity the supplier accepts.
UnitQuantity Decimal No How many base units make up one sellable unit.
Unit String Yes Name of the unit, e.g. "ml".
SellInclusive Decimal No Current selling price including tax.
BaseSellInclusive Decimal No Selling price before any price-list or special adjustment. Compare with SellInclusive to see whether a discount is in force.
Commission Decimal No Commission value or percentage, per the account's commission setup.
LoyaltyPoints Decimal No Loyalty points earned on this product.
Description String Yes Long description, as shown to clients online.
Image Byte[] Yes Product image as raw bytes, base64-encoded in the JSON. Null where none is set.
SellOnline Boolean No Whether the product is sellable through online booking.

PackageList #

Several items sold together as one product. Returned by GET /api/v2/packages.

SpecialValidity exists on this class in C# but is never serialized - it has no [DataMember] and the type is opt-in.

Fields of the PackageList object
Field Type Null Description
PackageId Guid Yes The package's id.
Name String Yes Display name.
Code String Yes The account's own package code.
Category String Yes Catalogue category name.
BarCode String Yes Barcode, where one is set.
Duration Int32 No Client-facing treatment time in minutes. As with a service, this is not the diary block - the diary also reserves preparation and recovery time, so the two numbers differ by design.
StartTime DateTime Yes Fixed start time, for packages that only run at a set time.
Cost Decimal No Cost price.
SellInclusive Decimal No Selling price including tax.
Commission Decimal No Commission value or percentage, per the account's commission setup.
LoyaltyPoints Decimal No Loyalty points earned on the package.
Image Byte[] Yes Package image as raw bytes, base64-encoded in the JSON. Null where none is set.
FirstItemTypeId Byte No Item type of the first item in the package - what the package leads with.
Description String Yes Long description, as shown to clients online.
SellOnline Boolean No Whether the package is bookable through online booking.
SellVoucher Boolean No Whether the package can be sold as a voucher.
Special Boolean No Whether the package is currently on special.

CourseList #

A course of treatments sold as one item. Returned by GET /api/v2/courses.

SpecialValidity exists on this class in C# but is never serialized - it has no [DataMember] and the type is opt-in.

Fields of the CourseList object
Field Type Null Description
CourseId Guid Yes The course's id.
Name String Yes Display name.
Category String Yes Catalogue category name.
Code String Yes The account's own course code.
BarCode String Yes Barcode, where one is set.
Duration Decimal No Treatment time in minutes for one session of the course.
Cost Decimal No Cost price.
SellInclusive Decimal Yes Selling price including tax.
Commission Decimal No Commission value or percentage, per the account's commission setup.
LoyaltyPoints Decimal No Loyalty points earned on the course.
Description String Yes Long description, as shown to clients online.
SellOnline Boolean No Whether the course can be bought through online booking.

MembershipList #

A membership product clients can subscribe to. Returned by GET /api/v2/memberships.

SpecialValidity exists on this class in C# but is never serialized - it has no [DataMember] and the type is opt-in. The membership's rules - expiry, booking limits, activation - are not on this list shape either; it is the catalogue view.

Fields of the MembershipList object
Field Type Null Description
MembershipId Guid Yes The membership's id.
Name String Yes Display name.
Category String Yes Catalogue category name.
Description String Yes Long description, as shown to clients online.
Code String Yes The account's own membership code.
Cost Decimal No Cost price.
SellInclusive Decimal Yes Selling price including tax.
Commission Decimal Yes Commission value or percentage, per the account's commission setup.
LoyaltyPoints Decimal No Loyalty points earned on the membership.
SellOnline Boolean No Whether the membership can be bought through online booking.

ClassList #

A class in the catalogue - the definition, not a scheduled occurrence of it. Returned by GET /api/v2/classes.

Do not confuse this with ClassScheduleList. This is the class as a sellable thing; a ClassScheduleList row is one dated, staffed instance of it that clients actually book.

Fields of the ClassList object
Field Type Null Description
ClassId Guid No The class's id.
Name String Yes Display name.
ClassLevel String Yes The class level's display name, e.g. Beginner.
Category String Yes Catalogue category name.
Start DateTime No First date the class runs from.
End DateTime No Last date the class runs to.
StartTime DateTime No Time of day the class starts.
StartTimeString String Yes StartTime pre-formatted as a short time in the account's culture, for display. Match on StartTime.

Class #

The full class definition, including the weekly pattern its occurrences are generated from. The shape POST and PUT /api/v2/classes accept.

This is where a class's recurrence lives: the seven day flags plus StartTime and EndTime describe the weekly pattern, and Start and End bound the period it runs over. ChiDesk generates the dated occurrences from that - you do not create ClassSchedules yourself. ClassList, returned by GET /api/v2/classes, is a much smaller catalogue shape and carries none of this.

Fields of the Class object
Field Type Null Description
ClassId Guid No The class's id. Send an empty Guid when creating.
State Byte No Item state as a number - 1 Active, 2 Inactive.
SiteId Guid No The site the class belongs to. Required in the body on a PUT - it decides which site the record is saved against.
ClassType Byte No The class type as a number. The same set the classType query parameter accepts elsewhere.
Name String Yes Display name. Required.
Code String Yes The account's own class code.
Category String Yes Catalogue category name.
Description String Yes Long description, as shown to clients online.
ServiceId Guid Yes The service delivered by the class. Required despite being nullable on the type - this is what the class is priced and reported as.
ClassLevelId Guid Yes The class level, e.g. Beginner.
Start DateTime No First date the class runs from.
End DateTime No Last date it runs to. Together with the day flags this bounds how many occurrences are generated.
Monday Boolean No Whether the class runs on Mondays.
Tuesday Boolean No Whether the class runs on Tuesdays.
Wednesday Boolean No Whether the class runs on Wednesdays.
Thursday Boolean No Whether the class runs on Thursdays.
Friday Boolean No Whether the class runs on Fridays.
Saturday Boolean No Whether the class runs on Saturdays.
Sunday Boolean No Whether the class runs on Sundays.
StartTime DateTime No Time of day the class starts. Only the time component is meaningful.
EndTime DateTime No Time of day it ends.
MaxPax Int32 No Total capacity of each occurrence.
MinPax Int32 No Minimum attendance for an occurrence to run.
BookOnline Boolean No Whether the class is offered for online booking.
MaxOnlineBookings Int32 No Cap on how many places may be taken online. A subset of MaxPax, not a second allowance.
Location String Yes Where the class is held.
EmployeeId Guid Yes The employee who normally takes it.
ResourceId Guid Yes The room or equipment it normally uses.
ClassSchedules ClassSchedule[] Yes The generated occurrences. Each entry carries ClassScheduleId, Location, ClassId, ClassName, ClassDescription, ClassType, ClassServiceId, SiteId, ClassScheduleState, Start, End, the employee and resource with their names, MaxPax, BookOnline, MaxOnlineBookings and its own ClassBookings. Note this is a different shape from ClassScheduleList, which the read endpoint returns.

ClassScheduleList #

One dated, staffed occurrence of a class, with how full it is. This is the shape clients book against.

Fields of the ClassScheduleList object
Field Type Null Description
ClassScheduleId Guid No This occurrence's id - the value a booking is made against.
Location String Yes Where the class is held.
ClassId Guid No The class this is an occurrence of.
Class String Yes The class's display name.
ClassLevel String Yes The class level's display name.
ClassType ClassTypes No The class type, serialized as its enum value.
Category String Yes Catalogue category name.
ClassScheduleState Byte No State of this occurrence as a number - scheduled, cancelled and so on.
Start DateTime No Start of the occurrence, UTC.
End DateTime No End of the occurrence, UTC.
EmployeeId Guid Yes The employee taking the class. Null where none is assigned yet.
Employee String Yes That employee's display name.
ResourceId Guid Yes The room or equipment booked for it.
Resource String Yes That resource's display name.
Bookings Int32 No How many places are taken in total.
OnlineBookings Int32 No How many of those came through online booking. Compare with MaxOnlineBookings, which caps only this subset.
MaxPax Int32 No Total capacity. Bookings against this is what tells you whether the class is full.
MinPax Int32 No Minimum attendance for the class to run.
MaxOnlineBookings Int32 No Cap on how many of the places may be taken online. A class can be bookable in person while online booking is already exhausted.
BookOnline Boolean No Whether this occurrence is offered for online booking at all.
StartDate DateTime No Start with the time component stripped - the date the class falls on.
StartTime String Yes Start pre-formatted as a short time in the account's culture. Match on Start.
EndTime String Yes End pre-formatted as a short time in the account's culture. Match on End.

ClassBooking #

One client's place in a scheduled class, and how it was paid for.

ClassBookingStateName exists on this class in C# but is never serialized - it is a computed getter with no [DataMember] on an opt-in type. Resolve ClassBookingState yourself: 0 Booked, 1 Cancelled, 2 CheckedIn, 3 LateCancelled, 4 NoShow.

Fields of the ClassBooking object
Field Type Null Description
ClassBookingId Guid No The booking's id.
ClassScheduleId Guid No The class occurrence booked.
ClientId Guid No The client holding the place.
ClientName String Yes That client's display name.
ClientMembership String Yes The membership the client is on, where they have one.
ClientEmail String Yes The client's email address.
ClientMobilePhone String Yes The client's mobile number.
ClientImage Byte[] Yes The client's photo as raw bytes, base64-encoded in the JSON.
AccountBalance Decimal No The client's account balance at the time the row was read.
OnlineBooking Boolean No Whether the place was booked online rather than in the back office.
ClassBookingState Byte No Booking state as a number - see the note above for the values.
AccountingDocumentId Guid Yes The invoice the place was billed on, where it has been billed.
DocumentNumber String Yes That invoice's number.
AccountingEntryId Guid Yes The receipt the place was paid on, where it has been paid.
EntryNumber String Yes That receipt's number.
Courses Int32 No How many course sessions the booking consumes.
EnrollmentId Guid Yes The enrollment the place came from, where the client is on a course or membership.
Notes String Yes Free-text notes on the booking.
ObjectState ObjectState No Internal change-tracking state, serialized as its enum value. Present on the wire but not meaningful to an integration - ignore it.

AccountingDocumentList #

One accounting document - an invoice, credit note, quotation or one of the purchasing documents. Returned by GET /api/v2/accountingdocuments.

A document says what was owed; the entries against it say what was paid. The payment side is summarised here in the four parallel AccountingEntry arrays, which are index-aligned with each other - entry n's id, payment type, number and total share position n.

Fields of the AccountingDocumentList object
Field Type Null Description
AccountingDocumentId Guid No The document's id.
DocumentDate DateTime No The document's own date - what it is reported against.
DocumentNumber String Yes The document number shown to the client or supplier.
ClientSupplierId Guid Yes The client or supplier the document is for, depending on its type.
ClientSupplierName String Yes That party's display name.
Reference String Yes Free-text reference on the document.
AccountingEntryIds Guid[] Yes Ids of the entries paid against this document. Index-aligned with the three arrays below.
AccountingEntryPaymentTypes String[] Yes Payment type of each entry, in the same order as AccountingEntryIds.
AccountingEntryNumbers String[] Yes Number of each entry, in the same order.
AccountingEntryTotals Decimal[] Yes Total of each entry, in the same order. Sum these to see how much of the document has been settled.
AccountingEntries String Yes The same entries pre-joined into a single display string.
LinkedAccountingDocumentId Guid Yes The document this one relates to - a credit note points at the invoice it credits.
LinkedAccountingDocumentNumber String Yes That document's number.
AccountingDocumentTypeId Byte No What kind of document this is, as a number. Match on this, not on the text field below.
Total Decimal No Document total.
UserName String Yes Back-office user who raised the document.
Category String Yes Category the document falls under.
Site String Yes Name of the site the document belongs to.
PostedDate DateTime Yes When the document was posted to an external accounting or PMS system. Null where it has not been posted.
FiscalStatus String Yes Fiscalisation status, on accounts where fiscalisation is in force.
AccountingDocumentType String Yes AccountingDocumentTypeId resolved to text in the account's language. Match on the id.

AccountingDocument #

The full document with its line items. Returned by GET /api/v2/accountingdocuments/{accountingDocumentId}, and the shape POST and PUT accept. Also the payload of the invoice, credit note, quotation and purchasing WebHook events.

The counterparty is split here into separate ClientId and SupplierId fields, where AccountingDocumentList collapses both into one ClientSupplierId. Which one is populated follows AccountingDocumentTypeId - a client invoice carries ClientId, a purchase order carries SupplierId. Note too that this shape carries no payment information at all: the AccountingEntry arrays exist only on the list shape.

Fields of the AccountingDocument object
Field Type Null Description
AccountingDocumentId Guid No The document's id. Send an empty Guid when creating.
SiteId Guid No The site the document belongs to. Required in the body - it decides which site the record is saved against.
ClientId Guid Yes The client, on a client-facing document. Null on a purchasing document.
SupplierId Guid Yes The supplier, on a purchasing document. Null on a client-facing one.
DestinationSiteId Guid Yes The receiving site on a stock transfer. Null on every other document type.
DocumentNumber String Yes The document number shown to the client or supplier. ChiDesk assigns this.
Reference String Yes Free-text reference.
Description String Yes Free-text description.
Notes String Yes Free-text notes.
LinkedAccountingDocumentId Guid Yes The document this one relates to - a credit note points at the invoice it credits.
LinkedAccountingDocumentTypeId Byte Yes That document's type, so you can tell what it is without fetching it.
AccountingDocumentTypeId Byte No What kind of document this is, as a number. Required when creating - it decides nearly everything else about how the document behaves.
DocumentDate DateTime No The document's own date - what it is reported against.
ExpiryDate DateTime Yes When the document lapses. Used by quotations.
DueDate DateTime Yes When payment falls due.
Total Decimal No Document total.
UserId Guid Yes Back-office user who raised it.
CashDrawerId Guid No Cash drawer the document was put through. Required.
Room String Yes Hotel room number, on accounts integrated with a PMS.
AccountingDocumentItems AccountingDocumentItem[] Yes The line items. Each carries AccountingDocumentItemId, Description, TaxId, Quantity, Cost, SellInclusive, SellExclusive, DiscountPercentage, the Total and Commission pairs, LoyaltyPoints, ItemType, the Service/Product/Voucher/Course/Membership id that was sold, SortOrder, SourceId, ClassBookingId, the client, package ids, activation and expiry dates, its Appointments, Employees and MedicalCodes with their joined-string equivalents, and a computed DiscountAmount.
CouponId Guid Yes The coupon applied to the document, where one was.
CouponReason String Yes Why the coupon was applied.
PostedDate DateTime Yes When the document was posted to an external accounting or PMS system. Null where it has not been posted.
PostedCode String Yes The external system's reference for the posting.
Recurrence Recurrence Yes Set on a recurring document, describing the schedule that generates it: RecurrenceId, Name, SendEmails, NotifyOnEnd, Interval, Day, IntervalAmount, DayOfWeek, Month, Start, End, Occurrences, DaysInAdvance, PreviousDate and NextDate.
RecurrenceCreationId Guid Yes Set on a document that was generated by a recurrence, pointing back at the run that created it.

AccountingEntryList #

One movement of money - a receipt or a payment. Returned by GET /api/v2/accountingentries.

An entry is not tied to exactly one document. AccountingDocumentId is nullable, so an entry can exist without one; reconcile from the document side using its AccountingEntry arrays rather than assuming a one-to-one match.

Fields of the AccountingEntryList object
Field Type Null Description
AccountingEntryId Guid No The entry's id.
EntryDate DateTime No Date of the movement.
EntryNumber String Yes The entry number.
ClientSupplierId Guid No The client or supplier the money moved to or from.
ClientSupplierName String Yes That party's display name.
CashDrawerId Guid No Cash drawer the entry was put through.
CashDrawerName String Yes That drawer's name.
LinkedAccountingDocumentNumber String Yes Number of the document this entry settles.
AccountingDocumentId Guid Yes The document this entry settles. Null where the entry stands on its own.
Reference String Yes Free-text reference on the entry.
AccountingEntryTypeId Byte No Entry type as a number: 1 Receipt, 2 Payment, 10 RecurringReceipt, 11 RecurringPayment. This is the same set the entryType query parameter accepts.
PaymentTypeId Guid No The payment type used.
PaymentType String Yes That payment type's display name.
PaymentMethod Byte No Underlying method behind the payment type, as a number.
VoucherNumber String Yes Number of the voucher redeemed, where the payment was by voucher.
Received Decimal No Amount handed over.
Gratuity Decimal No Portion of the amount that is a gratuity.
Change Decimal No Change given back.
Total Decimal No Net value of the entry - Received less Change.
UserName String Yes Back-office user who processed the entry.
PostedDate DateTime Yes When the entry was posted to an external accounting or PMS system. Null where it has not been posted.

AccountingEntry #

The full record of one movement of money. Returned by GET /api/v2/accountingentries/{accountingEntryId}, and the shape POST and PUT accept. Also the payload of the receipt and payment WebHook events.

As with documents, the counterparty is split into ClientId and SupplierId here where the list shape collapses them into ClientSupplierId. The money fields are not interchangeable: Received is what was handed over, Change is what was given back, and Total is the net of the two - reconcile on Total, not Received.

Fields of the AccountingEntry object
Field Type Null Description
AccountingEntryId Guid No The entry's id. Send an empty Guid when creating.
SiteId Guid No The site the entry belongs to. Required in the body.
ClientId Guid Yes The client, on a receipt. Null on a supplier payment.
SupplierId Guid Yes The supplier, on a payment. Null on a client receipt.
EntryNumber String Yes The entry number. ChiDesk assigns this.
Reference String Yes Free-text reference.
Description String Yes Free-text description.
EntryDate DateTime No Date of the movement.
AccountingEntryTypeId Byte No Entry type as a number: 1 Receipt, 2 Payment, 10 RecurringReceipt, 11 RecurringPayment. Required when creating.
AccountingDocumentId Guid Yes The document this entry settles. Null where the entry stands on its own - an entry is not tied to exactly one document.
TaxId Guid No The tax rate applied to the entry.
AmountDue Decimal No What was outstanding at the time - context for the payment, not part of it.
Received Decimal No Amount handed over.
TaxAmount Decimal No Tax portion of the entry.
Gratuity Decimal No Portion of the amount that is a gratuity.
Change Decimal No Change given back.
Total Decimal No Net value of the entry - Received less Change. This is the figure to reconcile on.
PaymentTypeId Guid No The payment type used. Required.
VoucherId Guid Yes The voucher redeemed, where the payment was by voucher.
UserId Guid Yes Back-office user who processed the entry.
CashDrawerId Guid No Cash drawer the entry was put through.
SelectedGratuityEmployees Guid[] Yes Employees the gratuity is being split between, as sent by the back office when capturing the entry. An input convenience - read Gratuities for what was actually recorded.
Gratuities AccountingEntryGratuity[] Yes The gratuity split as recorded. Each entry carries AccountingEntryId, EntryDate, EntryNumber, the employee and their name, DateSettled, the user, payment type and cash drawer with their names, and the Amount.
PostedDate DateTime Yes When the entry was posted to an external accounting or PMS system. Null where it has not been posted.
PostedCode String Yes The external system's reference for the posting.
Recurrence Recurrence Yes Set on a recurring entry, describing the schedule that generates it. Same shape as on AccountingDocument.
RecurrenceCreationId Guid Yes Set on an entry generated by a recurrence, pointing back at the run that created it.
ChargedDate DateTime Yes When the payment was actually taken, for entries collected by debit order or stored card.

VoucherList #

A gift voucher and what is left on it. Returned by GET /api/v2/vouchers.

Fields of the VoucherList object
Field Type Null Description
VoucherId Guid No The voucher's id.
Category String Yes Voucher category name.
VoucherNumber String Yes The number printed on the voucher and quoted by the client.
CreationDate DateTime No When the voucher was created, UTC.
ExpiryDate DateTime No When the voucher expires, UTC. Match on this, not on ExpiryDateString.
ActivationDate DateTime Yes When the voucher was activated. Null while it is sold but not yet active.
ExpiryDateString String Yes ExpiryDate pre-formatted as a short date in the account's culture, for display.
ClientId Guid Yes The client who bought it. Null where it was sold without a client record.
Client String Yes Display name of the buying client.
Description String Yes Free-text description.
Recipient String Yes Who the voucher is for, where that differs from the buyer.
BarCode String Yes Barcode, where one is set.
Amount Decimal No Face value of the voucher.
UsedAmount Decimal No How much has been redeemed so far.
Remaining Decimal No Balance still available. This is the figure to check before accepting the voucher.
InvoiceNumber String Yes Number of the invoice the voucher was sold on.
InvoiceSoldOn DateTime Yes Date of that invoice.

Voucher #

The full voucher record. Returned by GET /api/v2/vouchers/{voucherId}, and the shape POST and PUT /api/v2/vouchers accept.

Remaining is computed as Amount less UsedAmount, so it is derived rather than stored - send Amount and UsedAmount when writing, not Remaining. This shape carries the recipient and delivery detail that VoucherList omits, but not the invoice the voucher was sold on, which only VoucherList has.

Fields of the Voucher object
Field Type Null Description
VoucherId Guid No The voucher's id. Send an empty Guid when creating.
SiteId Guid Yes The site the voucher belongs to. Required in the body - omitting it causes a 500 on update.
State Byte No Item state as a number - 1 Active, 2 Inactive.
VoucherType Byte No What kind of voucher this is, as a number. Required.
VoucherNumber String Yes The number printed on the voucher and quoted by the client. Must be unique on the account.
CreationDate DateTime No When the voucher was created, UTC.
ExpiryDate DateTime No When it expires, UTC.
DeliveryDate DateTime Yes When the voucher is to be delivered to its recipient. Null for immediate delivery.
ActivationDate DateTime Yes When it was activated. Null while it is sold but not yet active - an inactive voucher still has a Remaining balance, so check this before accepting it.
ClientId Guid Yes The client who bought it. Null where it was sold without a client record.
SourceId Guid Yes How the sale originated.
CreatedSiteId Guid Yes The site that actually issued the voucher, which can differ from SiteId on a multi-site account.
Description String Yes Free-text description.
Recipient String Yes Who the voucher is for, where that differs from the buyer.
RecipientEmail String Yes Where the voucher is emailed.
RecipientMessage String Yes The personal message printed on it.
Template Int32 No Which voucher template to render it with.
Notes String Yes Internal notes, not shown to the recipient.
BarCode String Yes Barcode, where one is set.
Category String Yes Voucher category name.
From String Yes Who the voucher is from, as printed.
SecurityCode String Yes The code a client must quote alongside the number to redeem it. Treat it as a secret - do not print it anywhere the number alone appears.
Amount Decimal No Face value.
UsedAmount Decimal No How much has been redeemed so far.
Remaining Decimal No Balance still available, computed as Amount less UsedAmount. Read-only in effect - see the note above.

Sales #

One sold line item, with its money broken out. Returned by GET /api/v2/reports/sales - one row per item sold, not one per document.

This is a reporting shape, so it denormalises heavily: the site, client, user, document and item all appear on every row. Group by AccountingDocumentId to get back to documents.

Fields of the Sales object
Field Type Null Description
AccountingDocumentItemId Guid No The line item's id - the only field unique per row.
SiteId Guid No Site the sale belongs to.
Site String Yes That site's name.
ClientId Guid Yes Client the sale was to. Null for a cash sale with no client record.
Client String Yes That client's display name.
ClientCode String Yes The account's own code for the client.
Gender Byte Yes The client's gender code, where recorded.
UserId Guid Yes Back-office user who processed the sale.
User String Yes That user's name.
CashDrawerId Guid No Cash drawer the sale was put through.
CreationDate DateTime No When the row was created, UTC.
DocumentDate DateTime No The document's own date - what the sale is reported against. This is the one to bucket by, not CreationDate.
AccountingDocumentId Guid No The document this line belongs to.
DocumentNumber String Yes That document's number.
Code String Yes The sold item's code.
BarCode String Yes The sold item's barcode.
Description String Yes The sold item's description as it appears on the document.
Reference String Yes Free-text reference on the line.
Quantity Decimal No How many were sold. Can be negative on a credit note.
TaxId Guid Yes The tax rate applied.
Cost Decimal No Cost of the item, used for the profit figure.
Discount Decimal No Discount given on the line.
Loyalty Decimal No Loyalty value applied to the line.
Tax Decimal No Tax charged on the line.
Profit Decimal Yes TotalExclusive less Cost. Null where cost is not known.
TotalInclusive Decimal No Line total including tax.
TotalExclusive Decimal No Line total excluding tax.
CommissionInclusive Decimal No Commission on the line including tax.
CommissionExclusive Decimal No Commission on the line excluding tax.
VoucherRedemptions Decimal No Value redeemed against a voucher on this line.
ItemTypeId Byte No What kind of thing was sold, as a number. ItemType carries the same value as text.
ItemType String Yes The item type resolved to text in the account's language. Match on ItemTypeId.
SupplierId Guid Yes Supplier of the item, for products.
Supplier String Yes That supplier's name.
Brand String Yes Brand of the item, for products.
Category String Yes Catalogue category of the item.
Source String Yes How the sale originated - resolve against the Sources reference.
Room String Yes Room the service was delivered in.
PostedDate DateTime Yes When the line was posted to an external accounting or PMS system. Null where it has not been posted.
Employees String[] Yes Employees credited with the line, one entry each.
ServiceId Guid Yes Set where the line sold a service.
ProductId Guid Yes Set where the line sold a product.
PackageId Guid Yes Set where the line sold a package.
CourseId Guid Yes Set where the line sold a course.
Duration Int32 No Treatment time in minutes, for service lines.
Online Boolean No Whether the sale originated online.
EmployeeNames String Yes The same employees pre-joined into a single display string.
RecurrenceId Guid Yes Set where the line came from a recurring transaction.

Alert #

One alert definition configured on the account - a flag that can be raised against a client or an appointment. Returned by GET /api/v2/alerts.

Selected is always false on an API response. It is the back-office editor's checkbox state, not an indication that the alert is set on anything - reading it as "this alert applies" is wrong for every row.

Fields of the Alert object
Field Type Null Description
AlertId Guid No The alert definition's id.
Name String Yes Display name.
Category String Yes Category the alert is grouped under. The list comes back ordered by this, then by name.
SiteId Guid Yes The site the alert belongs to. Null on an account-wide alert, which is returned for every siteId you ask for.
Selected Boolean No Always false here. See the note above.

AppointmentState #

One appointment state the account has configured - Booked, Arrived, Completed and whatever else the account has added. Returned by GET /api/v2/appointmentstates.

States are per-account data, not a fixed enum. Do not hard-code the names or assume a particular set exists: read this list and resolve the AppointmentStateId you get on an appointment against it. The same id is what POST /api/v2/appointments/setstate takes.

Fields of the AppointmentState object
Field Type Null Description
AppointmentStateId Guid No The state's id. This is the value carried on AppointmentList.AppointmentStateId and accepted by setstate.
Name String Yes Display name, in the account's language.
Active Boolean No Whether the state is still in use. Inactive states are kept because historical appointments still point at them.
SortOrder Int32 No Position in the back-office list. The endpoint returns rows already ordered by this.
Colour String Yes The state's diary colour as a hex string. Same value as AppointmentList.AppointmentStateColour.
SiteId Guid Yes The site the state belongs to. Null on an account-wide state.

SourceList #

One referral source - how a client or a sale came to the business. Returned by GET /api/v2/sources.

This is what Sales.Source resolves against, but note the join is by name, not by id: the sales report carries the source's name as a string rather than its SourceId.

Fields of the SourceList object
Field Type Null Description
SourceId Guid Yes The source's id.
SiteId Guid Yes The site the source belongs to. Null on an account-wide source.
State Byte No Whether the source is active, as a number - the same item-state set the itemState query parameter uses elsewhere.
Code String Yes The account's own short code for the source, at most 5 characters.
Name String Yes Display name. The endpoint returns rows ordered by this.
CommissionPercentage Decimal No Commission percentage attributed to the source, where the account pays referral commission.

partner_list #

The account as ClassPass sees it. Returned by GET /api/v2/classpass.

One ChiDesk account is exactly one ClassPass partner, so partners always holds a single entry and the pagination block is hard-coded to page 1 of 1 - the page argument is accepted and ignored. Do not write a paging loop against this endpoint. Note also that the partner's last_updated is generated as "now minus one day" on every call rather than being a real modification time, and partner_description is never populated.

Fields of the partner_list object
Field Type Null Description
partners partner[] Yes Always exactly one entry. Each partner carries partner_id (the ChiDesk account name), partner_name (the account's BusinessName setting), partner_description (never populated) and last_updated.
pagination pagination Yes Always page 1, total_pages 1. Carries page, page_size and total_pages; page_size echoes what you sent.

partner_item #

One partner, wrapped. Returned by GET /api/v2/classpass/{partner_id}.

A wrapper object, not the partner itself - the partner is one level down, under the partner key. This is ClassPass's convention for single-item fetches and it applies to venue_item too.

Fields of the partner_item object
Field Type Null Description
partner partner Yes The partner: partner_id, partner_name, partner_description and last_updated.

venue_list #

The account's sites as ClassPass venues. Returned by GET /api/v2/classpass/{partner_id}/venues.

The pagination block here always reads page 1 of 1 regardless of how many venues come back, so it cannot be used to page. Read the whole venues array in one call.

Fields of the venue_list object
Field Type Null Description
venues venue[] Yes One entry per site. Each venue carries partner_id, venue_id, venue_name, venue_description, phone, email, website, a nested address and last_updated.
pagination pagination Yes Always page 1, total_pages 1. See the note above.

venue_item #

One venue, wrapped. Returned by GET /api/v2/classpass/{partner_id}/venues/{venue_id}.

Fields of the venue_item object
Field Type Null Description
venue venue Yes The venue: partner_id, venue_id, venue_name, venue_description, phone, email, website, address and last_updated. The nested address carries address_line1, address_line2, city, state, zip and country - a different shape from ChiDesk's own Address object.

service_list #

The services bookable at a venue. Returned by GET /api/v2/classpass/{partner_id}/venues/{venue_id}/services.

Unlike the partner and venue lists, this endpoint pages for real - total_pages is computed from the result count and the array is sliced to the page you asked for. Loop until page reaches total_pages.

Fields of the service_list object
Field Type Null Description
services service[] Yes The page of services. Each carries service_id, name, duration, start_interval (how far apart bookable start times are, in minutes), description, late_cancel_window and last_updated. Note duration and late_cancel_window are strings, not numbers.
pagination pagination Yes A real pagination block: page, page_size and a total_pages computed from the full result count.

bookable_list #

The practitioners who can be booked at a venue. Returned by GET /api/v2/classpass/{partner_id}/venues/{venue_id}/bookables.

Bookables are employees only. Rooms and equipment are not offered through this integration, so a service that needs a resource still resolves to the staff member here. This endpoint pages for real.

Fields of the bookable_list object
Field Type Null Description
bookables bookable[] Yes The page of practitioners. Each carries type, bookable_id, first_name, last_name, gender, display_name, biography, last_updated and a services array of the service_ids they can deliver.
pagination pagination Yes A real pagination block: page, page_size and a computed total_pages.

availability_list #

When each practitioner is free, over a requested date range. Returned by GET /api/v2/classpass/{partner_id}/venues/{venue_id}/availability.

A practitioner with no free time in the range is omitted from the array entirely rather than returned with an empty availability_windows list - absence means no availability, not an error.

Fields of the availability_list object
Field Type Null Description
availabilities availability[] Yes One entry per practitioner who has free time. Each carries type, bookable_id, last_updated and an availability_windows array, where every window is a start_datetime and end_datetime pair.
pagination pagination Yes Carries page, page_size and total_pages.

appointment #

A booking coming in from ClassPass. The body POST /api/v2/classpass/{partner_id}/venues/{venue_id}/appointments accepts.

This is ClassPass's appointment shape and has nothing in common with ChiDesk's own Appointment object - different casing, different fields, and the id is a string rather than a Guid because ClassPass assigns it. The response echoes only { "appointment_id": "..." }, not the object you sent.

Fields of the appointment object
Field Type Null Description
appointment_id String Yes ClassPass's id for the booking. You supply this - ChiDesk stores it against the appointment it creates and echoes it back.
bookables bookable[] Yes Which practitioner the booking is with. Only bookable_id is read; the rest of the bookable shape may be sent but is ignored.
service_id String Yes The service being booked, as returned by the services endpoint.
start_datetime String Yes When the booking starts, as a string rather than a parsed date - the same format the availability windows use.
customization String Yes Free-text customisation passed through from ClassPass.
user user Yes The client. Carries user_id, user_email, user_username, first_name, last_name, gender, phone, birthday, a nested address and an emergency_contact of name and phone. ChiDesk matches or creates a client record from this.

appointment_status #

A status change on an existing ClassPass booking. The body PATCH /api/v2/classpass/{partner_id}/venues/{venue_id}/appointments/{appointment_id} accepts.

Only CANCELLED and LATE CANCELLED do anything. Any other status returns 200 with the appointment unchanged, so a successful response is not evidence that the status was applied - check the appointment afterwards if it matters.

Fields of the appointment_status object
Field Type Null Description
status String Yes The new status. Only CANCELLED and LATE CANCELLED have any effect.
customization String Yes Free-text customisation passed through from ClassPass.

attendance_list #

Whether the client actually turned up. Returned by GET /api/v2/classpass/{partner_id}/venues/{venue_id}/appointments/{appointment_id}/attendance.

The array holds exactly one record, for the appointment you asked about. Its status is either ATTENDED or CANCELLED - there is no third value and no "unknown", so a booking that was never marked off reads as one of the two.

Fields of the attendance_list object
Field Type Null Description
attendance attendance[] Yes Exactly one record, carrying cp_user_id, appointment_id, status and last_updated.

Address #

A postal address, carried inside larger objects rather than returned on its own.

Cities, States and Countries are picker lists for the back-office form, not data about this address. They are serialized because the type is shared with the admin UI, and they are normally null on an API response - do not read them as the address's values.

Fields of the Address object
Field Type Null Description
AddressId Guid No The address's id.
Street String Yes Street address.
City String Yes City.
State String Yes Province or state - the address field, not a status flag.
Code String Yes Postal code.
Country String Yes Country. Required when writing an address.
Cities String[] Yes Picker list, not this address's value. See the note above.
States String[] Yes Picker list, not this address's value.
Countries String[] Yes Picker list, not this address's value.