Skip to main content

The booking flow

Four steps, identical in every vertical:

Only step 1 differs per vertical. Steps 2–4 are the same call for everything you sell.

1 · Discover

Each vertical has its own discovery endpoint, because a coach seat and a VIP subscription are not searched the same way:

VerticalDiscovery
Airport transferPOST /api/v1/airport-transfer/search
Express busPOST /api/v1/express-bus/search
EntertainmentGET /api/v1/entertainment/catalog

Entitlement filters discovery silently — inventory you are not contracted to sell simply does not appear. An empty result is normal and is not an error.

2 · Quote

Discovery mints a price-locked quoteId on every row. You do not call a separate quote endpoint; you choose a row and carry its quoteId forward. See Quotes for expiry rules.

3 · Book

One vertical-agnostic call:

POST /api/v1/bookings
{
"idempotencyKey": "a1f4c2e0-77b2-4c31-9c2e-6b1f0a9d3e55",
"items": [
{ "quoteId": 90188, "details": { /* per-vertical */ } }
]
}
  • idempotencyKey — yours to choose, unique per attempt. Re-sending it replays the original order instead of booking twice.
  • quoteId — send it unquoted. A string or a UUID is rejected.
  • details — the per-vertical bag. Which shape applies is decided by the offering behind the quote, not by anything you set. See the vertical pages for the exact fields.

You never send a price. Net and sell both come from the frozen quote.

A round trip is two items

items is a list, and every entry needs its own quoteId from its own search — a quote cannot be reused. Two entries book atomically: both confirm, or both compensate.

{
"idempotencyKey": "7b3e9a01-4c22-4d3a-9e88-1f2a3b4c5d6e",
"items": [
{ "quoteId": 90188, "details": { "...": "outbound" } },
{ "quoteId": 90189, "details": { "...": "inbound" } }
]
}

This is the whole story for multi-leg orders. There is no round-trip endpoint and no package endpoint — the item list is the mechanism.

4 · Read back

GET /api/v1/bookings/{id} returns the same envelope at any time. Use it to poll an order whose outcome was not yet decided, rather than booking again.

The response envelope

Identical for every supplier and vertical:

{
"orderId": "b3f1c2d4-9a8b-4c7d-8e6f-1a2b3c4d5e6f",
"referenceNo": "GHB1723600000000K7Q2",
"state": "CONFIRMED",
"items": [
{
"itemId": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"referenceNo": "GHB1723600000000K7Q2-1",
"state": "CONFIRMED",
"supplierBookingRef": "TB-100234",
"redemptionRef": "TC-9",
"sellAmount": 48.00,
"currency": "MYR",
"detail": { "type": "TAXI_COUPON", "coupons": [ /* ... */ ] }
}
],
"payable": 48.00,
"currency": "MYR",
"charges": []
}
FieldWhat it is for
orderIdThe UUID. Use it to read the order back.
referenceNoThe short handle. Quote this to GoHub support.
stateWhere the order rests — see Outcomes.
items[].redemptionRefWhat the customer redeems, where there is such a thing.
items[].detailThe supplier-specific bag, discriminated by its own type.
payable / chargesYour commercial view: what your account is charged, and the fee lines behind it.

detail is the only part of the envelope that varies by supplier. Switch on detail.type to render a coupon QR, a boarding code, or nothing — and the envelope around it will not change when we add a supplier.

payable and charges are returned only to the agent that owns the order. Another agent reading the same id gets null and an empty list.