SDK Reference

Scheduling

Classes follow a three-level hierarchy: Class (a reusable template — capacity, pricing, waitlist config, but no timing) → ClassSchedule (a recurrence rule attached to a template — days of week, start/end time, date range) → ClassInstance (an actual occurrence on the calendar, normally auto-generated by the backend when a schedule is created or regenerated, not hand-created). A Booking is a single spot in a specific ClassInstance. Outside that hierarchy, Service is a bookable 1-on-1 offering (what an Appointment is booked against), and TimeSlot is a legacy, largely-superseded booking primitive.

Class

company/scheduling/class/class.ts. The template — holds capacity/pricing/waitlist config only; no timing fields (timing lives on ClassSchedule).

Gotcha:ClassStatus ("active" | "paused" | "ended") tracks the template's own lifecycle — this is a different type of the same name from ClassInstance's ClassStatus ("scheduled" | "active" | "completed" | "canceled"), which tracks a single occurrence instead. Don't confuse the two despite the identical export name.

Fields

FieldTypeLazyNotes
idstring✓
companyIDstring?✓
namestring✓
descriptionstring?✓
locationIDstring?✓
roomIDstring?✓
serviceIDstring?✓optional service this class is built on
staffIDstring?✓
colorstring?
capacitynumber
pricenumber?
waitlistEnabledboolean?
waitlistCapacitynumber?
statusClassStatus?"active" | "paused" | "ended"
createdAt / updatedAtDate

CreateClassData (payload for create): name, description?, capacity, locationID, roomID, serviceID?, staffID?— same "no timing" rule; a schedule is created separately against the resulting class id.

Construction

ts
new Class(id: string, company: Company);                                    // fetches lazily
new Class(data: LazyClassData | ClassData, company: Company, lazy?: boolean); // from known data

icon → GraduationCap (lucide-react). viewHref → /company/{companyID}/classes/{id}. Related-entity getters location, room, service lazily resolve and cache their instances (private-field caching).

Methods

Endpoint: /company/{companyID}/classes.

ts
protected fetch(lazy?: boolean): Promise<SimpleResult<ClassData>>;   // GET .../classes?ids={id}&limit=1&lazy=...
save(): Promise<SimpleResult<EventData>>;                            // PATCH .../classes, body [this._data]
static create(company: Company, items: CreateClassData[]): Promise<EventData<string[]>>; // POST .../classes
delete(): Promise<SimpleResult<EventData>>;                          // DELETE .../classes?ids={id} — soft delete
static query(company: Company, data: QueryRequest): Promise<SimpleResult<Class[]>>; // POST .../classes/query

Parent → children convenience wrappers:

ts
GetSchedulings({ limit = 100, offset = 0, lazy = true }): Promise<SimpleResult<ClassSchedule[]>>;
// ClassSchedule.query filtered by classID

GetListOfBookings({ limit = 100, offset = 0, lazy = true }): Promise<SimpleResult<Booking[]>>;
// Booking.query filtered by classID — every booking across every instance of this template

GetListOfTagAssignments({ limit = 100, offset = 0, lazy = true }): Promise<SimpleResult<TagAssignment[]>>;
// TagAssignment.query filtered by resourceType: "class", resourceID: this.id

AssignTag(tag: Tag): Promise<...>;
// TagAssignment.create({ tagID, resourceID: this.id, resourceType: "class" })

ClassSchedule

company/scheduling/class/schedule.ts. The recurrence rule built on top of a Class template. Per the code comment on CreateClassScheduleItem: creating a schedule generates ClassInstance rows for the next 8 weeks (or until scheduleEndDate, if sooner).

WeekDay = 0 | 1 | 2 | 3 | 4 | 5 | 6 — 0 = Sunday … 6 = Saturday.

Fields

FieldTypeLazyNotes
idstring✓
classIDstring✓part of the create payload directly, not a separate constructor/method param
companyIDstring✓
recurrenceDaysWeekDay[]✓0=Sun…6=Sat
startTimestring✓"HH:MM:SS"
endTimestring✓"HH:MM:SS"
scheduleStartDatestring✓
scheduleEndDatestring | null✓
createdAt / updatedAtDate

CreateClassScheduleItem (payload for create): classID, recurrenceDays, startTime, endTime, scheduleStartDate, scheduleEndDate?.

