Skip to navigation

6 · Book

Place a booking.

What: places the booking. Requires the quoteToken from /fare-quote; the supplier-quoted price is taken from the signed token, so a tenant can’t book a different price than was quoted.

When: after a successful /fare-quote, within the token’s validity.

Before / After: /fare-quote → here → do what the booking’s nextAction says (WAIT, ISSUE_TICKET, SUBMIT_PNR, SEARCH_AGAIN, CONTACT_SUPPLIER or NONE).

Gotchas

  • Read the new names — the old ones are removed in the next release. Each new name has the same value as its old one until then, and the reference marks the old name deprecated. supplierCode for sourceCode; seller for supplier; origin / destination ({ iataCode, name, city, countryCode, timezone }) for originCode / destCode; price (with quotedTotalAmount, the total of the quote the booking was made on) for totalAmount / currency / fareBreakdown; policy.refund / policy.change for policy.refundPolicy / changePolicy; each flight’s marketingCarrier { iataCode, name, flightNumber } for airlineCode / airlineName / flightNumber, origin / destination for its codes, names and time zones, departureTerminal / arrivalTerminal / cabin for originTerminal / destinationTerminal / cabinClass, a technical stop’s airport for airportCode / airportName; each passenger’s passengerType for travelerType and guardianPassengerIndex for guardian.
  • Send the offer’s trip back — tripType, departureDate and (round trip) returnDate — exactly as on /fare-quote (the token binds them).
  • Idempotency is the Idempotency-Key HEADER (required). Persist it and reuse the same value on retries — the booking is reserved before the supplier call, so a retry returns the same booking instead of charging twice. There is no body idempotency field.
  • passengers[].passengerType is ADULT / CHILD / INFANT, and the array must match adults/children/infants — which must be the party the offer was searched for (else 400 PASSENGER_COUNTS_CHANGED). A CHILD or INFANT needs dateOfBirth. The type goes by age on the date of the last flight (the return flight on a round trip): ADULT 12 or older, CHILD 2–11, INFANT under 2 — so a baby who turns 2 before the return flight books as a CHILD. A date of birth that does not fit the type, or is after the departure date, → 422 PASSENGER_FIELD_REQUIRED naming the passenger by position, the date and the type to use: search again with that passenger counted as that type (the passengers you book must be the ones you searched with, so changing the type on /book alone is refused again). Every INFANT needs guardianPassengerIndex: the position in passengers (0 = the first) of the ADULT it travels with, on their lap; each adult takes one infant.
  • Every booking shows what the offer was sold with: each flight’s departureTerminal, arrivalTerminal and cabin, the offer’s cabin and policy, and its included baggage (the supplier’s booking answer when it gave one, else the quote at booking, else Search; a later supplier answer that states baggage — a refresh, or the status check of a booking confirmed after /book — replaces the rows of the passenger types it states, the others stay; null when none stated any). These describe the offer, so a FAILED booking shows them too: nothing was booked. Each passenger shows the passportIssuingCountry Kitionz sent.
  • An adult with no dateOfBirth on a domestic trip: some suppliers refuse a booking without one, so Kitionz sends them 1 January of the year 20 years back. The booking does not show that date as the passenger’s: it answers dateOfBirth: null with dateOfBirthDefaulted: true. Send the real date when you have it.
  • booker is your record of who made the booking: Kitionz stores and returns it, and some sellers receive booker.name as the contact name. The seller receives your organisation’s registered email and mobile as the booking contact (your Kitionz account details), not booker.email / booker.phone (booker.phone is sent only when your organisation has no mobile on record). Seller and airline messages (e-ticket, schedule change, cancellation) therefore come to you: pass them on to your traveller.
  • Which reference to keep: the booking id (bkg_…) — your key for every call after booking; key your orders on it, never on the PNR. pnr is the airline’s booking code for the traveller (check-in, the airline’s website); it can arrive after /book. supplierReference is the seller’s own booking number: quote it to the seller’s support. Send your own order number as externalReference: if you lose the /book answer, GET /v1/flights/bookings?externalReference=… finds the booking. Kitionz returns no e-ticket numbers or ticket document: get them from the seller named in seller.
  • Old names, removed in the next release: supplierOfferRef (now offerId), passengers[].travelerType (now passengerType), passengers[].guardian (the adult’s name; now guardianPassengerIndex), bookingMode: "LIVE" (now "INSTANT"), originCode and destCode (not needed: Kitionz takes the route from the offer). They are still accepted until then; use the names above. An old and a new name with different values → 400 VALIDATION_ERROR naming both. Answers carry the new names beside the old ones (see the first note); bookingMode still answers "LIVE" for an instant booking until the next version.
  • bookingMode: "INSTANT" (the default) books now, with no hold. The offer decides how: through the seller’s system, or, when the offer is a request booking, as a request you complete by sending the PNR (nextAction says which). "HOLD" holds the seats where the offer allows it.
  • The body takes only the fields in the reference: any other field → 400 VALIDATION_ERROR. Seats, meals and extra baggage, payment details, corporate or tour codes and frequent-flyer numbers cannot be sent at booking (no supplier receives them).
  • A 201 means Kitionz recorded a booking — decide by data.status and data.nextAction, never by the HTTP status alone. FAILED (with failure saying why) means nothing was booked or charged: search again. PENDING_CONFIRMATION means wait: the booking may exist; read GET /bookings/{id} after nextAction.pollAfterSeconds, and never book again meanwhile.
  • If /book is slow or your request times out, never book again with a new key. Send the identical request again with the SAME Idempotency-Key: you get the first booking back, or 409 BOOKING_IN_PROGRESS / IDEMPOTENT_REQUEST_IN_PROGRESS while it is still running (wait, then send it again). 409 QUOTE_TOKEN_ALREADY_USED names the booking that quote already made in details.existingBookingId: read it.
  • 500 BOOKING_OUTCOME_UNKNOWN (with details.bookingId) and any answer with no Kitionz error.code (for example a 504 from the network) mean the booking may exist: send the same request again with the same key, or read the booking. Set your HTTP timeout for /book to at least 90 seconds; a slow supplier can take more than a minute.
  • A 4xx means nothing was booked and no supplier booking was made.
  • Just before booking, Kitionz asks the supplier for the fare again and checks it the same way as /fare-quote. A broken answer → 422 FARE_QUOTE_UNUSABLE. An infant price that changed since your quote → 409 FARE_RECHECK_FAILED: failureClass INFANT_PRICE_WITHDRAWN (you were quoted one, the supplier gives none now; quote again: it shows NOT_GIVEN) or INFANT_PRICE_APPEARED (you were quoted NOT_GIVEN, the supplier gives one now; quote again: it shows PRICED). All are definite: nothing was booked or charged.
  • You never send an amount. When the quote said infantFare.status: NOT_GIVEN, the booking is made at the supplier’s total (adults and children) and your own infant charge stays with you. The booking’s price.infantFare is the supplier’s statement when the booking was made; since /book refuses any change from the quote, it is always the status you were quoted.

