openapi: 3.0.3 info: title: Launch27 API description: >- Unofficial-but-real, actively used REST API for Launch27, a booking and scheduling platform for cleaning service businesses. Documented in a public Bitbucket wiki (https://bitbucket.org/awoo23/api-2.0/wiki/Home) linked from the launch27.com site footer, rather than in Launch27's first-party docs.launch27.com knowledge base. The API is multi-tenant: every client account has its own subdomain. This document models the current v2.1 surface (the deprecated v2.0 surface is not modeled). Not every endpoint mentioned in the wiki is represented here in full schema detail; refer to the wiki pages linked per operation for complete field-by-field documentation. version: '2.1' contact: name: API Evangelist email: kin@apievangelist.com servers: - url: https://{tenant}.launch27.com/v1 description: Production (per-tenant subdomain) variables: tenant: default: acme description: Launch27 client account subdomain. - url: https://{tenant}.l27.co/v1 description: Testing/sandbox (per-tenant subdomain) variables: tenant: default: acme-sandbox description: Launch27 sandbox account subdomain. tags: - name: Authentication description: Login and JWT bearer token issuance. - name: Account description: Account/tenant settings and configuration. - name: Booking Helpers description: Supporting data for building and pricing a booking form. - name: Policy description: Booking, reschedule, cancellation, and location policies. - name: Guest Booking description: Booking creation for non-logged-in customers. - name: Customer Bookings description: Authenticated customer-portal booking CRUD. security: - bearerAuth: [] paths: /login: post: operationId: login tags: - Authentication summary: Authenticate and obtain a JWT bearer token security: [] description: >- Authenticates a customer or staff user. Returns 401 on failure, 403 if the account is locked (5 invalid attempts within 1 minute locks the account for 5 minutes) or a 2FA token is required, 422 on schema errors. requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/LoginRequest' responses: '200': description: Authenticated successfully. content: application/json: schema: $ref: '#/components/schemas/LoginResponse' '401': $ref: '#/components/responses/Unauthorized' '403': description: Account locked out, or OTP token required. '422': $ref: '#/components/responses/ValidationError' /settings: get: operationId: getSettings tags: - Account summary: Get account settings security: - bearerAuth: [] - {} description: >- Returns account configuration. Response content varies by authentication state, user type, and feature availability - some fields (stripe_public_key, team_permissions, alerts, admin_id, pubsub_url, quickbooks_authorized) require authentication and/or a staff/team user type. responses: '200': description: Account settings. content: application/json: schema: $ref: '#/components/schemas/Settings' /booking/form: get: operationId: getBookingForm tags: - Booking Helpers summary: Get booking form setup security: [] parameters: - name: type in: query required: false description: >- Booking form type (requires features.new_booking_form_widget). schema: type: string enum: [cleaning, auto, lawn, carpet] responses: '200': description: Booking form headings, system fields, paragraphs, and appearance settings. content: application/json: schema: type: object additionalProperties: true /booking/services: get: operationId: getBookingServices tags: - Booking Helpers summary: Get services available for booking security: - bearerAuth: [] - {} parameters: - $ref: '#/components/parameters/BookingUuid' - $ref: '#/components/parameters/LocationId' responses: '200': description: List of available services. content: application/json: schema: type: array items: $ref: '#/components/schemas/Service' /booking/spots: post: operationId: getBookingSpots tags: - Booking Helpers summary: Get available spots for booking security: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SpotsRequest' responses: '200': description: Available spots by date (or grid + days if grid=true). content: application/json: schema: type: object additionalProperties: true /booking/location: post: operationId: getBookingLocation tags: - Booking Helpers summary: Resolve location for a booking address security: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/LocationRequest' responses: '200': description: Resolved location. content: application/json: schema: $ref: '#/components/schemas/Location' /booking/frequencies: get: operationId: getBookingFrequencies tags: - Booking Helpers summary: Get frequencies available for booking security: - bearerAuth: [] - {} parameters: - $ref: '#/components/parameters/BookingUuid' - $ref: '#/components/parameters/LocationId' responses: '200': description: List of configured frequencies. content: application/json: schema: type: array items: $ref: '#/components/schemas/Frequency' /booking/frequencies/{id}/next: post: operationId: getNextBookingDateForFrequency tags: - Booking Helpers summary: Get next recurring booking date for a frequency security: - bearerAuth: [] parameters: - name: id in: path required: true description: Frequency unique ID. schema: type: integer requestBody: required: true content: application/json: schema: type: object required: [date] properties: date: type: string description: 'YYYY-MM-DDTHH:MM:SS' responses: '200': description: Next recurring date. content: application/json: schema: type: object properties: date: type: string format: date-time '422': description: Schema error, or frequency is not recurring. /booking/custom_fields: get: operationId: getBookingCustomFields tags: - Booking Helpers summary: Get custom fields for booking security: - bearerAuth: [] - {} parameters: - $ref: '#/components/parameters/BookingUuid' responses: '200': description: List of custom fields. content: application/json: schema: type: array items: $ref: '#/components/schemas/CustomField' /booking/estimate_price: post: operationId: estimateBookingPrice tags: - Booking Helpers summary: Estimate price for a booking security: - bearerAuth: [] - {} requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PriceEstimateRequest' responses: '200': description: Price estimate. content: application/json: schema: $ref: '#/components/schemas/PriceEstimate' '401': $ref: '#/components/responses/Unauthorized' '403': description: Customer has no access to booking. /policy/booking: get: operationId: getNewBookingPolicy tags: - Policy summary: Get new booking policy security: [] responses: '200': description: New booking lead-time policy. content: application/json: schema: $ref: '#/components/schemas/LeadTimePolicy' /policy/reschedule: get: operationId: getRescheduleBookingPolicy tags: - Policy summary: Get booking reschedule policy security: - bearerAuth: [] responses: '200': description: Reschedule lead-time policy. content: application/json: schema: $ref: '#/components/schemas/LeadTimePolicy' /policy/cancellation: get: operationId: getCancellationPolicy tags: - Policy summary: Get booking cancellation policy security: - bearerAuth: [] responses: '200': description: Cancellation fee/percent and reason-required policy. content: application/json: schema: $ref: '#/components/schemas/CancellationPolicy' /policy/location: get: operationId: getLocationPolicy tags: - Policy summary: Get "prevent booking if location not found" policy security: [] responses: '200': description: Location policy. content: application/json: schema: type: object properties: prevent_booking: type: boolean prevent_booking_text: type: string /booking: post: operationId: createGuestBooking tags: - Guest Booking summary: Create a new booking as a non-logged-in customer security: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/GuestBookingRequest' responses: '200': description: Booking created. content: application/json: schema: $ref: '#/components/schemas/BookingCreatedResponse' '422': $ref: '#/components/responses/ValidationError' /customer/bookings: get: operationId: listCustomerBookings tags: - Customer Bookings summary: List active bookings for the logged-in customer parameters: - name: from in: query schema: type: string format: date - name: to in: query schema: type: string format: date - name: query in: query description: Free-text search across address, city, phone. schema: type: string - name: options in: query description: Comma-separated; completed, not_completed, with_feedback, without_feedback. schema: type: string - name: limit in: query schema: type: integer - name: offset in: query schema: type: integer - name: sort in: query schema: type: string enum: [asc, desc] default: desc responses: '200': description: List of bookings. content: application/json: schema: type: array items: $ref: '#/components/schemas/Booking' '401': $ref: '#/components/responses/Unauthorized' '403': description: User is not a customer user. post: operationId: createCustomerBooking tags: - Customer Bookings summary: Create a new booking as the logged-in customer requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CustomerBookingRequest' responses: '200': description: Booking created. content: application/json: schema: $ref: '#/components/schemas/BookingCreatedResponse' '401': $ref: '#/components/responses/Unauthorized' '422': $ref: '#/components/responses/ValidationError' /customer/bookings/count: get: operationId: countCustomerBookings tags: - Customer Bookings summary: Count active bookings for the logged-in customer responses: '200': description: Booking count. content: application/json: schema: type: object properties: count: type: integer '401': $ref: '#/components/responses/Unauthorized' '403': description: User is not a customer user. /customer/bookings/{id}: parameters: - name: id in: path required: true description: Booking unique ID. schema: type: integer get: operationId: getCustomerBooking tags: - Customer Bookings summary: Get a single booking's details responses: '200': description: Booking detail. content: application/json: schema: $ref: '#/components/schemas/Booking' '401': $ref: '#/components/responses/Unauthorized' '403': description: Customer has no access to booking. put: operationId: updateCustomerBooking tags: - Customer Bookings summary: Update or reschedule an existing booking requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateCustomerBookingRequest' responses: '200': description: Updated booking. content: application/json: schema: $ref: '#/components/schemas/Booking' '401': $ref: '#/components/responses/Unauthorized' '422': $ref: '#/components/responses/ValidationError' /customer/bookings/{id}/cancel: post: operationId: cancelCustomerBooking tags: - Customer Bookings summary: Cancel an existing booking parameters: - name: id in: path required: true description: Booking unique ID. schema: type: integer requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/CancelBookingRequest' responses: '200': description: Booking cancelled. '401': $ref: '#/components/responses/Unauthorized' '422': $ref: '#/components/responses/ValidationError' components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT description: >- JWT returned as `bearer` in the Login response. Sent as `Authorization: Bearer `. A legacy `email:token` form (Authorization: email:token) existed but was retired March 1, 2023. parameters: BookingUuid: name: booking_uuid in: query required: false description: Booking digest; required (and requires auth) when requesting for an existing booking. schema: type: string LocationId: name: location_id in: query required: false description: Unique ID of location; default location used if omitted. schema: type: integer responses: Unauthorized: description: Missing/invalid Authorization header, or invalid credentials. ValidationError: description: JSON schema or data validation error. content: application/json: schema: type: object additionalProperties: true schemas: LoginRequest: type: object required: [email, password] properties: email: type: string format: email password: type: string token: type: string description: 6-digit 2FA token, if enabled. LoginResponse: type: object properties: id: type: integer email: type: string type: type: string example: 'Tenant::Customer' first_name: type: string last_name: type: string bearer: type: string description: JWT bearer token. Settings: type: object description: See https://bitbucket.org/awoo23/api-2.0/wiki/Get_settings for full attribute documentation. additionalProperties: true properties: logo: type: string format: uri color: type: string name: type: string status: type: string enum: [trial, active, frozen, sleep] sandbox: type: boolean setup_complete: type: boolean timezone: type: string currency: type: object properties: code: type: string symbol: type: string country: type: object additionalProperties: true features: type: object additionalProperties: true payment_methods: type: object additionalProperties: true default_location: type: object properties: id: type: integer name: type: string pubsub_url: type: string description: >- wss:// notification subscription URL, present only when authenticated. Example: wss://pubsub.l27.co/v1/subscribe?channel=3yicbolh5nv4s8wv Service: type: object properties: id: type: integer name: type: string price: type: number commercial: type: boolean discount_by_frequency: type: boolean discount_by_code: type: boolean hourly: type: object nullable: true additionalProperties: true extras: type: array items: $ref: '#/components/schemas/Extra' pricing_parameters: type: array items: $ref: '#/components/schemas/PricingParameter' Extra: type: object properties: id: type: integer name: type: string price: type: number quantity_based: type: boolean recurring: type: boolean discount_by_frequency: type: boolean discount_by_code: type: boolean PricingParameter: type: object properties: id: type: integer name: type: string price: type: number quantity_minimum: type: integer quantity_maximum: type: integer SpotsRequest: type: object required: [date, days, mode] properties: date: type: string format: date days: type: integer minimum: 1 maximum: 7 mode: type: string enum: [new, reschedule] location_id: type: integer nullable: true grid: type: boolean nullable: true LocationRequest: type: object properties: address: type: string nullable: true city: type: string nullable: true state: type: string nullable: true zip: type: string nullable: true Location: type: object properties: id: type: integer name: type: string Frequency: type: object properties: id: type: integer interval: type: string description: "'o' = one time; otherwise a recurring interval pattern like 1w, 2m15d." name: type: string percent: type: number amount: type: number exclude_first: type: boolean default: type: boolean CustomField: type: object properties: id: type: integer code: type: string label: type: string control_type: type: string enum: [single_line, multi_line, radio_buttons, checkboxes, drop_down, plain_text, date, datetime, checkbox] value_required: type: boolean visible: type: boolean ordering: type: integer default_value: type: string options: type: array items: type: object additionalProperties: true ServiceSelection: type: object required: [id] properties: id: type: integer hourly: type: object nullable: true properties: quantity: type: integer minutes: type: integer extras: type: array nullable: true items: type: object required: [id, quantity] properties: id: type: integer quantity: type: integer recurring: type: boolean nullable: true pricing_parameters: type: array nullable: true items: type: object required: [id, quantity] properties: id: type: integer quantity: type: integer PriceEstimateRequest: type: object required: [service_date, frequency_id, services] properties: email: type: string format: email booking_uuid: type: string nullable: true original_booking_id: type: integer nullable: true service_date: type: string location_id: type: integer nullable: true frequency_id: type: integer services: type: array items: $ref: '#/components/schemas/ServiceSelection' discount_code: type: string nullable: true tip: type: number nullable: true tip_recurring: type: boolean nullable: true price_adjustment: type: number nullable: true description: Staff-only. final_price: type: number nullable: true description: Staff-only. PriceEstimate: type: object properties: services: type: number extras: type: number discount_amount: type: number discount_message: type: string giftcard_amount: type: number price_adjustment: type: number tax: type: object additionalProperties: true tip: type: number total: type: number next_total: type: number duration: type: integer price_for_frequency: type: number price_for_discount_by_code: type: number price_recurring_for_frequency: type: number LeadTimePolicy: type: object properties: custom: type: boolean unit: type: string enum: [hour, day] quantity: type: integer CancellationPolicy: type: object properties: custom: type: boolean unit: type: string enum: [hour, day] quantity: type: integer late_cancellation_fee: type: number late_cancellation_percent: type: number cancellation_reason_on: type: boolean GuestBookingRequest: type: object required: [user, address, frequency_id, service_date, arrival_window, services, payment_method] properties: location_id: type: integer nullable: true user: type: object required: [email, first_name, last_name] properties: email: type: string format: email first_name: type: string last_name: type: string company_name: type: string nullable: true address: type: string city: type: string nullable: true state: type: string nullable: true zip: type: string nullable: true phone: type: string nullable: true sms_notifications: type: boolean nullable: true frequency_id: type: integer service_date: type: string arrival_window: type: integer minimum: 0 maximum: 1440 services: type: array minItems: 1 items: $ref: '#/components/schemas/ServiceSelection' discount_code: type: string nullable: true tip: type: number nullable: true tip_recurring: type: boolean nullable: true payment_method: type: string enum: [stripe, paypal, cash, check] stripe_token: type: string nullable: true description: Required if payment_method=stripe. Generated via Stripe.js using stripe_public_key from Get Settings. customer_notes: type: string nullable: true custom_fields: type: array nullable: true items: type: object additionalProperties: true CustomerBookingRequest: type: object required: [address, frequency_id, service_date, arrival_window, services, payment_method] properties: location_id: type: integer nullable: true original_booking_id: type: integer nullable: true description: '"Book This Again" reference to a prior booking owned by the customer.' address: type: string city: type: string nullable: true state: type: string nullable: true zip: type: string nullable: true phone: type: string nullable: true sms_notifications: type: boolean nullable: true frequency_id: type: integer service_date: type: string arrival_window: type: integer services: type: array minItems: 1 items: $ref: '#/components/schemas/ServiceSelection' discount_code: type: string nullable: true tip: type: number nullable: true tip_recurring: type: boolean nullable: true payment_method: type: string enum: [stripe, paypal, cash, check] customer_notes: type: string nullable: true custom_fields: type: array nullable: true items: type: object additionalProperties: true UpdateCustomerBookingRequest: allOf: - $ref: '#/components/schemas/CustomerBookingRequest' - type: object properties: create_next_recurring: type: object nullable: true required: [service_date] properties: service_date: type: string CancelBookingRequest: type: object properties: confirmed_late: type: boolean confirmed_fee: type: number nullable: true confirmed_recurring: type: object nullable: true properties: cancel_future: type: boolean reason: type: string nullable: true BookingCreatedResponse: type: object properties: id: type: integer ga_transaction: type: object additionalProperties: true ga_item: type: object additionalProperties: true Booking: type: object description: See https://bitbucket.org/awoo23/api-2.0/wiki/Get_booking_for_customer for full attribute documentation. additionalProperties: true properties: id: type: integer digest: type: string service_date: type: string format: date-time arrival_window: type: integer address: type: object additionalProperties: true active: type: boolean completed: type: boolean name: type: string email: type: string phone: type: string frequency: type: object additionalProperties: true payment_method_info: type: object additionalProperties: true services: type: array items: type: object additionalProperties: true summary: type: object additionalProperties: true actions: type: object additionalProperties: true