UpdateClassScheduleItem (payload merged into save()'s PATCH body): recurrenceDays?, startTime?, endTime?, scheduleStartDate?, scheduleEndDate?: string | null, and regenerateFuture?: boolean— per the code comment: "If true, cancels future scheduled instances and regenerates from updated rules."

Construction

ts
new ClassSchedule(id: string, company: Company);
new ClassSchedule(data: LazyClassScheduleData | ClassScheduleData, company: Company, lazy?: boolean);

icon → GraduationCap (same icon as Class, ScheduleIcon alias). viewHref → /company/{companyID}/classSchedules/{id}. class getter lazily resolves/caches the parent Class.

Methods

Endpoint: /company/{companyID}/classes/schedules.

ts
protected fetch(lazy?: boolean): Promise<SimpleResult<ClassScheduleData>>; // GET .../schedules?ids={id}&limit=1&lazy=...

// Pass opts.regenerateFuture: true to cancel + regenerate future scheduled instances.
save(opts?: UpdateClassScheduleItem): Promise<SimpleResult<EventData>>;
// PATCH .../schedules?ids={id}, body = { ...currentData, ...opts }

static create(company: Company, items: CreateClassScheduleItem[]): Promise<EventData<string[]>>;
// POST .../schedules — each item generates its own ClassInstance rows

// Soft-deletes the schedule AND cancels all future `scheduled` instances it generated.
delete(): Promise<SimpleResult<EventData>>; // DELETE .../schedules?ids={id}

static query(company: Company, data: QueryRequest): Promise<SimpleResult<ClassSchedule[]>>; // POST .../schedules/query

Helpers:

ts
ListOfDays(): string[];  // recurrenceDays mapped to short labels, e.g. ["Sun", "Wed"]
StartTime(): Date;       // startTime ("HH:MM:SS") parsed onto today's date
EndTime(): Date;         // endTime parsed the same way

ClassInstance

company/scheduling/class/instance.ts. A single scheduled occurrence, normally auto-generated when a ClassSchedule is created or regenerated — not created directly in practice. ClassStatus here ("scheduled" | "active" | "completed" | "canceled") is its own type, distinct from Class's same-named status type (see Class above).

Note:Has no standalone view route— surfaced via the app's Calendar page, which renders instances queried per class template for a date range, rather than a dedicated detail page.

Fields

FieldTypeLazyNotes
idstring✓
companyIDstring?✓
classIDstring✓
scheduleIDstring?✓optional — an instance could in principle exist without a generating schedule
locationIDstring?✓
roomIDstring?✓
staffIDstring?✓
startsAtstring✓
endsAtstring✓
statusClassStatus✓"scheduled" | "active" | "completed" | "canceled"
canceledAtstring | null
cancelReasonstring | null
createdAt / updatedAtDate

The create payload interface (CreateClassInstanceData — classID, scheduleID?, locationID?, roomID?, staffID?, startsAt, endsAt, status?) is not module-exported. Per its doc comment: "instances are normally auto-generated by ClassSchedule, not hand-created; use the schedule-create flow."

Construction

ts
new ClassInstance(id: string, company: Company);
new ClassInstance(data: LazyClassInstanceData | ClassInstanceData, company: Company, lazy?: boolean);

No icon/viewHref overrides are defined on this class. location, room, class getters lazily resolve and cache their related instances.

Methods

Endpoint: /company/{companyID}/classes/instances.

ts
protected fetch(lazy?: boolean): Promise<SimpleResult<ClassInstanceData>>; // GET .../instances?ids={id}&limit=1&lazy=...
save(): Promise<SimpleResult<EventData>>;   // PATCH .../instances, body [this._data] — used for edits/cancellations
static create(company: Company, items: CreateClassInstanceData[]): Promise<SimpleResult<EventData>>;
// POST .../instances — prefer going through ClassSchedule so recurrence stays in sync
delete(): Promise<SimpleResult<EventData>>; // DELETE .../instances?ids={id} — soft delete
static query(company: Company, data: QueryRequest): Promise<SimpleResult<ClassInstance[]>>; // POST .../instances/query

Service

company/scheduling/service.ts. A bookable service — what an Appointment is booked against, and optionally what a Class is layered on top of via serviceID.

Fields

FieldTypeLazyNotes
idstring✓
companyIDstring?✓
namestring✓
descriptionstring?✓
durationMinutesnumber✓
pricenumber | null?✓nullable/optional for a free or not-yet-priced service
visibleOnlinebooleangates whether it's surfaced in Company.GetPublicServices
categorystring?
colorstring?
createdAt / updatedAtDate

CreateServiceData (payload for create): name, description, durationMinutes, price?.

Construction

ts
new Service(id: string, company: Company);
new Service(data: LazyServiceData | ServiceData, company: Company, lazy?: boolean);

No icon/viewHref overrides in this file.

Methods

Endpoint: /company/{companyID}/services.

ts
protected fetch(lazy?: boolean): Promise<SimpleResult<ServiceData>>; // GET .../services?ids={id}&limit=1&lazy=...
save(): Promise<SimpleResult<EventData>>;                            // PATCH .../services
static create(company: Company, items: CreateServiceData[]): Promise<EventData<string[]>>; // POST .../services
delete(): Promise<SimpleResult<EventData>>;                          // DELETE .../services?ids={id}
static query(company: Company, data: QueryRequest): Promise<SimpleResult<Service[]>>;       // POST .../services/query

Parent → children convenience wrappers:

ts
GetListOfTagAssignments({ limit = 100, offset = 0, lazy = true }): Promise<SimpleResult<TagAssignment[]>>;
// TagAssignment.query filtered by resourceType: "service", resourceID: this.id

AssignTag(tag: Tag): Promise<...>;

GetListOfAppointments({ limit = 100, offset = 0, lazy = true }): Promise<SimpleResult<Appointment[]>>;
// Appointment.query filtered by serviceID
Gotcha:Group pricing is not a Service method. Discounted class/service pricing for a Group lives in the standalone GroupPricing class.

Appointment

company/scheduling/appointment.ts. A 1-on-1 scheduled appointment for a Service, distinct from the Class/ClassSchedule/ClassInstance hierarchy.

AppointmentStatus = "pending" | "unpaid" | "confirmed" | "canceled" | "completed" | "no_show".

Note:Per the code comment on the type: "unpaid" mirrors Booking's status of the same name — for a priced service, Appointment.create first tries to consume an eligible service-scoped or fully generic Credit; if none is available, the appointment is created with status "unpaid" instead of requiring payment up front. See Members for Credit realm-scoping rules.

Fields

FieldTypeLazyNotes
idstring✓
companyIDstring✓
memberIDstring | null✓nullable — a walk-in appointment can exist with no linked user yet
serviceIDstring | null✓
locationIDstring | null✓
roomIDstring | null✓
staffIDstring | null✓
startsAtstring✓
endsAtstring✓
statusAppointmentStatus✓
notesstring | null
checkedInAtstring | null
confirmedAtstring | null
canceledAtstring | null
cancelReasonstring | null
createdAt / updatedAtDate

CreateAppointmentItem (payload for create): startsAt, endsAt, memberID?, serviceID?, locationID?, roomID?, staffID?, notes?, status?. memberID is optional specifically to allow a walk-in appointment with no linked user yet.

Construction

ts
new Appointment(id: string, company: Company);
new Appointment(data: LazyAppointmentData | AppointmentData, company: Company, lazy?: boolean);

No icon/viewHref overrides in this file.

Methods

Gotcha:Endpoint: /company/{companyID}/appointments. Uses a singular id= query param (not ids=) and a non-array PATCH body, unlike most other scheduling classes.
ts
protected fetch(): Promise<SimpleResult<AppointmentData>>; // GET .../appointments?id={id}
save(): Promise<SimpleResult<EventData>>;                  // PATCH .../appointments?id={id}, body = data (single object, not an array)

// For a priced service, the backend first tries to consume an eligible
// service-scoped or fully generic Credit; with none available, the
// appointment is created as "unpaid" instead of requiring payment up front.
static create(company: Company, items: CreateAppointmentItem[]): Promise<EventData<string[]>>; // POST .../appointments

delete(): Promise<SimpleResult<EventData>>; // DELETE .../appointments?id={id}
static query(company: Company, data: QueryRequest): Promise<SimpleResult<Appointment[]>>; // POST .../appointments/query
ts
/** Sets status = "completed" and checkedInAt = NOW(). Returns 202 if the user has an alert. */
CheckIn(): Promise<SimpleResult<CheckInResponse & { appointmentID?: string }>>;
// POST .../appointments/{id}/check-in

Per the check-in convention: status === 200 is a clean check-in; status === 202 means it succeeded but result.data.alert(singular, per this method's extended return type) should be surfaced to the operator — never treated as failure.

Booking

company/scheduling/booking.ts. A single spot in a ClassInstance — the third level of the Class → ClassSchedule → ClassInstance hierarchy.

Note:Per its class doc comment, Booking is "not scoped to a classID/instanceID pair, unlike a DataStructuresubclass tied to a parent" — instances are constructed by plain id, the same as every other DataStructure subclass, despite conceptually belonging to one class/instance.

BookingStatus = "booked" | "unpaid" | "waitlisted" | "canceled" | "attended" | "no_show". Per the code comment: "unpaid" mirrors Appointment's status of the same name — set when no eligible class-realm/generic Credit was available to cover the booking at creation time.

Note:Only class-realm or fully generic credits can later be applied via credit.ApplyToBooking() — service- and door/location-scoped credits are excluded (see Credit / CreditUse).

Fields

FieldTypeLazyNotes
idstring✓
classIDstring✓
instanceIDstring✓
companyIDstring✓
memberIDstring | null✓
statusBookingStatus✓
checkoutLineIDstring | nullset only when the booking was paid for via checkout rather than a credit
checkedInAtstring | null
canceledAtstring | null
cancelReasonstring | null
createdAt / updatedAtDate

CreateBookingData (payload for create): instanceID, memberID?, status?: "booked" | "waitlisted". memberID is optional (e.g. a waitlist hold created before a specific user is assigned).

Construction

ts
new Booking(id: string, company: Company);
new Booking(data: LazyBookingData | BookingData, company: Company, lazy?: boolean);

viewHref → /company/{companyID}/bookings/{id}. No icon override is defined. class and instance getters lazily resolve and cache the related instances (private-field caching).

Methods

Endpoint: /company/{companyID}/classes/bookings.

ts
protected fetch(): Promise<SimpleResult<BookingData>>; // GET .../bookings?ids={id}
save(): Promise<SimpleResult<EventData>>;               // PATCH .../bookings, body [data]
static create(company: Company, items: CreateBookingData[]): Promise<SimpleResult<EventData>>; // POST .../bookings
delete(): Promise<SimpleResult<EventData>>;              // DELETE .../bookings?ids={id}
static query(company: Company, data: QueryRequest): Promise<SimpleResult<Booking[]>>; // POST .../bookings/query
ts
/** Sets status = "attended" and checkedInAt = NOW(). Returns 202 if the user has an alert. */
CheckIn(): Promise<SimpleResult<CheckInResponse>>;
// POST .../bookings/checkIn, body { bookingIDs: [this.id] }
Gotcha:The endpoint itself accepts a bookingIDs array, but Booking.CheckIn() always sends a single-element array for this.id — there is currently no bulk CheckInBookings helper exported from this file, despite the endpoint shape suggesting one could exist; check in bookings one at a time until a bulk helper is added. status === 202 carries result.data.alerts (plural array) per the shared CheckInResponse shape.

TimeSlot

Legacy:Per its class doc comment: "A bookable time window at a location/service, predating the current Class/ClassSchedule/ClassInstance and Appointment models. Not referenced anywhere else in the app — appears to be superseded by those, but kept here rather than removed since it's still a live, fully-CRUD backend resource." Treat it as legacy — new scheduling work should use Class/ClassSchedule/ClassInstance or Appointment instead.

Fields

FieldTypeLazyNotes
idstring✓
startTimestring✓
endTimestring✓
capacitynumber?✓
companyIDstring
serviceIDstring?
instructorIDstring?predates the staffID naming used elsewhere — kept as-is since it's the literal wire field name
locationIDstring?
notesstring?
createdAt / updatedAtDate

CreateTimeSlotItem (payload for create): startTime, endTime, capacity?, serviceID?, instructorID?, locationID?, notes?.

Construction

ts
new TimeSlot(id: string, company: Company);
new TimeSlot(data: LazyTimeSlotData | TimeSlotData, company: Company, lazy?: boolean);

No icon/viewHref overrides.

Methods

Endpoint: /company/{companyID}/timeSlots. Internally routed through a handful of module-private helper functions (getTimeSlotData, updateTimeSlots, deleteTimeSlots, queryTimeSlotData) rather than calling APIRequest directly inline, unlike most other classes in this doc:

ts
protected fetch(lazy?: boolean): Promise<SimpleResult<TimeSlotData>>;
save(): Promise<SimpleResult<EventData>>;                 // PATCH .../timeSlots
static create(company: Company, items: CreateTimeSlotItem[]): Promise<SimpleResult<EventData>>; // POST .../timeSlots
delete(): Promise<SimpleResult<EventData>>;                // via deleteTimeSlots — DELETE .../timeSlots, body { ids: [this.id] }
static deleteMany(company: Company, ids: string[]): Promise<SimpleResult<EventData>>; // batch delete, same endpoint
static query(company: Company, data: QueryRequest): Promise<SimpleResult<TimeSlot[]>>; // POST .../timeSlots/query

Also exports a standalone module-level function:

ts
GetTimeSlot({ company, ids?, limit?, offset?, lazy? }: GetTimeSlotRequest): Promise<SimpleResult<TimeSlot[]>>;
Gotcha:Per its doc comment: "Standalone list/fetch helper predating the TimeSlot.query-based pattern used elsewhere in the SDK — prefer TimeSlot.query()for new code; kept for compatibility with any existing callers of this function." This is the one place in the scheduling module where a non-class standalone function is still the documented, exported entry point rather than an internal implementation detail.