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.
company/scheduling/class/class.ts. The template — holds capacity/pricing/waitlist config only; no timing fields (timing lives on ClassSchedule).
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.| Field | Type | Lazy | Notes |
|---|---|---|---|
id | string | ✓ | |
companyID | string? | ✓ | |
name | string | ✓ | |
description | string? | ✓ | |
locationID | string? | ✓ | |
roomID | string? | ✓ | |
serviceID | string? | ✓ | optional service this class is built on |
staffID | string? | ✓ | |
color | string? | ||
capacity | number | ||
price | number? | ||
waitlistEnabled | boolean? | ||
waitlistCapacity | number? | ||
status | ClassStatus? | "active" | "paused" | "ended" | |
createdAt / updatedAt | Date |
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.
new Class(id: string, company: Company); // fetches lazily
new Class(data: LazyClassData | ClassData, company: Company, lazy?: boolean); // from known dataicon → GraduationCap (lucide-react). viewHref → /company/{companyID}/classes/{id}. Related-entity getters location, room, service lazily resolve and cache their instances (private-field caching).
Endpoint: /company/{companyID}/classes.
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/queryParent → children convenience wrappers:
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" })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.
| Field | Type | Lazy | Notes |
|---|---|---|---|
id | string | ✓ | |
classID | string | ✓ | part of the create payload directly, not a separate constructor/method param |
companyID | string | ✓ | |
recurrenceDays | WeekDay[] | ✓ | 0=Sun…6=Sat |
startTime | string | ✓ | "HH:MM:SS" |
endTime | string | ✓ | "HH:MM:SS" |
scheduleStartDate | string | ✓ | |
scheduleEndDate | string | null | ✓ | |
createdAt / updatedAt | Date |
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."
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.
Endpoint: /company/{companyID}/classes/schedules.
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/queryHelpers:
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 waycompany/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).
| Field | Type | Lazy | Notes |
|---|---|---|---|
id | string | ✓ | |
companyID | string? | ✓ | |
classID | string | ✓ | |
scheduleID | string? | ✓ | optional — an instance could in principle exist without a generating schedule |
locationID | string? | ✓ | |
roomID | string? | ✓ | |
staffID | string? | ✓ | |
startsAt | string | ✓ | |
endsAt | string | ✓ | |
status | ClassStatus | ✓ | "scheduled" | "active" | "completed" | "canceled" |
canceledAt | string | null | ||
cancelReason | string | null | ||
createdAt / updatedAt | Date |
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."
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.
Endpoint: /company/{companyID}/classes/instances.
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/querycompany/scheduling/service.ts. A bookable service — what an Appointment is booked against, and optionally what a Class is layered on top of via serviceID.
| Field | Type | Lazy | Notes |
|---|---|---|---|
id | string | ✓ | |
companyID | string? | ✓ | |
name | string | ✓ | |
description | string? | ✓ | |
durationMinutes | number | ✓ | |
price | number | null? | ✓ | nullable/optional for a free or not-yet-priced service |
visibleOnline | boolean | gates whether it's surfaced in Company.GetPublicServices | |
category | string? | ||
color | string? | ||
createdAt / updatedAt | Date |
CreateServiceData (payload for create): name, description, durationMinutes, price?.
new Service(id: string, company: Company);
new Service(data: LazyServiceData | ServiceData, company: Company, lazy?: boolean);No icon/viewHref overrides in this file.
Endpoint: /company/{companyID}/services.
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/queryParent → children convenience wrappers:
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 serviceIDService method. Discounted class/service pricing for a Group lives in the standalone GroupPricing class.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".
"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.| Field | Type | Lazy | Notes |
|---|---|---|---|
id | string | ✓ | |
companyID | string | ✓ | |
memberID | string | null | ✓ | nullable — a walk-in appointment can exist with no linked user yet |
serviceID | string | null | ✓ | |
locationID | string | null | ✓ | |
roomID | string | null | ✓ | |
staffID | string | null | ✓ | |
startsAt | string | ✓ | |
endsAt | string | ✓ | |
status | AppointmentStatus | ✓ | |
notes | string | null | ||
checkedInAt | string | null | ||
confirmedAt | string | null | ||
canceledAt | string | null | ||
cancelReason | string | null | ||
createdAt / updatedAt | Date |
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.
new Appointment(id: string, company: Company);
new Appointment(data: LazyAppointmentData | AppointmentData, company: Company, lazy?: boolean);No icon/viewHref overrides in this file.
/company/{companyID}/appointments. Uses a singular id= query param (not ids=) and a non-array PATCH body, unlike most other scheduling classes.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/** Sets status = "completed" and checkedInAt = NOW(). Returns 202 if the user has an alert. */
CheckIn(): Promise<SimpleResult<CheckInResponse & { appointmentID?: string }>>;
// POST .../appointments/{id}/check-inPer 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.
company/scheduling/booking.ts. A single spot in a ClassInstance — the third level of the Class → ClassSchedule → ClassInstance hierarchy.
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.
credit.ApplyToBooking() — service- and door/location-scoped credits are excluded (see Credit / CreditUse).| Field | Type | Lazy | Notes |
|---|---|---|---|
id | string | ✓ | |
classID | string | ✓ | |
instanceID | string | ✓ | |
companyID | string | ✓ | |
memberID | string | null | ✓ | |
status | BookingStatus | ✓ | |
checkoutLineID | string | null | set only when the booking was paid for via checkout rather than a credit | |
checkedInAt | string | null | ||
canceledAt | string | null | ||
cancelReason | string | null | ||
createdAt / updatedAt | Date |
CreateBookingData (payload for create): instanceID, memberID?, status?: "booked" | "waitlisted". memberID is optional (e.g. a waitlist hold created before a specific user is assigned).
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).
Endpoint: /company/{companyID}/classes/bookings.
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/** 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] }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.Class/ClassSchedule/ClassInstance or Appointment instead.| Field | Type | Lazy | Notes |
|---|---|---|---|
id | string | ✓ | |
startTime | string | ✓ | |
endTime | string | ✓ | |
capacity | number? | ✓ | |
companyID | string | ||
serviceID | string? | ||
instructorID | string? | predates the staffID naming used elsewhere — kept as-is since it's the literal wire field name | |
locationID | string? | ||
notes | string? | ||
createdAt / updatedAt | Date |
CreateTimeSlotItem (payload for create): startTime, endTime, capacity?, serviceID?, instructorID?, locationID?, notes?.
new TimeSlot(id: string, company: Company);
new TimeSlot(data: LazyTimeSlotData | TimeSlotData, company: Company, lazy?: boolean);No icon/viewHref overrides.
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:
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/queryAlso exports a standalone module-level function:
GetTimeSlot({ company, ids?, limit?, offset?, lazy? }: GetTimeSlotRequest): Promise<SimpleResult<TimeSlot[]>>;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.