Booking flow
The API keeps no session between calls. Everything a later call needs travels in the objects you send back, so store them exactly as you received them.
Lifecycle
| Step | Endpoint | Required? |
|---|---|---|
| 1 | POST /v1/search | Yes |
| 2 | POST /v1/transfer/recheck | Strongly recommended immediately before every book |
| 3 | POST /v1/transfer/book | Yes |
| 4 | POST /v1/transfer/booking-details | After booking, and whenever you show the booking |
| 5 | POST /v1/transfer/cancel | When the customer cancels |
What to carry between calls
| From | Field | Send it to | As |
|---|---|---|---|
| search offer | provider | recheck, book, booking details, cancel | provider_context.provider |
| search offer | rate_key | recheck, book | provider_context.rate_key |
| search offer | offer_token | recheck | provider_context.offer_token |
| recheck response | offer_token | book | provider_context.offer_token (instead of the offer's token) |
| book response | supplier_reference (whole object) | booking details, cancel | supplier_reference |
| book response | the whole response (optional) | booking details | book_response |
| cancel response | the whole response | booking details | cancel_response |
offer_tokenandsupplier_reference.booking_tokenare opaque, encrypted strings. They cannot be read; any change, or a token from another key or environment, is rejected with422.- Store the whole
supplier_referenceas JSON and send it back unchanged. offer_ididentifies an offer within one search response only; it is regenerated on every search.
Recommended booking record on your side:
| Your column | Value |
|---|---|
internal_reference | your own reference, sent at book time |
provider | supplier_reference.provider |
supplier_booking_id, supplier_booking_ref | from the book response (show supplier_booking_ref to customers) |
supplier_reference | JSON, unchanged |
book_response, cancel_response | JSON, unchanged (recommended) |
price_total, price_currency | total_price and currency from the book response |
Offer expiry
Offers are not held. How long an offer stays bookable depends on the provider, and recheck adapts to it:
| Provider behaviour | Validity in practice | What recheck does |
|---|---|---|
| Live price check available | Often short: some quotes expire about 20 minutes after the search | Live price or availability check. An expired quote answers available: false. |
| No price check available | 24 hours from the search | Checks the offer's age: up to 24 hours it answers available: true with the search price. The provider confirms availability at book time. |
For every provider, an offer_token older than 24 hours from the search is refused with 409 offer_expired.
A recheck returns a fresh token with the same expiry: it does not extend the offer.
Recommendation: recheck immediately before booking. If the customer's search is older than about 15 minutes,
or recheck answers available: false, search again.
Price changes at recheck
available | price_changed | What to do |
|---|---|---|
true | false | Book with the recheck offer_token. |
true | true | Show total_price and currency to the customer. Book only after they accept, with the recheck offer_token. |
false | – | Do not book. Search again; error explains why. |
A difference of 0.01 or less is not reported as a change. The book response returns the final price in
total_price; compare it with the price you showed.
Booking outcome
status | Meaning | Next step |
|---|---|---|
completed | The provider confirmed the booking. | Store supplier_reference, then call booking details. |
pending | Accepted but not confirmed yet. | Poll booking details (for example after 1, 5 and 15 minutes) until booking.status is confirmed or failed. |
failed | Nothing was booked; error explains why. | Show an error. You can recheck and try again, or offer another option. |
unknown | The provider did not answer in time after receiving the request, so a booking may exist. error.code is book_outcome_unknown; no supplier_reference yet. We reconcile it with the provider. | Do not rebook. Re-send the identical request later: see Idempotency & retries. |
Round trips
A point_to_point search with trip_type: round_trip returns offers with a leg field:
leg | What it is | How to book |
|---|---|---|
outbound | pickup → drop-off on pickup_date / pickup_time | One booking |
return | drop-off → pickup on return_date / return_time | One separate booking |
round_trip | One package covering both directions, from providers that sell round trips natively | One booking for both directions |
- Sell either one
outboundand onereturnoffer (they can come from different providers), or oneround_tripoffer. Do not combine around_tripoffer with leg offers. - Each leg is rechecked, booked, shown and cancelled on its own, with its own
internal_reference(for exampleORDER-10002-OUTandORDER-10002-RET). - There is no atomic round-trip booking. If the outbound books but the return fails, offer another return or cancel the outbound.
The airport equivalent is a search with both arrival_* and departure_* set; the arrival and departure
offers are booked separately in the same way.
Minimal integration
offers = POST /v1/search
offer = customer_choice(offers) # keep provider, rate_key, offer_token
rc = POST /v1/transfer/recheck {provider_context: {provider, rate_key, offer_token: offer.offer_token}}
if HTTP 409 or not rc.available: search again
if rc.price_changed: show rc.total_price; stop unless the customer accepts
bk = POST /v1/transfer/book {provider_context: {provider, rate_key, offer_token: rc.offer_token},
travellers, internal_reference, flight_info}
if HTTP 409 duplicate_booking or bk.status == "unknown": re-send the identical request later; stop
if bk.status == "failed": show bk.error.message; stop
store(bk.supplier_reference, bk)
bd = POST /v1/transfer/booking-details {provider_context: {provider}, supplier_reference}
render_voucher(bd.booking_details)