- Overview
- Discover — Services and Staff
- List Services (GET /services)
- List Staff Members (GET /staff)
- Plan — Search for Available Slots (POST /availability/search)
- Request Body Fields
- Response Fields and Timezone Semantics
- Truncation and Suppression
- Errors
- Reserve — Creating a Booking (POST /bookings)
- Idempotency-Key
- Booking Statuses (State Machine)
- Lifecycle Monitoring
- Webhook-First Approach (Recommended)
- Listing Bookings (GET /bookings)
- Polling Fallback
- Cancel — Cancelling a Booking (POST /bookings/{bookingId}/cancel)
- Errors During the Flow
- Retry Strategy
- Best Practices
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:
- Discover — Query available services and staff members.
- Plan — Search for available time slots for a given date range.
- Reserve — Create a booking.
- Lifecycle — Monitor booking status (webhook or polling).
- Cancel — Cancel a booking if necessary.
Discover — Services and Staff#
List Services (GET /services)#
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)#
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#
| Field | Type | Description |
|---|---|---|
salonSlug | string (mandatory) | Identifier for the service provider business |
locationSlug | string (mandatory) | Identifier for the location |
serviceId | string (mandatory) | Identifier for the requested service |
staffId | string (optional) | If provided, only slots for that staff member appear. |
dateRange.from | YYYY-MM-DD (mandatory) | Start of the search period (business's local timezone) |
dateRange.to | YYYY-MM-DD (mandatory) | End of search period — inclusive, max 31 days after from |
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[].date—YYYY-MM-DDin the business's local timezone (not UTC).slots[].startTime—HH:MMbusiness-local wall-clock time.slots[].endTime—HH:MMbusiness-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: trueindicates truncation.meta.returnedSlots— number of slots actually returned.meta.slotLimit— the truncation limit (500).
If online booking is disabled for the business:
meta.availabilitySuppressed: truedata.slotsis an empty array.
Errors#
availability.date_range_too_wide(400) — ThedateRangeis 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:
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)#
| Status | Description |
|---|---|
requested | Created, waiting for confirmation |
confirmed | Confirmed by the business |
declined | Declined |
rescheduleProposed | Rescheduling proposal received |
cancelled | Cancelled |
completed | Completed |
noShow | Guest did not show up |
Lifecycle Monitoring#
Webhook-First Approach (Recommended)#
Set up a webhook at the PUT /webhooks/config endpoint — you will receive notifications for every booking change.
The 8 webhook events:
booking.requestedbooking.confirmedbooking.declinedbooking.cancelledbooking.reschedule_proposedbooking.reschedule_confirmedbooking.completedbooking.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:
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.fromandto(optional, in pairs): Date range filter based on the booking date.
Polling Fallback#
If webhooks are not available, you can query the booking status:
curl -X GET "https://api.bokko.io/v1/bookings/{bookingId}?salonSlug=example-business" \
-H "Authorization: Bearer bk_live_..."Cancel — Cancelling a Booking (POST /bookings/{bookingId}/cancel)#
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#
| Code | HTTP | When it occurs |
|---|---|---|
booking.slot_unavailable | 409 | The requested time slot is no longer available. |
booking.service_pricing_changed | 409 | Pricing changed between availability search and booking. |
idempotency.payload_mismatch | 409 | Same Idempotency-Key, different payload. |
idempotency.request_in_progress | 503 | Request processing is still in progress (retryable). |
auth.plan_insufficient | 403 | The 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/endTimeare business-local;confirmedAtand 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.