# Qssly Headless Booking API — LLM Context File > This file is optimized for AI coding assistants (Claude, Gemini, ChatGPT, Copilot). > Paste this into your AI's system prompt to build booking integrations. ## Key Links & Resources - [Developer Hub](https://qssly.com/developer-hub): Full API reference, code snippets, system prompts, and webhooks documentation. - [Portal Hub](https://qssly.com/portal-hub): Tenant administration, business portals, and setup guides. - [Features Overview](https://qssly.com/features): Complete list of scheduling features, isolation rules, and integrations. - [Full LLM Specification](https://qssly.com/llms-full.txt): Comprehensive API specification for LLMs. ## What is Qssly? Qssly is a headless multi-tenant booking engine API. You build the UI; Qssly handles availability computation, scheduling, double-booking prevention, payments, and webhooks. ## Authentication - API Key Auth: All tenant-scoped requests require `X-API-Key: qs_live_` in the headers. - Bearer JWT Auth: Rescheduling, cancellation, and staff endpoints require `Authorization: Bearer `. - JWT Acquisition: * POST `/auth/login` -> Body: `{ "email": "...", "password": "..." }` -> Returns `{ "access_token": "...", "token_type": "bearer" }` * POST `/auth/otp/request` -> Body: `{ "email": "...", "phone": "..." }` -> Triggers OTP email * POST `/auth/otp/verify` -> Body: `{ "email": "...", "code": "..." }` (6-digit OTP code) -> Returns `{ "access_token": "...", "token_type": "bearer" }` - JWT Claims (HS256): `sub` (User UUID), `tenant_id` (Tenant UUID), `role` (SUPER_ADMIN | TENANT_ADMIN | TENANT_STAFF | END_CUSTOMER), `type` (access | refresh), `exp` (expiry). - Role Enforcement: `END_CUSTOMER` can only reschedule/cancel bookings they own (authenticated email matches `booking.customer_email`). ## Data Hierarchy - Tenant → Your business account (auto-resolved from API key) - Location → Branch with IANA timezone (e.g. "Asia/Kolkata") - Resource → Bookable entity (room, doctor, court, vehicle). Has capacity field. - Service → Rate package with price, currency, duration_minutes, buffer_minutes, payment_type (FULL_ADVANCE | PARTIAL_ADVANCE | PAY_LATER), advance_payment_amount, cancellation_cutoff_hours - Resource-Service Binding → Links which services a resource can offer - Availability Rule → Weekly time window when resource is bookable (day_of_week 0-6 where 0=Monday, start_time, end_time) - Booking → Reservation with start_time, end_time, customer_email, status (CONFIRMED | PENDING_PAYMENT | CANCELLED | NO_SHOW | COMPLETED) ## API Endpoints (Base: /api/v1) ### Read Endpoints (API Key auth) GET /locations → { items: [{ id, name, timezone }], total, page, page_size, pages } GET /resources?location_id={uuid} → { items: [{ id, name, location_id, capacity, metadata_schema }], total, page, page_size, pages } GET /services → { items: [{ id, name, duration_minutes, buffer_minutes, price, currency, payment_type, advance_payment_amount, cancellation_cutoff_hours }], total, page, page_size, pages } GET /availability?resource_id={uuid}&service_id={uuid}&date=YYYY-MM-DD → { resource_id, service_id, timezone, slots: [{ date, times: ["HH:MM"] }] } GET /availability?resource_id={uuid}&service_id={uuid}&start_date=YYYY-MM-DD&end_date=YYYY-MM-DD → same shape, multiple dates (max 31 days) GET /bookings?resource_id={uuid}&date=YYYY-MM-DD&status=CONFIRMED → [BookingResponse] GET /bookings/{id} → BookingResponse (includes `amount_paid`, `balance_due`, `location_timezone`, and `provider_transaction_id` if loaded) ### Write Endpoints (API Key auth) POST /bookings → { resource_id, service_id, start_time, end_time, customer_email, customer_phone?, booking_metadata? } → Returns { id, status, payment_status, total_amount, amount_paid, balance_due, location_timezone, checkout_url?, provider_transaction_id? } → If checkout_url is present, redirect user to it for payment POST /bookings/{id}/cancel → { cancellation_reason? } (Also respects Bearer JWT auth for role enforcement) ### Admin & Staff Endpoints (Bearer JWT required) PATCH /bookings/{id} → { start_time, end_time } (reschedule; respects Bearer JWT auth for role enforcement) POST /bookings/{id}/no-show POST /resource-services → { resource_id, service_id } (bind service to resource) POST /availability-rules → { resource_id, day_of_week: 0-6, start_time: "HH:MM:SS", end_time: "HH:MM:SS", is_available: true } GET /availability-rules?resource_id={uuid} ## Slot Generation Logic 1. Availability rules define weekly time windows (e.g. Monday 09:00–17:00) 2. System divides windows into slots: each slot = duration_minutes + buffer_minutes apart 3. Existing bookings are subtracted from available slots 4. Returned times are available slot start times ## Key Rules - start_time must be before end_time - Double bookings are strictly prevented by the system. - All datetimes in booking requests are ISO 8601 UTC. - **Timezone Warning**: Slot times returned from `/availability` are in the location's local timezone (e.g., `Asia/Kolkata`). You MUST convert these local times to UTC before sending booking requests (e.g., 09:00:00 local time in Kolkata (IST, UTC+5:30) is 03:30:00 UTC). - buffer_minutes creates gaps between bookings (NOT added to booking end_time) - Errors return { "detail": "message" } - Double-booking conflicts (409 Conflict) return a list of overlapping dates: { "detail": "...", "conflicting_dates": ["YYYY-MM-DD", ...] } - Status codes: 400 (validation), 401 (auth), 404 (not found), 409 (conflict/double-booking), 422 (invalid payload) ## Hotel Booking Example Service: duration_minutes=1440 (24h), buffer_minutes=180 (3h cleanup), payment_type=FULL_ADVANCE Check availability: GET /availability?resource_id=ROOM&service_id=STAY&start_date=2026-07-01&end_date=2026-07-04 (returns slots in Asia/Kolkata) *Checkout Date Nuance*: Hotel checkout dates are available for check-in by the next guest. Exclude the checkout date slot on the client side when booking. Book: POST /bookings { start_time: "2026-07-01T08:30:00Z", end_time: "2026-07-04T05:30:00Z", customer_email: "guest@email.com" } (Converted to UTC check-in at 2 PM IST and checkout at 11 AM IST) Result: { status: "PENDING_PAYMENT", payment_status: "PENDING", total_amount: 7500.0, amount_paid: 0.0, balance_due: 7500.0, location_timezone: "Asia/Kolkata", checkout_url: "https://..." } ## Doctor Appointment Example Service: duration_minutes=30, buffer_minutes=10, payment_type=PAY_LATER Check slots: GET /availability?resource_id=DOCTOR&service_id=CONSULT&date=2026-07-15 Slots generated every 40 minutes: ["09:00", "09:40", "10:20", ...] Book: POST /bookings { start_time: "2026-07-15T03:30:00Z", end_time: "2026-07-15T04:00:00Z", customer_email: "patient@email.com" } (Converted from 09:00 AM IST to 03:30 AM UTC) Result: { status: "CONFIRMED", payment_status: "PENDING", total_amount: 500.0, amount_paid: 0.0, balance_due: 500.0, location_timezone: "Asia/Kolkata", checkout_url: null } ## Webhooks POST to your configured URL with headers: X-Qssly-Signature: sha256= X-Qssly-Idempotency-Key: :: Body: { event: "booking.confirmed", timestamp: "ISO8601", data: { id, resource_id, service_id, start_time, end_time, status, payment_status, total_amount, amount_paid, balance_due, location_timezone, customer_email, metadata, checkout_url, provider_transaction_id } } Verify: HMAC-SHA256(webhook_secret, JSON.stringify(body, separators=(',',':'), sort_keys=True)) - Events: `booking.confirmed`, `booking.cancelled`, `booking.rescheduled`, `booking.no_show`, `booking.completed`, `booking.created` - Retry Policy: 5 retries (6 attempts total) with exponential backoff (`delay = 10 * 3^retry + random(0, 5)` seconds). - Failed payloads will be dropped after the maximum number of retries.