SDK Reference

Company

The tenant entity itself, plus the company-scoped resources that live directly under zensile-sdk/company/ (not nested under member/, scheduling/, coupon/, etc.): Company, CompanyEvent, GroupPricing, MaintenanceTicket, SoundEffect, Staff(the grant), the company's own Stripe billing (subscription.ts/paymentMethod.ts), the unauthenticated public.ts endpoints, and DocumentTemplate.

Note:See the Introduction for DataStructure<T>, SimpleResult<T>, EventData<T>, and QueryRequest<T> — not re-documented here.

Company

zensile-sdk/company/company.ts. The tenant/organization entity. Most instance methods here are thin delegates to standalone functions in sibling modules (./paymentMethod, ./subscription, ./report/report, ./public, etc.) — kept as methods so callers go through company.X()rather than importing each module's functions directly.

Fields

LazyCompanyData:

FieldTypeNotes
idstring
namestring
isSandbox?booleanTrue for a temporary demo company provisioned by CreateSandbox().
sandboxExpiresAt?Date
sandboxClientID?string

CompanyData extends LazyCompanyData— full record, including Stripe Connect (for accepting customer payments) and Stripe Customer (for the company's own subscription billing) linkage:

FieldTypeNotes
ownerID?string
stripeCustomerID?stringCompany's own Stripe customer, used for platform subscription billing.
stripeConnectID?stringStripe Connect account for accepting customer payments.
stripePaymentMethodID?string
stripeChargesEnabled?boolean
stripePayoutsEnabled?boolean
[object Object] / [object Object]Date

Static methods

ts
Company.create(data: CreateCompanyData): Promise<EventData<string[]>>

POST /company. CreateCompanyData is { name: string, preset?: CompanyPreset }, where CompanyPreset (default "empty") optionally seeds starter data inside the same transaction as company creation: "gym_single_location"creates one “Main Location” and points the singleLocation/defaultLocationID CompanySettings at it; "gym_multi_location" sets singleLocation to "false" but creates no locations.

ts
Company.GetPublic(companyID: string): Promise<SimpleResult<PublicCompanyData>>
Company.GetPublicLocations(companyID: string): Promise<SimpleResult<PublicLocationData[]>>
Company.GetPublicServices(companyID: string): Promise<SimpleResult<PublicServiceData[]>>
Company.GetPublicProducts(companyID: string): Promise<SimpleResult<PublicProductData[]>>
Company.GetPublicClasses(companyID: string, opts?: GetPublicCompanyClassesOptions): Promise<SimpleResult<PublicClassInstanceData[]>>

Static and keyed by companyID rather than instance methods — a signed-out visitor has no Company instance to call these on (constructing one hits the authenticated /company fetch). Delegates to ./public — see public.ts below for field shapes and 404/402 gating.

Instance methods

Standard DataStructure overrides: save() (PATCH /company), delete() (DELETE /company?ids={id}), fetch(lazy?) (GET /company?ids={id}&limit=1).

Query wrappers (delegate to the resource's own .query()):

ts
company.QueryUsers(opts: QueryRequest): Promise<SimpleResult<Member[]>>
company.QueryVisits(opts: QueryRequest): Promise<SimpleResult<Visit[]>>
company.QueryEvents(opts: QueryRequest): Promise<SimpleResult<CompanyEvent[]>>       // read-only audit log
company.QueryNotifications(opts: QueryRequest): Promise<SimpleResult<Notification[]>>
company.QueryReportDefinitions(opts: QueryRequest): Promise<SimpleResult<ReportDefinition[]>>

Reporting/catalog:

ts
company.RunReport(request: RunReportRequest | RunSavedReportRequest): Promise<SimpleResult<ReportRow[]>>

Ad-hoc filter/groupBy/metric aggregation over an allowlisted resource, or { reportDefinitionID } to re-run a saved ReportDefinition. See Reports.

ts
company.GetAvailableCatalog(opts?: GetAvailableCatalogOptions): Promise<SimpleResult<AvailableCatalogResponse>>

Full products/services/upcoming-classes catalog, annotated for eligibility. Defaults to the caller's own eligibility — pass memberID (staff only) to view it for another member. See Misc.

Thin paginated list wrappers — prefer Query*/the resource's own .query() directly when you need filtering/sorting; these are just { limit, offset } conveniences:

ts
company.GetListOfCheckouts(limit = 100, offset = 0): Promise<Checkout[] | undefined>
company.GetListOfUsers(limit = 100, offset = 0): Promise<Member[] | undefined>
company.GetListOfRoles(limit = 100, offset = 0): Promise<Role[] | undefined>
company.GetListOfEvents(limit = 100, offset = 0): Promise<CompanyEvent[] | undefined>
company.GetListOfNotifications(limit = 100, offset = 0): Promise<Notification[] | undefined>
company.GetListOfProducts(limit = 100, offset = 0, lazy = true): Promise<Product[]>
company.GetListOfGroups(limit = 100, offset = 0, lazy = true): Promise<Group[]>
company.GetListOfLocations(limit = 100, offset = 0): Promise<Location[] | undefined>
Inconsistent return shape:Products/Groups return [] on failure (never undefined) and expose a lazy param; the rest return undefined on failure/empty and always query lazy: true internally (Locations) or the default (Users/Roles/Events/Notifications) implicitly via Member.query/etc.

User lookup/creation:

ts
company.GetUser(user: User): Promise<Member | undefined>

Finds this company's Member row for a given account User, if one is linked — queries filter: [{ field: "userID", condition: "=", value: user.id }], limit: 1.

ts
company.CreateUser(data: CreateMemberData): Promise<EventData<string[]>>   // Member.create(this, [data])
company.CreateRole(data: CreateRoleData): Promise<EventData<string[]>>    // Role.create(this, [data])

Session:

ts
company.SetActiveCompany(): Promise<boolean>

Sets this company as the active session (ss-company-id cookie) — the action taken when a user enters a company from the account dashboard. Returns false on error rather than throwing.

ts
Company.Logout()  // clears company session — see Auth & Session

Logo (generic media system, resourceType: "company"):

ts
company.UploadLogo(file: File | Blob, contentType: string = "image/jpeg")
company.GetLogo(): Promise<string | undefined>

Company's own on-file payment method (used to pay the platform subscription — delegates to ./paymentMethod, see below):

ts
company.GetPaymentMethod(): Promise<SimpleResult<CompanyPaymentMethodData | null>>
company.SetupPaymentMethod(): Promise<SimpleResult<SetupCompanyPaymentMethodResult>>
company.SavePaymentMethod(paymentMethodID: string): Promise<SimpleResult<EventData>>
company.deletePaymentMethod(): Promise<SimpleResult<EventData>>
Lowercase method name:deletePaymentMethodis lowercase-first — inconsistent casing vs. the rest of the class's PascalCase-style method names, present as-is in the source.

Company's own Stripe billing plan (the platform subscription the company pays Zensile — delegates to ./subscription, see Company subscription below):

ts
company.GetSubscription(): Promise<SimpleResult<CompanySubscriptionData>>
company.BuySubscription(): Promise<SimpleResult<BuySubscriptionResult>>       // 400 if no payment method on file or already active
company.CancelSubscription(): Promise<SimpleResult<EventData>>
company.PauseSubscription(): Promise<SimpleResult<EventData>>
company.ResumeSubscription(): Promise<SimpleResult<EventData>>
company.ChangePlan(newPriceId: string): Promise<SimpleResult<EventData>>       // prorated invoice generated immediately
company.UpdateSubscriptionPaymentMethod(paymentMethodId: string): Promise<SimpleResult<EventData>>
company.SetupAndSubscribe(): Promise<SimpleResult<SetupAndSubscribeResult>>    // full setup-intent → save → buy flow

Stripe Connect:

ts
company.LinkStripeAccount(express: boolean): Promise<SimpleResult<any>>

POST /company/{id}/stripe/connect with { express } — starts Stripe Connect onboarding (standard or express) so the company can accept customer payments.

CompanyEvent

zensile-sdk/company/event.ts. Read-only audit log of API events for a company.

System-generated only:Cannot be created, edited, or deleted manually — every write is system-generated as a side effect of other API calls.

Fields

LazyEventData:

FieldTypeNotes
idstring
userID?stringUnset for system-triggered events.
companyID?string
method"POST" | "GET" | "PATCH" | "DELETE" | "PUT"
endpointstring
statusCodenumber
successboolean
createdAtDate

CompanyEventData extends LazyEventData — adds diagnostics only captured on the full (non-lazy) fetch: resourceId?, errorCode?, errorMessage?, ipAddress?, userAgent?, requestId?, eventData?: Record<string, any>, responseTimeMs?, environment?: EventEnvironment ("dev" | "staging" | "prod" — lets audit queries filter out staging/dev noise).

Methods

ts
CompanyEvent.query(company: Company, data: QueryRequest): Promise<SimpleResult<CompanyEvent[]>>

POST /company/{cID}/events/query. Standard filter/sort/paging shape.

ts
event.save(): Promise<SimpleResult<EventData>>                         // always { status: 400, message: "Events cannot be updated directly." }
CompanyEvent.create(_company, _data): Promise<SimpleResult<EventData>> // always { status: 400, message: "Events are created automatically by the system." }
event.delete(): Promise<SimpleResult<EventData>>                       // always { status: 400, message: "Events cannot be deleted." }
ts
get event.IsSystemEvent(): boolean   // !userID
get event.IsUserAction(): boolean    // !!userID

Inverses of each other, kept as a pair for readability at call sites.

fetch() hits GET /company/{cID}/events?ids={id}&limit=1.

GroupPricing

zensile-sdk/company/groupPricing.ts. A group's discounted price for either a Class or a Service — exactly one of classID/serviceID is set, which determines whether the row lives at the /classes or /services endpoint prefix. Replaces the old per-class AddGroupPricing/GetGroupPricing/RemoveGroupPricing methods, shared across both pricing types.

Fields

LazyGroupPricingData: id, companyID?, groupID, classID?, serviceID?, discountType: DiscountType, discountAmount?: number.

DiscountType = "free" | "percent" | "fixed" — "free" waives the price entirely; "percent"/"fixed" require discountAmount.

GroupPricingData extends LazyGroupPricingData adds createdAt/updatedAt.

CreateGroupPricingData — same realm rules as the lazy shape (groupID, classID?, serviceID?, discountType, discountAmount?), passed alongside the explicit pricingType param on create.

Getters

group, service, class — each lazily resolves and caches (in a private field) the related Group/Service/Class instance from groupID/serviceID/classID.

Methods

ts
IsServicePricing(): boolean   // serviceID != undefined
IsClassPricing(): boolean     // classID != undefined
ts
GroupPricing.create(company: Company, pricingType: "services" | "classes", data: CreateGroupPricingData[]): Promise<SimpleResult<EventData>>

POST /company/{cID}/{pricingType}/groupPricing. Note the explicit pricingType param (unlike most create() statics, which don't need a type discriminator since it's inferable from the payload) — required because the endpoint itself differs by target type.

ts
GroupPricing.query(company: Company, data: QueryRequest): Promise<SimpleResult<GroupPricing[]>>

POST /company/{cID}/groupPricing — the query endpoint is not split by type; a single groupPricing query endpoint covers both class and service rows.

Split CRUD vs. single query endpoint:save()/delete()/fetch() route through a protected GetTypeEndpoint() helper that resolves to "services" or "classes" based on IsServicePricing()/IsClassPricing() — throws Error("Group pricing is not valid") if neither classID nor serviceID is set, since exactly one is expected. Yet the queryendpoint is unified rather than split the same way — don't assume CRUD and query share a routing scheme here.

Endpoint: /company/{cID}/services/groupPricing or /company/{cID}/classes/groupPricing depending on realm; query endpoint is /company/{cID}/groupPricing.

Note:Product has no group-pricing relationship at all — only Class/Service do.

MaintenanceTicket

zensile-sdk/company/maintenanceTicket.ts. A facility repair/issue ticket at a Location, optionally narrowed to a specific Room, with up to MAX_TICKET_PHOTOS photos and MAX_TICKET_DOCUMENTS documents attached via the generic media system.

Fields

LazyMaintenanceTicketData: id, companyID, locationID, roomID?: string | null, name?: string | null, state: MaintenanceTicketState, priority: MaintenanceTicketPriority.

MaintenanceTicketData extends LazyMaintenanceTicketData adds description?, notes?, resolvedAt?: Date | null, createdAt, updatedAt (free-text detail fields only present on the non-lazy fetch).

CreateMaintenanceTicketData: locationID, roomID?, name?, description?, notes?, state?, priority?.

Enums

MaintenanceTicketState = "active" | "paused" | "resolved" (MaintenanceTicketStateOptions for display labels).

MaintenanceTicketPriority = "low" | "medium" | "high" | "urgent" (MaintenanceTicketPriorityOptions for display labels).

Media constants and pattern

ts
export const MAX_TICKET_PHOTOS = 5;
export const MAX_TICKET_DOCUMENTS = 10;
Shared backend pool:Mirrors the backend's MEDIA_RESOURCE_TYPES["maintenanceTicket"].maxItems (15) — kept in sync manually since photos and documents are both stored in the generic media table, not columns on maintenance_tickets. Photos and documents share one backend-enforced pool; these two constants just split that pool into a soft per-kind allotment on the client (5 + 10 = 15).
ts
interface TicketPhoto { id: string; url: string; contentType?: string; createdAt: Date; }
interface TicketDocument { id: string; url: string; contentType?: string; createdAt: Date; } // same wire shape as TicketPhoto, kept distinct by kind
ts
ticket.UploadPhoto(file: File | Blob, contentType: string = "image/jpeg")
ticket.GetPhotos(): Promise<TicketPhoto[]>
ticket.DeletePhoto(id: string)
ticket.UploadDocument(file: File | Blob, contentType: string)
ticket.GetDocuments(): Promise<TicketDocument[]>
ticket.DeleteDocument(id: string)
contentType-less rows are treated as photos:GetPhotos() lists every active media item via ListMedia and filters to items with no stored contentType or one starting with image/ — items with no contentType are treated as photos rather than excluded, since older rows predate contentType being recorded (before documents existed, every upload was a photo). GetDocuments() filters to the complementary set (contentType set and not starting with image/).

Getters / helpers

ts
get ticket.location(): Location | undefined   // cached instance from locationID
get ticket.room(): Room | undefined           // cached instance from roomID
get ticket.IsResolved(): boolean              // state === "resolved"
// Detail route: /company/{cID}/maintenanceTickets/{id}
ts
ticket.Resolve(): Promise<SimpleResult<EventData>>

Sets state: "resolved" + resolvedAt: new Date() via setData (new object spread) and calls save().

Static methods

ts
MaintenanceTicket.create(company: Company, tickets: CreateMaintenanceTicketData[]): Promise<EventData<string[]>>
MaintenanceTicket.query(company: Company, data: QueryRequest): Promise<SimpleResult<MaintenanceTicket[]>>

Endpoint: /company/{cID}/maintenanceTickets (create/save/delete), /company/{cID}/maintenanceTickets/query (query).

SoundEffect

zensile-sdk/company/sound-effect.ts (file soundEffect.ts). Company-configurable audio played on check-in/check-out/booking events.

Fields

LazySoundEffectData: id, name, trigger: SoundEffectTrigger, isDefault: boolean (isDefault marks the one played when a trigger has no more specific override).

SoundEffectData extends LazySoundEffectData adds companyID, s3Key, createdAt, updatedAt, deletedAt?: Date | null.

CreateSoundEffectItem: name, s3Key (must come from a prior UploadAudio call), trigger, isDefault. UpdateSoundEffectItem: same fields, all optional — only name/s3Key/trigger/isDefault are ever persisted by save().

Enums

SoundEffectTrigger = "check_in" | "check_out" | "class_booking" | "appointment_booking".

SoundEffectContentType = "audio/mpeg" | "audio/ogg" | "audio/wav" | "audio/mp4".

Methods

ts
soundEffect.save(): Promise<SimpleResult<EventData>>
SoundEffect.create(company: Company, items: CreateSoundEffectItem[]): Promise<SimpleResult<EventData>>
soundEffect.delete(): Promise<SimpleResult<EventData>>
SoundEffect.deleteMany(company: Company, ids: string[]): Promise<SimpleResult<EventData>>   // deletes several in one call (Promise.all over per-id DELETE)
SoundEffect.query(company: Company, data: QueryRequest): Promise<SimpleResult<SoundEffect[]>>
ts
soundEffect.UploadAudio(file: File | Blob, contentType: SoundEffectContentType = "audio/mpeg"): Promise<SimpleResult<{ key: string }>>
Must call save() afterwards:Uploads an audio file for this sound effect and returns the s3Keyon success — mutates the loaded data's s3Key locally but you must call save() afterwards to persist the new key.
ts
soundEffect.GetAudioURL(): Promise<string | undefined>

Resolves the stored s3Key to a temporary public download/playback URL via POST /company/{cID}/media/download-url.

ts
UploadSoundEffectAudio({ company, file, contentType }): Promise<SimpleResult<{ key: string }>>

Standalone function — uploads audio to S3 without requiring the SoundEffect to already exist (the key is needed up front to call SoundEffect.create, before an instance exists to call .UploadAudio() on). Requests a presigned URL via POST /company/{cID}/media/upload-url with { type: "sound-effect", contentType }, then PUTs the file directly. Prefer the instance method when replacing an existing effect's audio.

ts
GetSoundEffect({ company, ids?, limit?, offset? }): Promise<SimpleResult<SoundEffect[]>>

Standalone paginated list function, optionally narrowed to specific ids. GET /company/{cID}/soundEffects.

Singular id on delete:Endpoint: /company/{cID}/soundEffects (CRUD), /company/{cID}/soundEffects/query (query), /company/{cID}/soundEffects?id={id} (per-id delete — note the singular id, not ids, unlike most other delete endpoints in this codebase).

Staff

zensile-sdk/company/staff.ts. Grants a Member staff access — pure employee identity now (memberID + companyID). Role assignment doesn't live here as a single field any more: a staff member can hold several roles at once, each optionally scoped to a Location, via StaffRole (below).

Fields

LazyStaffData: id, companyID, memberID. StaffData extends LazyStaffData adds createdAt, updatedAt. CreateStaffData: companyID, memberID, plus optional roleID/locationID convenience fields — set them to grant that one role as part of creation (equivalent to a follow-up StaffRole.create() call), or omit both to create a bare staff row with no roles yet.

Parent → children convenience methods

ts
staff.rates(opts: QueryRequest)       // StaffRate.query(company, { filter: [{ field: "staffID", condition: "=", value: this.id }], ...opts })
staff.timeBlocks(opts: QueryRequest)  // StaffTimeBlock.query(company, { filter: [{ field: "staffID", condition: "=", value: this.id }], ...opts })
staff.roles(opts: QueryRequest)       // StaffRole.query(company, { filter: [{ field: "staffID", condition: "=", value: this.id }], ...opts })

Mirrors the coupon.promotionalCodes() parent-→-children pattern.

CRUD

ts
Staff.create(company: Company, staff: CreateStaffData[]): Promise<EventData<string[]>>
staff.save(): Promise<SimpleResult<EventData>>
staff.delete(): Promise<SimpleResult<EventData>>
Staff.query(company: Company, data: QueryRequest): Promise<SimpleResult<Staff[]>>

Endpoint: /company/{cID}/staff (CRUD), /company/{cID}/staff/query (query).

The detailed StaffRate, StaffPayrollBonus, StaffTimeBlock, TimeBlockBreak, and payroll (PayrollSchedule/PayrollRun/PayrollRunLine) classes that build on this grant are documented in Payroll — this section covers only the Staff grant record itself.

StaffRole

zensile-sdk/company/staff/role.ts — one role grant held by a staff member, replacing the old single Staff.roleID column. A staff member is expected to hold several of these at once — e.g. a regional manager granted the same roleIDonce per location — there's no "one per staffID" constraint. Effective permissions are the bitwise-OR union of every held role's levels (see Management's loadPermissions); locationID is carried for future location-aware authorization but isn't consulted by permission checks today.

ts
interface LazyStaffRoleData {
  id: string;
  companyID: string;
  staffID: string;
  roleID: string;
  locationID?: string | null;   // unset/null = applies company-wide
}
interface StaffRoleData extends LazyStaffRoleData {
  createdAt: Date;   // hard-deleted junction row — no updatedAt/deletedAt
}
ts
StaffRole.create(company: Company, grants: CreateStaffRoleData[]): Promise<EventData<CreateStaffRoleData[]>>
staffRole.delete(): Promise<SimpleResult<EventData>>
StaffRole.query(company: Company, data: QueryRequest): Promise<SimpleResult<StaffRole[]>>

Endpoint: /company/{cID}/staff/roles (create/revoke), /company/{cID}/staff/roles/query (query). Getters staff/role/location (the last undefined for a company-wide grant) lazily resolve and cache the related instances.

Gotcha:create() is idempotent and batch: granting a staffID/roleID/locationID combination that's already held is silently skipped (not an error), as is an unknown/foreign staffID or roleID. The response data echoes back only the grants actually processed as plain {staffID, roleID, locationID?} objects — not generated ids like most create() methods, since a repeat grant returns no new row. Re-query via staff.roles(opts) afterward to get real StaffRole instances/ids. There is also no edit — a grant is an immutable identity row; changing a role or location means deleting this grant and creating a new one.

Company subscription

zensile-sdk/company/subscription.ts. The company's own Stripe platform billing plan — the subscription the company pays to Zensile for use of the platform. Not a DataStructure subclass — a set of standalone functions, each also exposed as a Company instance-method delegate (see Company above).

Naming collision, read carefully:This is entirely distinct from a company user's subscription to a product the company sells (zensile-sdk/company/member/subscription.ts's Subscription class, documented in Members), and from that user's on-file cards (zensile-sdk/company/member/paymentMethod.ts, documented in Checkout (POS)). "Subscription" and "PaymentMethod" both exist at the company level (this page, billing-the-company) and the member level (a different file, billing-a-customer) — always check which module a given Subscription/PaymentMethod reference comes from.

Types

ts
type CompanySubscriptionStatus = "none" | "active" | "past_due" | "paused" | "canceled";

interface CompanySubscriptionData {
  hasActiveSubscription: boolean;
  subscriptionStatus: CompanySubscriptionStatus;  // app-normalized, for gating
  currentPeriodEnd: number | null;
  daysRemaining: number | null;
  stripeSubscriptionID: string | null;
  stripeCustomerID: string | null;
  status: string;   // mirrors Stripe's raw status string
}

interface BuySubscriptionResult {
  stripeSubscriptionID: string;
  status: string;
  currentPeriodEnd: number;
}

interface SetupAndSubscribeResult {
  clientSecret: string;
  setupIntentID: string;
  complete: (paymentMethodID: string) => Promise<SimpleResult<BuySubscriptionResult>>;
}

Functions

ts
GetCompanySubscription(company: Company): Promise<SimpleResult<CompanySubscriptionData>>

GET /company/{cID}/subscription.

ts
BuyCompanySubscription(company: Company): Promise<SimpleResult<BuySubscriptionResult>>

POST /company/{cID}/subscription— buys using the company's stored payment method. Returns 400with a specific message if there's no payment method on file, or if a subscription is already active; check result.message to distinguish these cases.

ts
CancelCompanySubscription(company: Company): Promise<SimpleResult<EventData>>       // DELETE /company/{cID}/subscription
PauseCompanySubscription(company: Company): Promise<SimpleResult<EventData>>        // PATCH { action: "pause" }
ResumeCompanySubscription(company: Company): Promise<SimpleResult<EventData>>       // PATCH { action: "resume" }
ChangeCompanyPlan(company: Company, newPriceId: string): Promise<SimpleResult<EventData>>   // PATCH { action: "updatePlan", newPriceId } — prorated invoice generated immediately; requires the company to already have a Stripe Connect account (stripeConnectID)
UpdateCompanySubscriptionPaymentMethod(company: Company, paymentMethodId: string): Promise<SimpleResult<EventData>>  // PATCH { action: "updatePaymentMethod", paymentMethodId } — also requires stripeConnectID
ts
SetupAndSubscribeCompany(company: Company): Promise<SimpleResult<SetupAndSubscribeResult>>

Full setup-flow helper, chaining three calls:

  1. SetupCompanyPaymentMethod(company) (from ./paymentMethod) → clientSecret + setupIntentID
  2. Caller confirms with Stripe.js using clientSecret
  3. Caller invokes the returned complete(paymentMethodID), which calls SaveCompanyPaymentMethod then BuyCompanySubscription
ts
const setup = await company.SetupAndSubscribe();
// confirm with Stripe.js using setup.data.clientSecret
const result = await setup.data.complete(paymentMethodID);

Endpoint throughout: /company/{cID}/subscription, verbs distinguished by HTTP method and (for PATCH) an action discriminator in the body.

Company-level PaymentMethod

zensile-sdk/company/paymentMethod.ts. The company's own singleon-file Stripe payment method — used only to pay the company's platform subscription (see Company subscriptionabove). Distinct from a company user's payment methods (zensile-sdk/company/member/paymentMethod.ts, Checkout (POS)), which back POS checkouts and of which a user can have several. Not a DataStructure subclass — standalone functions, also exposed as Company instance-method delegates.

Types

ts
interface CompanyPaymentMethodData {
  id: string;
  brand?: string;
  last4?: string;
  expMonth?: number;
  expYear?: number;
}

interface SetupCompanyPaymentMethodResult {
  clientSecret: string;
  setupIntentID: string;
}

Functions

ts
SetupCompanyPaymentMethod(company: Company): Promise<SimpleResult<SetupCompanyPaymentMethodResult>>

POST /company/{cID}/paymentMethods/setup — starts a Stripe SetupIntent for attaching a new company payment method.

ts
GetCompanyPaymentMethod(company: Company): Promise<SimpleResult<CompanyPaymentMethodData | null>>

GET /company/{cID}/paymentMethods — null data if none is on file.

ts
SaveCompanyPaymentMethod(company: Company, paymentMethodID: string): Promise<SimpleResult<EventData>>
Replaces the existing card:PATCH /company/{cID}/paymentMethods with { paymentMethodID }— saves a confirmed Stripe payment method id as the company's on-file card, replacing any existing one (single-card model, unlike the member-level list).
ts
DeleteCompanyPaymentMethod(company: Company): Promise<SimpleResult<EventData>>

DELETE /company/{cID}/paymentMethods.

public.ts

zensile-sdk/company/public.ts. Unauthenticated public.* endpoints — no cookie/token required (every APIRequest call passes needUser: false as the 4th argument), and no Company instance needed since the caller may not be signed into this company (or signed in at all). Every handler is gated backend-side by resolvePublicCompany(): 404 for an unknown/deleted company, 402 for one without an active platform subscription.

Responses are deliberately thin since they're visible to anonymous visitors:

  • Locations expose only address/phone — no doors/rooms
  • Services/products are filtered to visibleOnline
  • Products collapse the raw stock count to a boolean inStock
  • Classes carry availability (spotsAvailable/isFull/willWaitlist) but no caller-specific eligibility fields — no alreadyBooked/hasCredit/canBook, since there's no auth context to compute them from (that's the authenticated GetAvailableCatalog's job, see Misc)

Types

ts
interface PublicCompanyData { id: string; name: string; logoUrl?: string; }

interface PublicLocationData {
  id: string; name?: string;
  addressLine1?: string; addressLine2?: string; city?: string; state?: string; country?: string; postalCode?: string;
  phone?: string;
}

interface PublicServiceData {
  id: string; name: string; description?: string;
  durationMinutes: number; price?: number | null; category?: string; color?: string;
}

interface PublicProductData {
  id: string; name: string; description?: string; price?: number; sku?: string;
  inStock: boolean;   // true when raw stock is NULL (unlimited) or > 0
}

interface PublicClassInstanceData {
  id: string; classID: string; className: string; classCapacity: number; classPrice?: number;
  locationID?: string; roomID?: string;
  startsAt: string; endsAt: string;
  spotsAvailable: number;   // remaining open (non-waitlist) spots; can be 0 while willWaitlist is still true
  isFull: boolean;
  willWaitlist: boolean;
}

interface GetPublicCompanyClassesOptions {
  days?: number;   // backend defaults to 14 when omitted
}

Functions

ts
GetPublicCompany(companyID: string): Promise<SimpleResult<PublicCompanyData>>

GET /public/company/{companyID} — name + presigned logo URL.

ts
GetPublicCompanyLocations(companyID: string): Promise<SimpleResult<PublicLocationData[]>>

GET /public/company/{companyID}/locations.

ts
GetPublicCompanyServices(companyID: string): Promise<SimpleResult<PublicServiceData[]>>

GET /public/company/{companyID}/services— bookable "appointment" types, visibleOnline only.

ts
GetPublicCompanyProducts(companyID: string): Promise<SimpleResult<PublicProductData[]>>

GET /public/company/{companyID}/products — visibleOnline only.

ts
GetPublicCompanyClasses(companyID: string, opts?: GetPublicCompanyClassesOptions): Promise<SimpleResult<PublicClassInstanceData[]>>

GET /public/company/{companyID}/classes[?days=N] — upcoming instances with availability.

All five are wrapped by the Company static methods Company.GetPublic/GetPublicLocations/GetPublicServices/GetPublicProducts/GetPublicClasses documented under Companyabove — prefer those over calling this module's functions directly.

DocumentTemplate

zensile-sdk/company/documentTemplate.ts. A company-scoped, reusable fillable PDF (e.g. a liability waiver). fieldMap resolves merge keys (e.g. "member.firstName") against the buyer/company context onto the base PDF's AcroForm text fields, and signaturePlacement positions a captured signature image when requiresSignature is true. The base PDF itself is attached via the generic media system (maxItems: 1, application/pdf only) rather than stored inline. Attaching a template to a product via a document-type Feature (zensile-sdk/company/product/feature.ts) generates a MemberDocument for the buyer on purchase.

Fields

LazyDocumentTemplateData: id, companyID, name, fieldMap: DocumentTemplateFieldMap, signaturePlacement?: SignaturePlacement | null, requiresSignature: boolean, active: boolean.

Note:fieldMap/signaturePlacement are JSON columns — mysql2 auto-parses them back into objects on read, so (like ReportDefinition.definition) there's no separate lazy/full split for them; they're present on the lazy shape too.

DocumentTemplateData extends LazyDocumentTemplateData adds createdAt, updatedAt.

ts
type DocumentTemplateFieldMap = Record<string, string>;   // merge key → AcroForm field name

interface SignaturePlacement {
  page: number; x: number; y: number; width: number; height: number;
}
Base PDF uploaded separately:CreateDocumentTemplateData: name, fieldMap, signaturePlacement?, requiresSignature?, active?. The base fillable PDF is uploaded separately via UploadBasePdfonce the template row exists — the resource must already exist for the generic media system, so this can't be threaded through the create payload.

Parent → children convenience method

ts
template.documents(opts: QueryRequest)

MemberDocument.query(company, { filter: [{ field: "documentTemplateID", condition: "=", value: this.id }], ...opts }) — every MemberDocument issued from this template. See Members for MemberDocument.

Media

ts
template.UploadBasePdf(file: File | Blob): Promise<...>

Uploads (or replaces — maxItems: 1) the base fillable PDF via UploadMedia({ resourceType: "documentTemplate", contentType: "application/pdf", ... }).

ts
template.GetBasePdfUrl(): Promise<string | undefined>

Resolves the uploaded base PDF to a temporary public download URL via DownloadMedia.

CRUD

ts
DocumentTemplate.create(company: Company, templates: CreateDocumentTemplateData[]): Promise<EventData<string[]>>
template.save(): Promise<SimpleResult<EventData>>
template.delete(): Promise<SimpleResult<EventData>>
DocumentTemplate.query(company: Company, data: QueryRequest): Promise<SimpleResult<DocumentTemplate[]>>

Endpoint: /company/{cID}/documentTemplates (CRUD), /company/{cID}/documentTemplates/query (query).