@proximityinsight/booking-sdk
v2.0.2
Published
TypeScript SDK for the Proximity Booking Widget — scoped to /v1/booking/*
Downloads
990
Readme
@proximityinsight/booking-sdk
A TypeScript SDK for embedding appointment booking into your website. Fully typed, scoped to the guest booking endpoints (/v1/booking/*).
Install
npm install @proximityinsight/booking-sdkRequirements: Node.js 18+ (or any runtime with native fetch)
Prerequisites
Before integrating, ensure you have the following:
| Item | Description |
|------|-------------|
| Tenant ID | Your unique tenant identifier, provided during onboarding |
| API Base URL | https://{environment}.proximityinsight.com — environment provided during onboarding |
| Domain Registration | Your website domain must be registered for access — contact support |
Quick Start
import { createBookingClient } from '@proximityinsight/booking-sdk';
const client = createBookingClient({
baseUrl: 'https://{environment}.proximityinsight.com',
});
// 1. Start a guest session
const { data: session } = await client.POST('/v1/booking/session', {
body: { tenantId: 'YOUR_TENANT_ID' },
});
// 2. Attach the token to all subsequent requests
client.use({
async onRequest({ request }) {
request.headers.set('Authorization', `Bearer ${session.token}`);
return request;
},
});
// 3. Browse locations
const { data: locations } = await client.GET('/v1/booking/locations');Authentication
All requests require a guest session token. No user credentials are needed.
Getting a Token
Create a session by calling the session endpoint with your tenant identifier:
const { data: session } = await client.POST('/v1/booking/session', {
body: { tenantId: 'YOUR_TENANT_ID' },
});
// { token: "eyJhbG...", expiresInSeconds: 900 }The token is valid for 15 minutes.
Domain Registration
Requests to the session endpoint are validated against your registered domains. Your domain must be registered before you can obtain a token. Contact support to add or update your allowed domains.
Attaching the Token
Use the .use() middleware to include the token on every request:
let currentToken = session.token;
client.use({
async onRequest({ request }) {
request.headers.set('Authorization', `Bearer ${currentToken}`);
return request;
},
});Refreshing a Session
Tokens cannot be extended. When a token expires, create a new session:
const { data: newSession } = await client.POST('/v1/booking/session', {
body: { tenantId: 'YOUR_TENANT_ID' },
});
currentToken = newSession.token;We recommend creating a new session proactively before the 15-minute window elapses to avoid failed requests.
Booking Flow
A typical booking follows these steps in order.
1. Load Configuration
Retrieve widget settings that control the booking experience.
const { data: config } = await client.GET('/v1/booking/config');Returns configuration for controlling widget behaviour, such as enabled features, display options, and booking rules.
2. Browse Locations
Fetch bookable locations, optionally filtered by proximity or product availability.
const { data: locations } = await client.GET('/v1/booking/locations', {
params: {
query: { lat: 51.5074, lng: -0.1278, productId: 'prod_001' },
},
});Pass lat and lng to sort results by distance. productId filters to locations that offer that product.
3. Browse Products
Fetch bookable products, optionally scoped to a specific location.
const { data: products } = await client.GET('/v1/booking/products', {
params: {
query: { locationId: 'loc_001' },
},
});4. Browse Staff
Fetch available staff at a location, optionally filtered by product.
const { data: tenantUsers } = await client.GET('/v1/booking/tenant-users', {
params: {
query: { locationId: 'loc_001', productId: 'prod_001' },
},
});locationId is required. productId is optional and narrows results to staff qualified for that product.
5. Check Available Dates
Retrieve dates that have availability within the booking window.
const { data: dates } = await client.GET('/v1/booking/availability/dates', {
params: {
query: {
productId: 'prod_001',
locationId: 'loc_001',
tenantUserId: 'staff_001',
},
},
});productId and locationId are required. tenantUserId is optional. The response includes the booking window boundaries.
6. Check Available Slots
Retrieve time slots for a specific date.
const { data: slots } = await client.GET('/v1/booking/availability/slots', {
params: {
query: {
productId: 'prod_001',
date: '2026-04-15',
locationId: 'loc_001',
tenantUserId: 'staff_001',
},
},
});productId, locationId, and date are required. tenantUserId is optional.
7. Hold Appointment
Create a temporary hold on the selected slot. The hold expires after 10 minutes by default (tenant-configurable).
const { data: appointment } = await client.POST('/v1/booking/appointments', {
body: {
locationId: 'loc_001',
productId: 'prod_001',
tenantUserId: 'staff_001',
date: '2026-04-15',
startTime: '10:00:00',
},
});8. Create or Match Customer
Submit customer details. By default the system matches an existing record by email and creates a new one if no match is found. Phone-based matching is available as an opt-in tenant configuration.
const { data: customerRecord } = await client.POST('/v1/booking/customers', {
body: {
firstName: 'Jane',
lastName: 'Doe',
email: '[email protected]',
phone: '+44 7700 900000',
},
});
// Returns: { customerId: "..." }9. Confirm Appointment
Attach the customer to the held appointment and confirm it. This will fail if the hold has expired.
const { data: confirmed } = await client.POST(
'/v1/booking/appointments/{id}/confirm',
{
params: { path: { id: appointment.appointmentId } },
body: { customerId: customerRecord.customerId },
},
);If the hold has expired, start again from step 7.
10. Release a Hold
Cancel a held appointment before confirmation to free the slot.
await client.DELETE('/v1/booking/appointments/{id}', {
params: { path: { id: appointment.appointmentId } },
});
// 204 No ContentManaging Appointments
Get Appointment Detail
const { data } = await client.GET('/v1/booking/appointments/{id}', {
params: { path: { id: 'apt_001' } },
});Update Appointment
Send only the fields you want to change. The server validates availability before applying.
const { data, error } = await client.PATCH('/v1/booking/appointments/{id}', {
params: { path: { id: 'apt_001' } },
body: {
date: '2026-04-16',
startTime: '14:00:00',
tenantUserId: 'staff_002',
},
});
if (error) {
// Handle validation or availability conflict
}Cancel Appointment
Cancellation is permanent. Ensure confirmation is collected from the end user before calling this endpoint.
await client.POST('/v1/booking/appointments/{id}/cancel', {
params: { path: { id: 'apt_001' } },
body: { cancellationReason: 'Customer requested cancellation' },
});Translations
Retrieve localised UI strings for multi-language support.
const { data: strings } = await client.GET('/v1/booking/translations');Strings are managed per-tenant in the Proximity platform.
Using Types
All request and response types are exported from the SDK.
import type { Schemas } from '@proximityinsight/booking-sdk';
type BookingProduct = Schemas['BookingProductResponse'];
type BookingLocation = Schemas['BookingLocationResponse'];
type BookingTenantUser = Schemas['BookingTenantUserResponse'];
type BookingTimeSlot = Schemas['BookingTimeSlot'];
type AppointmentHold = Schemas['BookingAppointmentHoldResponse'];
type AppointmentConfirm = Schemas['BookingAppointmentConfirmResponse'];
type AppointmentDetail = Schemas['BookingAppointmentDetailResponse'];
type CancelResult = Schemas['BookingAppointmentCancelResponse'];
type AvailableDates = Schemas['BookingAvailableDatesResponse'];
type AvailableSlots = Schemas['BookingAvailableSlotsResponse'];
type BookingCustomer = Schemas['BookingCustomerResponse'];
type BookingConfig = Schemas['BookingConfigResponse'];Error Handling
Every SDK method returns { data, error, response }. On a non-2xx response, data is undefined, error contains the parsed response body, and response is the standard Response object. Branch on response.status -- it is the authoritative, type-safe source of the HTTP status code. The error payload follows the API's error schema (typically { error: { statusCode, name, message } } for LoopBack endpoints).
const { data, error, response } = await client.POST('/v1/booking/appointments', {
body: appointmentRequest,
});
if (error) {
switch (response.status) {
case 400:
// Validation failure — missing or invalid fields
showValidationError(error.error?.message);
break;
case 401:
// Session expired — create a new session
redirectToSessionStart();
break;
case 409:
// Slot no longer available — prompt for a different time
showSlotUnavailable();
break;
default:
showGenericError();
}
return;
}
// data is fully typed
console.log(data.appointmentId);Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | /v1/booking/session | Start a new booking session |
| GET | /v1/booking/config | Retrieve booking configuration |
| GET | /v1/booking/locations | List available locations |
| GET | /v1/booking/products | List bookable products |
| GET | /v1/booking/tenant-users | List available staff members |
| GET | /v1/booking/availability/dates | Get available dates |
| GET | /v1/booking/availability/slots | Get available time slots |
| POST | /v1/booking/customers | Create or match a customer |
| POST | /v1/booking/appointments | Hold an appointment slot |
| GET | /v1/booking/appointments/{id} | Get appointment details |
| PATCH | /v1/booking/appointments/{id} | Update an appointment |
| POST | /v1/booking/appointments/{id}/confirm | Confirm a held appointment |
| POST | /v1/booking/appointments/{id}/cancel | Cancel an appointment |
| POST | /v1/booking/appointments/auto-assign | Create a new held appointment with an auto-selected staff member (original hold unchanged) |
| DELETE | /v1/booking/appointments/{id} | Release a held appointment |
| GET | /v1/booking/translations | Retrieve UI translation strings |
Support
For help with integration, contact your Proximity account manager:
- Tenant ID and API base URL
- Domain registration
- General integration questions