Example — request (with the Idempotency-Key: <your-uuid> header):

{
"offerId": "floffer_9x2k7q",
"quoteToken": "eyJhbGci…",
"adults": 1, "tripType": "ONE_WAY", "departureDate": "2027-01-15",
"booker": { "name": "Asha Rao", "email": "asha@example.com", "phone": "+919800000000" },
"externalReference": "ORDER-1042",
"passengers": [ { "title": "Ms", "firstName": "Asha", "lastName": "Rao", "passengerType": "ADULT" } ]
}

Response (trimmed) — the HTTP status is 201; decide by data.status:

{ "data": { "id": "bkg_7f3a92", "status": "CONFIRMED", "pnr": "AB12CD", "supplierCode": "gapi", "seller": { "name": "Airnatt Holidays", "website": "https://airnatt.com", "supportPhone": "+91 99999 00000", "supportEmail": "help@airnatt.com" }, "price": { "totalAmount": 5480.00, "currency": "INR" }, "quotedTotalAmount": 5480.00, "returnSupplier": null } }

seller names who sold the flight (the real seller behind a marketplace), with the support contacts Kitionz holds — on every booking from the moment it exists (supplier is its old name). returnSupplier is always null (one seller per booking); it will be removed in a later version.

Authentication

AuthorizationBearer

Your Kitionz API key, sent as Authorization: Bearer keyId:secret. Create a Test key at sandbox.kitionz.com → API keys, a Live key at kitionz.com → API → API keys.

Headers

Idempotency-KeystringRequiredformat: "^[a-zA-Z0-9_-]{1,255}$"

A new unique value (a UUID) for every write: one booking, one cancel. Save it with your order before you call. Send the SAME value again only when you send the SAME request again after a timeout: you get the first answer back. Reusing it on any other write answers 422 IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_PARAMS.

Request

This endpoint expects an object.
offerIdstringRequired1-30 characters

Required (or its old name supplierOfferRef; send one, null is refused): the id of the offer you chose in /search — the offer’s offerId (floffer_…, 30 characters).

adultsintegerRequired1-9
departureDatestringRequired

The offer’s departureDate from /search — send it back unchanged (a different date → 400 OFFER_TRIP_MISMATCH).

passengerslist of objectsRequired
quoteTokenstringRequired40-8192 characters
sourceCodestringOptional2-50 characters

Leave it out: the offer id already names the supplier. Only an organisation shown suppliers before booking may send it, and it must then match the offer; for everyone else it is refused with SOURCE_CODE_NOT_ACCEPTED.

childrenintegerOptional0-9Defaults to 0
infantsintegerOptional0-9Defaults to 0
tripTypeenumOptional

The offer’s tripType from /search. May be left out: the offer’s own trip type is used. When sent it must be the offer’s (another → 400 OFFER_TRIP_MISMATCH).

Allowed values:
returnDatestringOptional

Round trip only: the offer’s returnDate from /search, sent only with tripType ROUND_TRIP. May be left out (the offer fixes it); when sent, send it on /fare-quote and /book alike.

bookerobjectOptionalDefaults to {}

Your record of who made the booking (for example your agent or customer): Kitionz stores it and returns it on the booking, and some sellers receive name as the contact name. The seller receives your organisation’s registered email and mobile as the booking contact (your Kitionz account details), not booker.email / booker.phone (booker.phone is sent only when your organisation has no mobile on record). Seller and airline messages — e-ticket, schedule change, cancellation — come to you: pass them on to your traveller.

bookingModeenumOptionalDefaults to INSTANT

INSTANT (the default): book now, with no hold. The offer decides how: through the seller’s system, or, when the offer is a request booking, as a request you complete by sending the PNR. HOLD: hold the seats where the offer allows it. LIVE is the old name of INSTANT, accepted until the next release, which removes it.

Allowed values:
externalReferencestringOptionalformat: "^[^\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]*$"<=120 characters

Your own order number, up to 120 characters. Kitionz stores it and returns it on the booking. Send it on every /book: if you lose the answer, GET /v1/flights/bookings?externalReference=… finds the booking. Not checked for uniqueness.

Response

Success
successboolean
statusCodedouble
dataobject

A full persisted booking (passengers, segments, events, amounts, status).

metaobject

Errors

409
Conflict Error