{"openapi":"3.1.0","info":{"title":"Durable Booking API","version":"1.1.0","description":"Public read-only API for checking booking availability, listing services, and finding available time slots on a Durable website.\n\nTypical agent workflow: 1) GET /api/booking/availability to confirm that bookings are open; 2) GET /api/booking/services to list services and read each service's `preBookingQuestions`; 3) GET /api/booking/time-slots?serviceId=...&date=YYYY-MM-DD to get available `slots`; 4) return the provided `bookingUrl` to the customer, optionally appending supported customer prefill query parameters. The customer must click “Confirm booking” in the widget. This API does not create bookings directly.\n\nAll responses are in JSON. Time fields are UTC ISO 8601 strings.","x-booking-deep-link":{"description":"Every GET response includes pre-built booking deep links so agents can provide a URL to a user without having to create it manually. The link opens the booking widget and pre-fills the specified state. The user is taken to the deepest valid step and clicks \"Confirm booking\" to submit—the link never automatically books the reservation.","urlTemplate":"https://{domain}/?booking=open&service={serviceId}&date={YYYY-MM-DD}&slot={UTC-ISO}&firstName=...&email=...","params":{"booking":{"type":"string","enum":["open"],"description":"Opens the widget."},"service":{"type":"integer","description":"Preselects the service; proceeds to step 2."},"date":{"type":"string","format":"date","description":"Preselects the date in step 2 (YYYY-MM-DD, business time zone)."},"slot":{"type":"string","format":"date-time","description":"Preselects a time slot; proceeds to step 3 after the slot loads."},"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string","format":"email"},"phone":{"type":"string","description":"Maps to phoneNumber in the customer form."},"address":{"type":"string"},"answers":{"type":"string","description":"URL-encoded JSON array of {question, answer} that matches the service's preBookingQuestions."}},"stepSemantics":"no params -> widget open at step 1; service only -> step 2; service+date -> step 2 with date selected; service+date+slot -> step 3 after slot loads; all fields -> step 3 with Confirm enabled. Never auto-submits."}},"servers":[{"url":"/","description":"Current Durable website host."}],"tags":[{"name":"Booking","description":"Read-only booking discovery endpoints. Return a bookingUrl to the customer; bookings are confirmed in the website widget."}],"paths":{"/api/booking/availability":{"get":{"tags":["Booking"],"operationId":"getBookingAvailability","summary":"Check if a business accepts online bookings","description":"Returns whether the business has booking enabled (active plan, calendar, at least one service, and site widget displayed).","parameters":[{"name":"businessId","in":"query","required":false,"schema":{"type":"integer"},"description":"Optional. The server resolves the business name from the request's Host header. If provided, the resolved host name must match; otherwise, the request is rejected with a 400 error."}],"responses":{"200":{"description":"Availability Status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AvailabilityResponse"},"examples":{"available":{"summary":"Bookings available","value":{"available":true,"hasCalendar":true,"hasServices":true,"calendarId":123,"timezone":"America/Los_Angeles","minimumLeadTimeMinutes":60,"maximumAdvanceTimeMinutes":43200,"bookingWidgetUrl":"https://example.com/?booking=open"}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"error":{"summary":"businessId must be a positive integer","value":{"error":"businessId must be a positive integer"}}}}}},"404":{"description":"Business, service, or booking resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"error":{"summary":"Not found","value":{"error":"Not found"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"error":{"summary":"Internal server error","value":{"error":"Internal server error"}}}}}}}}},"/api/booking/services":{"get":{"tags":["Booking"],"operationId":"listBookingServices","summary":"List available services for a business","parameters":[{"name":"businessId","in":"query","required":false,"schema":{"type":"integer"},"description":"Optional. The server resolves the business name from the request's Host header. If provided, the resolved host name must match; otherwise, the request is rejected with a 400 error."}],"responses":{"200":{"description":"List of Services","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServicesResponse"},"examples":{"services":{"summary":"Service list","value":{"services":[{"id":456,"name":"Consultation","description":"Initial consultation.","durationMinutes":60,"duration":"1 hour","price":null,"startIncrement":15,"bufferTime":0,"preBookingQuestions":[{"question":"What would you like help with?","answerType":"multiple-lines","required":true}],"phoneNumberRequired":true,"location":{"type":"google_meet"},"requiresPayment":false,"bookingUrl":"https://example.com/?booking=open&service=456"}],"bookingWidgetUrl":"https://example.com/?booking=open","currency":"USD"}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"error":{"summary":"businessId must be a positive integer","value":{"error":"businessId must be a positive integer"}}}}}},"404":{"description":"Business, service, or booking resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"error":{"summary":"Not found","value":{"error":"Not found"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"error":{"summary":"Internal server error","value":{"error":"Internal server error"}}}}}}}}},"/api/booking/time-slots":{"get":{"tags":["Booking"],"operationId":"getBookingTimeSlots","summary":"Get available time slots for a date and service","description":"Times are returned as UTC ISO strings. The `date` parameter is interpreted as a local day in `customerTimezone` when provided; otherwise, it is interpreted in the business calendar time zone.","parameters":[{"name":"businessId","in":"query","required":false,"schema":{"type":"integer"},"description":"Optional. The server resolves the business name from the request's Host header. If provided, the resolved host name must match; otherwise, the request is rejected with a 400 error."},{"name":"date","in":"query","required":true,"schema":{"type":"string","format":"date","example":"2026-05-15"},"description":"ISO date (YYYY-MM-DD). With `customerTimezone`, this is the customer's local date. Without `customerTimezone`, this is the business/calendar local date."},{"name":"serviceId","in":"query","required":true,"schema":{"type":"integer"},"description":"The service ID from /api/booking/services."},{"name":"customerTimezone","in":"query","required":false,"schema":{"type":"string","example":"America/Los_Angeles"},"description":"IANA time zone. When provided, only slots within the requested date in this customer's local time zone are returned. When omitted, slots are filtered by the business/calendar time zone."}],"responses":{"200":{"description":"Available time slots","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeSlotsResponse"},"examples":{"slots":{"summary":"Available slots","value":{"slots":[{"id":"2026-05-15T16:00:00.000Z","startTime":"2026-05-15T16:00:00.000Z","endTime":"2026-05-15T17:00:00.000Z","available":true,"bookingUrl":"https://example.com/?booking=open&service=456&date=2026-05-15&slot=2026-05-15T16%3A00%3A00.000Z"}],"timezone":"America/Los_Angeles"}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"error":{"summary":"businessId must be a positive integer","value":{"error":"businessId must be a positive integer"}}}}}},"404":{"description":"Business, service, or booking resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"error":{"summary":"Not found","value":{"error":"Not found"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"error":{"summary":"Internal server error","value":{"error":"Internal server error"}}}}}}}}}},"components":{"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"AvailabilityResponse":{"type":"object","required":["available","hasCalendar","hasServices"],"properties":{"available":{"type":"boolean","description":"True if the business can currently accept bookings through the website widget (plan + calendar + services + widget displayed)."},"hasCalendar":{"type":"boolean"},"hasServices":{"type":"boolean"},"calendarId":{"type":["integer","null"]},"timezone":{"type":["string","null"]},"isDemoMode":{"type":"boolean"},"minimumLeadTimeMinutes":{"type":"integer"},"maximumAdvanceTimeMinutes":{"type":"integer"},"bookingWidgetUrl":{"type":"string","format":"uri","description":"Pre-built deep link that opens the booking widget (?booking=open). Use this as the booking entry point to redirect users to."}}},"ServicesResponse":{"type":"object","required":["services"],"properties":{"services":{"type":"array","items":{"$ref":"#/components/schemas/Service"}},"isDemoMode":{"type":"boolean"},"bookingWidgetUrl":{"type":"string","format":"uri","description":"Pre-built deep link that opens the booking widget with no service preselected (?booking=open)."},"currency":{"type":"string","description":"ISO 4217 currency code from the business's invoicing settings.","example":"USD"}}},"Service":{"type":"object","required":["id","name","description","durationMinutes","duration","price","startIncrement","bufferTime","preBookingQuestions","requiresPayment"],"properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string","description":"May be an empty string."},"durationMinutes":{"type":"integer"},"duration":{"type":"string","description":"Formatted duration string, e.g. '1 hour'."},"price":{"type":["number","null"],"description":"Null when the service has no set price; 0 when it is explicitly free."},"startIncrement":{"type":"integer","description":"Slot start interval in minutes (e.g. 15 = slots offered on every quarter hour)."},"bufferTime":{"type":"integer","description":"Buffer minutes appended after each booking."},"preBookingQuestions":{"type":"array","description":"Custom questions the business wants the customer to answer before booking. Agents can encode answers in the deep link `answers` query param, but the customer still reviews and confirms in the widget.","items":{"$ref":"#/components/schemas/PreBookingQuestion"}},"phoneNumberRequired":{"type":"boolean","description":"When true, the booking widget form requires the customer phone number before confirmation."},"location":{"$ref":"#/components/schemas/ServiceLocation"},"bookingUrl":{"type":"string","format":"uri","description":"Pre-built deep link that opens the widget with this service pre-selected (?booking=open&service={id}). Return this URL to users to let them book this service."},"requiresPayment":{"type":"boolean","description":"True when picking this service will surface a payment step before confirmation."},"cancellationPolicy":{"type":"object","additionalProperties":true,"description":"Cancellation policy details for the service, when configured."}}},"PreBookingQuestion":{"type":"object","required":["question","answerType"],"properties":{"question":{"type":"string"},"answerType":{"type":"string","enum":["one-line","multiple-lines","radio","checkbox","dropdown"]},"required":{"type":"boolean"},"options":{"type":"array","description":"Provided for radio / checkbox / dropdown answer types.","items":{"$ref":"#/components/schemas/PreBookingOption"}}}},"PreBookingOption":{"type":"object","required":["id","label"],"properties":{"id":{"type":"string"},"label":{"type":"string"}}},"ServiceLocation":{"description":"How the booking will be conducted. The `type` field discriminates the variant. May be omitted when no location has been configured.","oneOf":[{"$ref":"#/components/schemas/ProvidedPhoneLocation"},{"$ref":"#/components/schemas/AskPhoneLocation"},{"$ref":"#/components/schemas/ProvidedAddressLocation"},{"$ref":"#/components/schemas/AskAddressLocation"},{"$ref":"#/components/schemas/GoogleMeetLocation"}],"discriminator":{"propertyName":"type","mapping":{"provided_phone":"#/components/schemas/ProvidedPhoneLocation","ask_phone":"#/components/schemas/AskPhoneLocation","provided_address":"#/components/schemas/ProvidedAddressLocation","ask_address":"#/components/schemas/AskAddressLocation","google_meet":"#/components/schemas/GoogleMeetLocation"}}},"ProvidedPhoneLocation":{"type":"object","required":["type","phoneNumber"],"additionalProperties":false,"properties":{"type":{"type":"string","const":"provided_phone"},"phoneNumber":{"type":"string"}}},"AskPhoneLocation":{"type":"object","required":["type"],"additionalProperties":false,"properties":{"type":{"type":"string","const":"ask_phone"}}},"ProvidedAddressLocation":{"type":"object","required":["type","address"],"additionalProperties":false,"properties":{"type":{"type":"string","const":"provided_address"},"address":{"type":"string"}}},"AskAddressLocation":{"type":"object","required":["type"],"additionalProperties":false,"properties":{"type":{"type":"string","const":"ask_address"}}},"GoogleMeetLocation":{"type":"object","required":["type"],"additionalProperties":false,"properties":{"type":{"type":"string","const":"google_meet"}}},"TimeSlotsResponse":{"type":"object","required":["slots"],"properties":{"slots":{"type":"array","items":{"$ref":"#/components/schemas/TimeSlot"}},"timezone":{"type":"string","description":"IANA timezone of the calendar the slots were calculated in."},"message":{"type":"string"},"isDemoMode":{"type":"boolean"}}},"TimeSlot":{"type":"object","required":["id","startTime","endTime","available"],"properties":{"id":{"type":"string"},"startTime":{"type":"string","format":"date-time"},"endTime":{"type":"string","format":"date-time"},"available":{"type":"boolean"},"bookingUrl":{"type":"string","format":"uri","description":"Pre-built deep link for this specific slot (?booking=open&service={id}&date={date}&slot={startTime}). Only present on available slots."}}}}}}