Docs
Ctrl+K

Booking Flow

End-to-end integration of the booking process — service discovery, availability, booking creation, lifecycle monitoring, cancellation.

Overview#

The Bokko booking process consists of 5 phases:

  1. Discover — Query available services and staff members.
  2. Plan — Search for available time slots for a given date range.
  3. Reserve — Create a booking.
  4. Lifecycle — Monitor booking status (webhook or polling).
  5. Cancel — Cancel a booking if necessary.

Discover — Services and Staff#

List Services (GET /services)#

bash
curl -X GET "https://api.bokko.io/v1/services?salonSlug=example-business&locationSlug=downtown" \
  -H "Authorization: Bearer bk_live_..."

Returns all active, online bookable services at the given location. You will need the serviceId for the availability search and when creating a booking.

List Staff Members (GET /staff)#

bash
curl -X GET "https://api.bokko.io/v1/staff?salonSlug=example-business&locationSlug=downtown" \
  -H "Authorization: Bearer bk_live_..."

In the availability search response, slots[].staffId indicates which staff member performs the service — you can display this based on the GET /staff response.


Plan — Search for Available Slots (POST /availability/search)#

Request Body Fields#

FieldTypeDescription
salonSlugstring (mandatory)Identifier for the service provider business
locationSlugstring (mandatory)Identifier for the location
serviceIdstring (mandatory)Identifier for the requested service
staffIdstring (optional)If provided, only slots for that staff member appear.
dateRange.fromYYYY-MM-DD (mandatory)Start of the search period (business's local timezone)
dateRange.toYYYY-MM-DD (mandatory)End of search period — inclusive, max 31 days after from
bash
curl -X POST "https://api.bokko.io/v1/availability/search" \
  -H "Authorization: Bearer bk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "salonSlug": "example-business",
    "locationSlug": "downtown",
    "serviceId": "svc_haircut_01",
    "staffId": "staff_anna_01",
    "dateRange": {
      "from": "2026-05-12",
      "to": "2026-05-18"
    }
  }'

Response Fields and Timezone Semantics#

Request/propose phase — Business local timezone:

  • slots[].dateYYYY-MM-DD in the business's local timezone (not UTC).
  • slots[].startTimeHH:MM business-local wall-clock time.
  • slots[].endTimeHH:MM business-local wall-clock time.
  • slots[].serviceId, slots[].staffId — Identifiers.

UTC timestamp:

  • snapshotAt — UTC ISO 8601 timestamp (moment of snapshot issuance).

Confirmed lifecycle — UTC ISO 8601: Booking metadata (createdAt, updatedAt) arrive in UTC ISO 8601 format.

Confirmed lifecycle — Business local timezone: The confirmed appointment (confirmedSlot.date, startTime, endTime) also arrive in the business's local timezone, matching the availability search format. This eliminates the need for complex timezone conversion for display.

Note on confirmedSlot: The confirmedSlot field is populated for active confirmed-style lifecycle states such as confirmed, completed, and noShow. For cancelled or declined bookings, confirmedSlot is always null in the API, even if the booking had been confirmed earlier. The status field determines whether the time slot is currently occupied.

Truncation and Suppression#

If the search would return more than 500 slots, the response is truncated:

  • meta.truncated: true indicates truncation.
  • meta.returnedSlots — number of slots actually returned.
  • meta.slotLimit — the truncation limit (500).

If online booking is disabled for the business:

  • meta.availabilitySuppressed: true
  • data.slots is an empty array.

Errors#

  • availability.date_range_too_wide (400) — The dateRange is wider than 31 days.

Reserve — Creating a Booking (POST /bookings)#

Idempotency-Key#

Every booking creation request requires an Idempotency-Key header in RFC 4122 UUID v1–v5 format. The idempotency window is 24 hours.

Generate the UUID once into a shell variable. For retries, send exactly the same key:

bash
IDEMPOTENCY_KEY="$(uuidgen)"
curl -X POST "https://api.bokko.io/v1/bookings" \
  -H "Authorization: Bearer bk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: ${IDEMPOTENCY_KEY}" \
  -d '{
    "salonSlug": "example-business",
    "locationSlug": "downtown",
    "serviceId": "svc_haircut_01",
    "expectedPricingVersion": "a1b2c3d4e5f6",
    "staffId": "staff_anna_01",
    "slot": {
      "date": "2026-05-12",
      "startTime": "10:00"
    },
    "guest": {
      "name": "Test Guest",
      "phone": "+36301234567"
    }
  }'

Booking Statuses (State Machine)#

StatusDescription
requestedCreated, waiting for confirmation
confirmedConfirmed by the business
declinedDeclined
rescheduleProposedRescheduling proposal received
cancelledCancelled
completedCompleted
noShowGuest did not show up

Lifecycle Monitoring#

Set up a webhook at the PUT /webhooks/config endpoint — you will receive notifications for every booking change.

The 8 webhook events:

  • booking.requested
  • booking.confirmed
  • booking.declined
  • booking.cancelled
  • booking.reschedule_proposed
  • booking.reschedule_confirmed
  • booking.completed
  • booking.no_show

Every event arrives with an X-Bokko-Signature: sha256=<hex> header with a HMAC-SHA256 signature — verify it to establish authenticity. Detailed guide: Webhooks →

Listing Bookings (GET /bookings)#

If you want to synchronize the status of multiple bookings at once:

bash
curl -X GET "https://api.bokko.io/v1/bookings?salonSlug=example-business&locationSlug=downtown&status=confirmed&from=2026-05-12&to=2026-05-18" \
  -H "Authorization: Bearer bk_live_..."

Filtering parameters:

  • status (optional): Only bookings with the given status.
  • locationSlug (optional): Filter by location.
  • from and to (optional, in pairs): Date range filter based on the booking date.

Polling Fallback#

If webhooks are not available, you can query the booking status:

bash
curl -X GET "https://api.bokko.io/v1/bookings/{bookingId}?salonSlug=example-business" \
  -H "Authorization: Bearer bk_live_..."
Warning
Do not overload the API unnecessarily! Recommended maximum polling frequency: 1 request / 30 seconds. Use webhooks for real-time status updates.

Cancel — Cancelling a Booking (POST /bookings/{bookingId}/cancel)#

bash
IDEMPOTENCY_KEY="$(uuidgen)"
curl -X POST "https://api.bokko.io/v1/bookings/{bookingId}/cancel?salonSlug=example-business" \
  -H "Authorization: Bearer bk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: ${IDEMPOTENCY_KEY}"

Cancellation is idempotent: if the booking is already in cancelled status, the request returns with a 409 booking.invalid_transition error — this is not a real error, the cancellation has successfully occurred.


Errors During the Flow#

CodeHTTPWhen it occurs
booking.slot_unavailable409The requested time slot is no longer available.
booking.service_pricing_changed409Pricing changed between availability search and booking.
idempotency.payload_mismatch409Same Idempotency-Key, different payload.
idempotency.request_in_progress503Request processing is still in progress (retryable).
auth.plan_insufficient403The key's service provider business does not have a Pro subscription.

Retry Strategy#

  • idempotency.request_in_progress (503) — retryable: yes, with the same Idempotency-Key.
  • booking.slot_unavailable (409) — retryable: no; search for a new time slot.
  • booking.service_pricing_changed (409) — retryable: no; query the service list again.

Best Practices#

  • Generate Idempotency-Key into a shell variable, not inline — it should not change during retries.
  • Webhook priority: Use polling only as a fallback; use webhooks for real-time notifications.
  • Timezone: Availability slot date/startTime/endTime are business-local; confirmedAt and similar lifecycle fields are UTC — do not mix them up.
  • Slot freshness: Availability snapshots can become outdated quickly; do not store them in cache for longer than 30 seconds before booking.