Appearance
Booking flow
CBMS booking is always two-phase:
availability → hold → (you collect payment) → confirmThere is no single “book this slot” endpoint. If you skip hold, you are not integrated.
1. Availability is a hint
http
GET /api/v1/slots/availability?venue_id=<uuid>&date=2026-09-20&resource_id=badminton-court-1
x-api-key: <your-api-key>is_available: true means “probably free when we looked.” Another channel can hold it before you do. Treat the list as a catalogue; hold is the mutex.
Book exactly the slot that availability returned: send its slot_start, and send slot_end − slot_start in minutes as slot_duration_minutes (availability has no separate duration field). Do not invent windows. See Availability.
2. Hold — this is the reservation
http
POST /api/v1/bookings/hold
Content-Type: application/json
x-api-key: <your-api-key>
{
"venue_id": "a0000000-0000-4000-8000-000000000001",
"resource_id": "badminton-court-1",
"slot_start": "2026-09-20T18:00:00.000Z",
"slot_duration_minutes": 60,
"idempotency_key": "hold-9f2a-4c1e-8b7d-1a2b3c4d5e6f"
}Do not send partner_id. Optional customer_phone lets membership pricing apply at hold time.
201:
json
{
"hold_id": "…",
"slot_id": "…",
"status": "ACTIVE",
"hold_expiry_at": "2026-09-20T17:25:00.000Z",
"price_rupees": 600
}Hold duration is set per venue and can be changed by the venue at any time — the current default for a newly configured venue is 15 minutes. On the sandbox a partner hold lasts at least 15 minutes; production gets that minimum with its next release (see Holds). hold_expiry_at is the authoritative deadline for this specific hold; read it from the response rather than assuming a fixed duration. Show a countdown. After expiry the slot returns to AVAILABLE automatically (usually within a minute). You will get hold_expired if you subscribed.
If the customer abandons checkout, release immediately:
http
POST /api/v1/partner/holds/{hold_id}/cancel
Content-Type: application/json
x-api-key: <your-api-key>
{ "idempotency_key": "cancel-hold-9f2a-…", "reason": "abandoned checkout" }Letting it expire also works; cancel is faster for the next customer.
Hold errors you will actually hit
| HTTP | error | What to do |
|---|---|---|
| 400 | SLOT_NOT_AVAILABLE | Past slot, closed day/hours, unknown resource_id, or booking-rule violation. On the sandbox, also a window that does not start and end on the court's slot grid (details.reason: "off_grid"; production with its next release). Pick another slot. |
| 409 | SLOT_NOT_AVAILABLE | Someone else holds or booked it, or the window overlaps a HELD/BOOKED row. Pick another slot. |
| 409 | TRANSFORMATION_CONFLICT | This court is free but a convertible sibling (e.g. the football field that occupies two box-cricket courts) is reserved. Pick another slot. |
| 403 | VENUE_NOT_AUTHORIZED | This venue is not granted to your key. |
| 429 | RATE_LIMIT_EXCEEDED | Honour Retry-After. |
3. You collect payment
CBMS does not charge the customer on confirm. payment_reference is an opaque string from your processor so you can correlate later.
Do not confirm until you intend the booking to exist. Confirm is the inventory commit.
4. Confirm — before hold_expiry_at
http
POST /api/v1/bookings/confirm
Content-Type: application/json
x-api-key: <your-api-key>
{
"hold_id": "…",
"payment_reference": "pay_example_ref",
"customer_name": "Aarav Sharma",
"customer_phone": "9876543210",
"customer_email": "customer@example.com",
"idempotency_key": "confirm-9f2a-4c1e-8b7d-1a2b3c4d5e6f"
}201:
json
{
"booking_id": "…",
"slot_id": "…",
"status": "BOOKED",
"booked_at": "2026-09-20T17:12:43.812Z",
"price_rupees": 600
}| HTTP | error | What to do |
|---|---|---|
| 404 | HOLD_NOT_FOUND | Wrong id. |
| 409 | HOLD_EXPIRED | The hold's window passed. Not booked — see below. |
| 409 | SLOT_NOT_AVAILABLE | The held slot can no longer be booked: its state changed, or the venue blacked it out after you held it. Not booked — see below. |
| 409 | TRANSFORMATION_CONFLICT | A linked court that shares the same physical space (e.g. the football field over two box-cricket courts) is now reserved. Not booked — see below. |
| 409 | INVALID_HOLD_STATE | The hold is already confirmed, cancelled, or expired. If you confirmed it, GET /bookings or a retry with your original confirm key returns the booking; otherwise treat it as not booked. |
| 409 | PACKAGE_CREDIT_UNAVAILABLE | Only with use_package_credit: true: no credit from a package you sold to this customer is available. Not booked, nothing deducted, hold still active. See Paying with a package credit. |
If confirm fails after you took payment
You collect payment before confirm, so a confirm that answers 409 HOLD_EXPIRED, 409 SLOT_NOT_AVAILABLE or 409 TRANSFORMATION_CONFLICT can leave you holding the customer's money for a booking that does not exist. In that case:
- The booking is not confirmed. No booking was created and no court is reserved for this customer.
- Do not tell the customer the booking succeeded, and do not show it as booked in your app.
- Do not retry the same confirm. It will fail the same way. Release the hold (
POST /partner/holds/{hold_id}/cancel) or let it expire. - Do not ask the customer to pay again because confirm failed. The payment you already took still belongs to this attempt.
- Refund or reconcile that payment in your own payment flow. CBMS has not charged anyone and will not refund anyone; the money is entirely on your side. If you offer the customer another slot, take a new hold and apply the payment you already hold, according to your own rules.
Keep the failed hold_id, your payment_reference and the response's request_id together, so support can trace the case.
5. Cancel or refund after confirm
See Cancellations. Each call needs its own idempotency_key, different from the hold and confirm keys.
Retries
Persist idempotency_key next to the local operation. If the HTTP call times out, retry the same body and key. Details — including the hold special case — are on Idempotency.
