@thebookingkit/core API
All exports from @thebookingkit/core.
Slot Engine
Section titled “Slot Engine”import { getAvailableSlots, isSlotAvailable } from "@thebookingkit/core";
// Compute available slots for a date rangeconst slots = getAvailableSlots( rules, overrides, existingBookings, { start: new Date("2026-03-09"), end: new Date("2026-03-13") }, "America/New_York", { duration: 30, bufferAfter: 15 });
// Check if a single slot is availableconst available = isSlotAvailable( rules, overrides, existingBookings, new Date("2026-03-10T14:00:00.000Z"), new Date("2026-03-10T14:30:00.000Z"));| Function | Description |
|---|---|
getAvailableSlots(rules, overrides, bookings, dateRange, timezone, options?) |
Compute available time slots |
isSlotAvailable(rules, overrides, bookings, start, end, bufferBefore?, bufferAfter?) |
Check if a specific slot is open |
RRULE Parser
Section titled “RRULE Parser”import { parseRecurrence, InvalidRRuleError } from "@thebookingkit/core";
const occurrences = parseRecurrence( "RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR", { start: new Date("2026-03-01"), end: new Date("2026-03-31") }, "09:00", "17:00");| Function | Description |
|---|---|
parseRecurrence(rrule, dateRange, startTime, endTime) |
Expand RRULE into date occurrences |
Timezone
Section titled “Timezone”import { normalizeToUTC, utcToLocal, isValidTimezone, getTimezoneOffset } from "@thebookingkit/core";
const utc = normalizeToUTC("2026-03-10T09:00:00", "America/New_York");const local = utcToLocal(new Date("2026-03-10T14:00:00.000Z"), "America/New_York");const valid = isValidTimezone("America/New_York");const offset = getTimezoneOffset("America/New_York", new Date());| Function | Description |
|---|---|
normalizeToUTC(localTime, timezone) |
Convert local time to UTC Date |
utcToLocal(utcDate, timezone) |
Convert UTC Date to local time string |
isValidTimezone(timezone) |
Validate IANA timezone string |
getTimezoneOffset(timezone, date) |
Get UTC offset in minutes |
Booking Limits
Section titled “Booking Limits”import { computeBookingLimits, filterSlotsByLimits } from "@thebookingkit/core";| Function | Description |
|---|---|
computeBookingLimits(config, existingBookings, dateRange) |
Calculate remaining capacity |
filterSlotsByLimits(slots, config, existingBookings) |
Remove slots that exceed limits |
Event Types
Section titled “Event Types”import { validateEventType, generateSlug, validateQuestionResponses, EventTypeValidationError } from "@thebookingkit/core";Confirmation Mode
Section titled “Confirmation Mode”import { getInitialBookingStatus, getAutoRejectDeadline, isPendingBookingOverdue, CONFIRMATION_TIMEOUT_HOURS } from "@thebookingkit/core";Auth Middleware
Section titled “Auth Middleware”import { withAuth, assertProviderOwnership, assertCustomerAccess } from "@thebookingkit/server";| Function | Description |
|---|---|
withAuth(handler, adapter, options?) |
Wrap a handler with authentication |
assertProviderOwnership(session, providerId) |
Assert the user owns the provider |
assertCustomerAccess(session, customerEmail) |
Assert the user has access to the customer’s data |
Serialization Retry
Section titled “Serialization Retry”import { withSerializableRetry, SerializationRetryExhaustedError } from "@thebookingkit/server";Booking Tokens
Section titled “Booking Tokens”import { generateBookingToken, verifyBookingToken } from "@thebookingkit/server";Team Scheduling
Section titled “Team Scheduling”import { getTeamSlots, assignHost, resolveManagedEventType, isFieldLocked, propagateTemplateChanges } from "@thebookingkit/core";
// Compute team slotsconst slots = getTeamSlots(teamMembers, dateRange, timezone, options);
// Assign host for a bookingconst host = assignHost(teamMembers, bookingCounts, "round_robin");
// Check field locks in managed templatesconst locked = isFieldLocked(template, "durationMinutes");| Function | Description |
|---|---|
getTeamSlots(members, dateRange, timezone, options) |
Compute available slots across team |
assignHost(members, counts, strategy) |
Assign team member to booking |
resolveManagedEventType(template, overrides) |
Resolve template for member |
isFieldLocked(template, field) |
Check if field is locked |
propagateTemplateChanges(template, eventTypes) |
Propagate template updates |
Payments
Section titled “Payments”import { evaluateCancellationFee, validateCancellationPolicy, computePaymentSummary, requiresPayment, formatPaymentAmount } from "@thebookingkit/core";
const policy = [ { hoursBefore: 48, feePercentage: 0 }, { hoursBefore: 0, feePercentage: 50 },];
const fee = evaluateCancellationFee(policy, 5000, appointmentStart, cancellationTime);const summary = computePaymentSummary(paymentRecords);const formatted = formatPaymentAmount(5000, "usd"); // "$50.00"| Function | Description |
|---|---|
evaluateCancellationFee(policy, amountCents, start, cancelTime) |
Calculate cancellation fee |
validateCancellationPolicy(policy) |
Validate policy structure |
computePaymentSummary(payments) |
Aggregate payment data |
requiresPayment(eventType) |
Check if payment required |
formatPaymentAmount(cents, currency) |
Format amount for display |
Webhooks
Section titled “Webhooks”import { signWebhookPayload, verifyWebhookSignature, matchWebhookSubscriptions } from "@thebookingkit/server";
const signature = signWebhookPayload(JSON.stringify(payload), secret, timestamp);const result = verifyWebhookSignature(body, signature, timestamp, secret);const matching = matchWebhookSubscriptions(subscriptions, "booking.created");| Function | Description |
|---|---|
signWebhookPayload(payload, secret, timestamp) |
Sign webhook with HMAC |
verifyWebhookSignature(payload, sig, ts, secret, tolerance?) |
Verify webhook signature |
matchWebhookSubscriptions(subscriptions, trigger) |
Find subscriptions for trigger |
getRetryDelay(attempt, config) |
Calculate retry delay |
Recurring Bookings
Section titled “Recurring Bookings”import { generateOccurrences, checkRecurringAvailability, cancelFutureOccurrences } from "@thebookingkit/core";
const occurrences = generateOccurrences({ startsAt: new Date(), durationMinutes: 30, frequency: "weekly", count: 8,});
const availability = checkRecurringAvailability(occurrences, rules, overrides, bookings);const cancelled = cancelFutureOccurrences(seriesBookings, new Date("2026-04-01"));| Function | Description |
|---|---|
generateOccurrences(input) |
Generate series occurrences |
checkRecurringAvailability(occurrences, rules, overrides, bookings) |
Validate availability |
cancelFutureOccurrences(bookings, fromDate) |
Cancel future bookings |
isValidFrequency(frequency) |
Validate frequency string |
Seats / Group Bookings
Section titled “Seats / Group Bookings”import { computeSeatAvailability, canReserveSeat, isGroupEvent, computeGroupEventSummary, formatSeatCount, validateSeatReservation, SeatError } from "@thebookingkit/core";Notifications
Section titled “Notifications”import { sendConfirmationEmail, sendReminderEmail, sendCancellationEmail, sendRescheduleEmail, scheduleAutoReject, syncBookingToCalendar, deleteBookingFromCalendar, formatDateTimeForEmail, formatDurationForEmail } from "@thebookingkit/server";Email Templates
Section titled “Email Templates”import { interpolateTemplate, CONFIRMATION_EMAIL_HTML, CONFIRMATION_EMAIL_TEXT, REMINDER_EMAIL_HTML, CANCELLATION_EMAIL_HTML, RESCHEDULE_EMAIL_HTML } from "@thebookingkit/server";Routing Forms
Section titled “Routing Forms”import { validateRoutingForm, evaluateRoutingRules, validateRoutingResponses, computeRoutingAnalytics, RoutingFormValidationError } from "@thebookingkit/core";import { validateEmbedConfig, generateEmbedSnippet, generateAllSnippets, buildEmbedUrl, EmbedConfigError } from "@thebookingkit/core";REST API Utilities
Section titled “REST API Utilities”import { createErrorResponse, createSuccessResponse, createPaginatedResponse, generateApiKey, hashApiKey, verifyApiKey, hasScope, isKeyExpired, checkRateLimit, encodeCursor, decodeCursor, validateSlotQueryParams, parseSortParam, API_ERROR_CODES } from "@thebookingkit/server";CLI Utilities
Section titled “CLI Utilities”import { COMPONENT_REGISTRY, findComponent, resolveComponentDependencies, listComponents, createManifestEntry, hasLocalModifications, generateThebookingkitConfig, generateEnvTemplate, parseMigrationFiles, getPendingMigrations, DEFAULT_MANIFEST } from "@thebookingkit/cli";Multi-Tenancy
Section titled “Multi-Tenancy”import { resolveEffectiveSettings, getRolePermissions, roleHasPermission, assertOrgPermission, assertTenantScope, buildOrgBookingUrl, parseOrgBookingPath, TenantAuthorizationError, GLOBAL_DEFAULTS } from "@thebookingkit/server";Errors
Section titled “Errors”import { BookingConflictError, SerializationRetryExhaustedError, UnauthorizedError, ForbiddenError } from "@thebookingkit/core";Adapters
Section titled “Adapters”import type { AuthAdapter, EmailAdapter, CalendarAdapter, JobAdapter, PaymentAdapter, StorageAdapter, SmsAdapter } from "@thebookingkit/server";import { generateICSAttachment, JOB_NAMES } from "@thebookingkit/server";Walk-In Queue
Section titled “Walk-In Queue”Imported from @thebookingkit/core. Enables providers to accept walk-in customers
alongside scheduled appointments. Walk-ins are placed in gaps between existing
bookings, with automatic wait-time estimation and a lifecycle state machine.
| Type | Description |
|---|---|
BookingSource |
"online" | "walk_in" | "phone" | "admin" — how a booking was created |
WalkInStatus |
"queued" | "in_service" | "completed" | "no_show" | "cancelled" |
WalkInQueueEntry |
Full walk-in record: id, bookingId, providerId, queuePosition, estimatedWaitMinutes, checkedInAt, serviceStartedAt, completedAt, status, customerName, durationMinutes, and optional contact fields |
AddWalkInInput |
Input shape for adding a walk-in: providerId, eventTypeId, durationMinutes, customerName, optional contact, optional buffers |
AddWalkInResult |
Result of adding a walk-in: queueEntry, estimatedStartTime, estimatedWaitMinutes, queuePosition |
WaitTimeEstimate |
estimatedMinutes, queueLength, nextAvailableAt |
ProviderWalkInState |
acceptingWalkIns: boolean, withinWorkingHours: boolean |
WalkInAnalytics |
Aggregated analytics — see computeWalkInAnalytics |
Functions
Section titled “Functions”import { estimateWaitTime, findNextAvailableGap, isValidQueueTransition, validateQueueTransition, recomputeQueuePositions, reorderQueue, recomputeWaitTimes, isAcceptingWalkIns, computeWalkInAnalytics,} from "@thebookingkit/core";
// Estimate how long a new walk-in will waitconst estimate = estimateWaitTime(queue, existingBookings, 30);// => { estimatedMinutes: 45, queueLength: 2, nextAvailableAt: Date }
// Find the earliest start time for a new walk-inconst startTime = findNextAvailableGap(queue, existingBookings, 30, 5, 5);
// State machine guardconst ok = isValidQueueTransition("queued", "in_service"); // trueconst bad = isValidQueueTransition("completed", "queued"); // false
// Throws InvalidQueueTransitionError if invalidvalidateQueueTransition("queued", "in_service");
// Normalise positions after a removalconst updated = recomputeQueuePositions(activeEntries);
// Drag-and-drop reorderconst reordered = reorderQueue(entries, ["id-3", "id-1", "id-2"]);
// Refresh all estimated wait times after a changeconst refreshed = recomputeWaitTimes(queue, existingBookings);
// Check provider walk-in status right nowconst state = isAcceptingWalkIns(true, rules, overrides);// => { acceptingWalkIns: true, withinWorkingHours: true }
// Aggregate analytics for a reporting periodconst analytics = computeWalkInAnalytics(entriesInRange, totalBookings);| Function | Signature | Description |
|---|---|---|
estimateWaitTime |
(queue, existingBookings, serviceDuration, now?) |
Estimate wait minutes for a new walk-in based on active queue and any in-progress appointment |
findNextAvailableGap |
(queue, existingBookings, durationMinutes, bufferBefore?, bufferAfter?, now?) |
Find the earliest moment the provider can begin serving a new walk-in |
isValidQueueTransition |
(from, to) |
Return true if the WalkInStatus transition is permitted by the state machine |
validateQueueTransition |
(current, target) |
Assert the transition is valid; throws InvalidQueueTransitionError otherwise |
recomputeQueuePositions |
(entries) |
Renumber queuePosition fields starting from 1, preserving input order |
reorderQueue |
(entries, orderedIds) |
Apply a new position order by entry ID; keeps the in-service entry at position 1 |
recomputeWaitTimes |
(queue, existingBookings, now?) |
Refresh estimatedWaitMinutes on every queued entry after any queue change |
isAcceptingWalkIns |
(acceptingWalkIns, rules, overrides, now?) |
Check the provider’s toggle and current availability window |
computeWalkInAnalytics |
(entries, totalBookings) |
Return aggregated WalkInAnalytics including no-show rate, hourly/daily distributions, and walk-in ratio |
State Machine
Section titled “State Machine”Valid transitions for WalkInStatus:
| From | Allowed targets |
|---|---|
queued |
in_service, no_show, cancelled |
in_service |
completed, no_show |
completed |
— (terminal) |
no_show |
— (terminal) |
cancelled |
— (terminal) |
Error Classes
Section titled “Error Classes”| Class | code |
Thrown when |
|---|---|---|
WalkInsDisabledError |
WALK_INS_DISABLED |
Provider’s acceptingWalkIns flag is false |
QueueEntryNotFoundError |
QUEUE_ENTRY_NOT_FOUND |
Referenced queue entry ID does not exist |
InvalidQueueTransitionError |
INVALID_QUEUE_TRANSITION |
validateQueueTransition is called with a disallowed from → to pair |
Kiosk Mode
Section titled “Kiosk Mode”Imported from @thebookingkit/core. Configuration, validation, and utilities for
the interactive kiosk calendar view (E-20). Handles display settings, reschedule
validation, break/block management, conflict detection, and multi-provider resource
views.
| Type | Description |
|---|---|
BlockDensityMode |
"compact" | "standard" | "detailed" — calendar block density |
ColorCodingMode |
"status" | "event_type" | "source" — block color scheme |
KioskViewType |
"day" | "3day" | "week" — default calendar view |
KioskFieldVisibility |
Object controlling which fields appear on compact blocks and detail popovers |
KioskSettings |
Full kiosk configuration: view, density, color coding, field visibility, auto-lock, PIN, walk-in sidebar, slot height, day start/end hours |
RescheduleValidation |
{ valid, reason?, conflictDetails? } — result of validateReschedule |
BlockType |
"break" | "personal" | "meeting" | "closed" |
BreakBlockInput |
{ title, startTime, endTime, blockType, recurring } |
KioskProvider |
{ id, displayName, acceptingWalkIns, queueCount, visible } |
Constants
Section titled “Constants”import { DEFAULT_KIOSK_SETTINGS } from "@thebookingkit/core";
// Full default KioskSettings object:// {// defaultView: "day",// blockDensity: "standard",// colorCoding: "status",// autoLockMinutes: 5,// pinHash: "",// showWalkInSidebar: true,// slotHeightPx: 48,// dayStartHour: 6,// dayEndHour: 22,// compactFields: { customerName: true, serviceName: true, ... },// detailFields: { customerName: true, customerEmail: true, ... },// }Functions
Section titled “Functions”import { validateKioskSettings, resolveKioskSettings, findConflicts, canReschedule, describeConflicts, validateReschedule, validateBreakBlock, breakBlockToOverride, resolveKioskProviders,} from "@thebookingkit/core";
// Validate a partial settings object before savingconst { valid, errors } = validateKioskSettings({ slotHeightPx: 10 });// => { valid: false, errors: ["slotHeightPx must be between 20 and 200."] }
// Merge provider settings over org defaultsconst settings = resolveKioskSettings(providerSettings, orgDefaults);
// Find overlapping bookings for a proposed time rangeconst conflicts = findConflicts(existingBookings, newStart, newEnd, excludeBookingId);
// Check whether a booking status allows reschedulingconst reschedulable = canReschedule("confirmed"); // trueconst notReschedulable = canReschedule("completed"); // false
// Human-readable conflict summaryconst message = describeConflicts(conflicts);// => "2 conflicts: Jane Smith (2:00 PM – 3:00 PM), Walk-in (3:15 PM – 3:45 PM)"
// Full reschedule validationconst result = validateReschedule( "confirmed", rules, overrides, existingBookings, newStart, newEnd, bufferBefore, bufferAfter,);// => { valid: false, reason: "conflict", conflictDetails: { bookingId, startsAt, endsAt } }
// Validate a break block before creating itconst check = validateBreakBlock( { title: "Lunch", startTime, endTime, blockType: "break", recurring: false }, existingBookings,);// => { valid: true, conflictingBookings: [] }
// Convert a break block to an availability override for DB storageconst override = breakBlockToOverride(block, "America/New_York");// => { date, isUnavailable: true, startTime: "12:00", endTime: "13:00" }
// Filter and annotate providers for the resource viewconst providers = resolveKioskProviders(allProviders, ["prov-1", "prov-2"]);// => providers with visible: true/false based on visibleIds| Function | Signature | Description |
|---|---|---|
validateKioskSettings |
(settings) |
Validate a partial KioskSettings object; returns { valid, errors[] } |
resolveKioskSettings |
(providerSettings?, orgDefaults?) |
Deep-merge provider and org settings over DEFAULT_KIOSK_SETTINGS; provider values win |
findConflicts |
(existing, newStart, newEnd, excludeId?) |
Detect bookings that overlap [newStart, newEnd) using half-open interval math; inactive statuses are excluded automatically |
canReschedule |
(status) |
Return true if status is "confirmed" or "pending" |
describeConflicts |
(conflicts, formatTime?) |
Produce a human-readable conflict summary string; optionally supply a custom time formatter |
validateReschedule |
(bookingStatus, rules, overrides, existingBookings, newStart, newEnd, bufferBefore?, bufferAfter?) |
Full reschedule pre-flight: checks status, availability, and buffer conflicts; returns RescheduleValidation |
validateBreakBlock |
(block, existingBookings) |
Check a break/block against active bookings; returns { valid, conflictingBookings } |
breakBlockToOverride |
(block, providerTimezone?) |
Convert a BreakBlockInput to an AvailabilityOverrideInput ready for DB storage; defaults timezone to "UTC" — always pass the provider’s actual timezone |
resolveKioskProviders |
(providers, visibleIds?) |
Annotate each provider with visible: true/false; if visibleIds is omitted, all providers are visible |
RescheduleValidation reasons
Section titled “RescheduleValidation reasons”reason |
Meaning |
|---|---|
"conflict" |
Proposed slot overlaps an existing booking |
"buffer_conflict" |
Proposed slot falls inside another booking’s buffer window |
"outside_availability" |
Proposed slot is not within the provider’s availability rules |
"blocked_date" |
An override marks the date as unavailable |
"invalid_status" |
The booking’s current status does not permit rescheduling |
Error Classes
Section titled “Error Classes”All error classes carry a code string property that can be used for programmatic
error handling without string-matching the message.
import { // Core booking errors BookingConflictError, ResourceUnavailableError, SerializationRetryExhaustedError, UnauthorizedError, ForbiddenError, // Parsing & validation InvalidRRuleError, InvalidTimezoneError, EventTypeValidationError, PaymentValidationError, EmbedConfigError, RoutingFormValidationError, // Seats & recurring SeatError, RecurringBookingError, // Walk-in queue WalkInsDisabledError, QueueEntryNotFoundError, InvalidQueueTransitionError,} from "@thebookingkit/core";| Class | code |
Additional properties | Thrown when |
|---|---|---|---|
BookingConflictError |
BOOKING_CONFLICT |
— | DB EXCLUDE constraint fires; slot was taken by a concurrent booking |
ResourceUnavailableError |
RESOURCE_UNAVAILABLE |
reason: "no_capacity" | "no_matching_type" | "all_booked" |
No resource can be assigned to the booking |
SerializationRetryExhaustedError |
SERIALIZATION_RETRY_EXHAUSTED |
— | All serializable-transaction retries failed (SQLSTATE 40001 × 3) |
UnauthorizedError |
UNAUTHORIZED |
statusCode: 401 |
Request carries no valid session or token |
ForbiddenError |
FORBIDDEN |
statusCode: 403 |
Session exists but user lacks permission for the resource |
InvalidRRuleError |
INVALID_RRULE |
— | parseRecurrence receives a malformed or unsupported RRULE string |
InvalidTimezoneError |
INVALID_TIMEZONE |
— | An unrecognised IANA timezone identifier is passed to a timezone utility |
EventTypeValidationError |
EVENT_TYPE_VALIDATION |
— | validateEventType finds invalid or conflicting event type configuration |
PaymentValidationError |
PAYMENT_VALIDATION |
— | Cancellation policy or payment config fails validation |
EmbedConfigError |
EMBED_CONFIG |
— | validateEmbedConfig rejects invalid embed configuration |
RoutingFormValidationError |
ROUTING_FORM_VALIDATION |
— | validateRoutingForm or validateRoutingResponses fails |
SeatError |
SEAT_ERROR |
— | Seat reservation violates capacity constraints |
RecurringBookingError |
RECURRING_BOOKING |
— | Recurring series configuration or occurrence generation fails |
WalkInsDisabledError |
WALK_INS_DISABLED |
— | Walk-in is attempted while provider’s acceptingWalkIns flag is false |
QueueEntryNotFoundError |
QUEUE_ENTRY_NOT_FOUND |
— | Referenced walk-in queue entry ID does not exist |
InvalidQueueTransitionError |
INVALID_QUEUE_TRANSITION |
— | validateQueueTransition called with a disallowed from → to pair |