Skip to navigation

Full flight booking flow

What you’ll build: a complete booking — search for flights, confirm a price, take the money decision, place the booking, and confirm (or issue) the ticket. This is the money path, so it leans on three Kitionz safety guarantees you’ll see throughout: idempotency (a retry never double-books), the signed quoteToken (Kitionz never books above the price you quoted), and safe retries (a slow supplier never looks like a failed booking).

Note: New to flight APIs or unsure about a word like quoteToken or offerId? The Glossary defines every term in plain English — keep it open in a second tab.

The same endpoints can be used for less than this. If you only want to show fares, stop after Search. If you only manage existing bookings, start at Step 4. This recipe is the full transaction.

Before you start

On the Live API you need two things:

  • Your Live key, sent as Authorization: Bearer <keyId>:<secret> on every request, from an IP on its allow-list, with your organisation’s KYC approved.

  • On every write (/book, /ticket, /cancel …): an Idempotency-Key: <uuid> header — a new UUID for each write, saved with your order before you call; send the same value again only when you resend the same request after a timeout — + Content-Type: application/json. You’re auto-pinned to an API version, so you don’t send Kitionz-Version.


What: find bookable offers for a route + date + passenger mix. When: the start of every booking. Next: pick one offer and send its offerId to the fare quote.

Send origin, destination and departureDate; every other field is optional (adults defaults to 1). A real request and its recorded answer: Search.

No tripType sent, so the answer holds every trip leaving that day: one-way fares and fixed round-trip packages, each with its own tripType. "tripType": "ROUND_TRIP" returns round trips only, and "tripType": "ONE_WAY" one-way offers only (data.meta.filtersApplied counts the round trips it left out).

There are two meta objects. data.meta describes the search (trip kinds, how many suppliers answered, filtersApplied). The outer meta, beside requestId, carries diagnostics when the answer is empty or an input looks wrong (see “What can go wrong” below).

Critical: a round trip is ONE offer. It comes only from suppliers that sell round trips as one two-leg fare: a single offer with legs: [OUTBOUND, RETURN], one price.totalAmount, one offerId. Never staple two one-way offers yourself.

Critical: every offer says which trip it is — tripType, departureDate and, for a round trip, returnDate. A round trip’s price covers both ways: say so on your screen. The fare quote and the booking take them from the offer: you need not send them.

Critical: search prices are indicative. They can move. Never charge your customer off a search price — re-price in the next step.

Required: search with the passengers you will book. An offer is quoted and booked only for the adults, children and infants it was searched with; another party is refused at the fare quote with 400 PASSENGER_COUNTS_CHANGED, and at the booking with 409 QUOTE_TOKEN_PAX_MISMATCH. Kitionz accepts an offer’s offerId until its expiresAt (about 15 minutes). That is not a price or seat guarantee.

Required — international trips: read the offer’s isInternational and requiredPassengerFields before you show a passenger form. isInternational is true when a flight crosses a border (or when Kitionz cannot tell); then every passenger needs dateOfBirth, passportNumber, passportExpiryDate and nationality, and the passport must be valid at least 6 months after the last flight. Collect them before you take payment (Step 3 has the details).

Show what the seller did not state

Kitionz never makes up a value. A value the seller did not state is null, and a refund or change policy is NOT_GIVEN. A 0 the seller did state stays 0. Live sellers leave many values unstated, and so does Kitionz Test Air (it is built to look like them), so build every screen for them:

ValueShow your agentWhat the agent does
baggage is null, or no row for a passenger type”Baggage: not stated”Do not promise an allowance; check the airline’s rule before travel
policy.refund is NOT_GIVEN”Refund terms: not stated”Do not promise a refund; ask the seller before selling it as refundable
policy.change is NOT_GIVEN”Change terms: not stated”Do not promise a date change
a flight’s cabin is null”Cabin: not stated”Do not print “Economy”
price.baseFare, taxes or published* are nullthe total onlyNever show “Taxes ₹0”
price.commission is null—Your commission was not stated: use your deal with the seller
price.infantFare.status is NOT_GIVEN”Infant fare not included”Add your infant charge; the seller or airline may still bill the infant

Baggage rows use paxType ADT, CHD and INF (for ADULT, CHILD, INFANT): match them to passengers with that table.

Important: a leg with a stop has one segment per flight. Some suppliers give the wait at a stop but not the clock times there, so a segment’s departureTime or arrivalTime can be null at that stop. The wait is in the leg’s layovers (layovers[k] is the stop after segments[k]); a booking carries it as segments[].layoverAfterMinutes. Show “Time not stated”, and never work out a time or a duration from a null. A leg’s first departure and last arrival always have a time. If the supplier’s own times at a stop contradict each other, Kitionz shows them as not stated as well; when it has no wait for that stop, the leg has no layovers and the booking’s layoverAfterMinutes is null.

The fields of this call and a real answer are on its page: Search.

Note — What can go wrong here?

  • Empty offers with no error → read diagnostics in the outer meta (beside requestId, not data.meta). It names why: NO_RESULTS_FOR_QUERY (your suppliers had nothing for that route and date), FILTERS_REMOVED_ALL_OFFERS (your filters removed every offer), UNKNOWN_AIRPORT (an airport code Kitionz does not know — check it), or a supplier not yet approved for Live. See Go live.
  • Your filters removed every offer → data.meta.filtersApplied has one { field, removed } entry per filter you sent. Few suppliers state a fare brand or a refund policy, so fareBrandsInclude or refundableOnly can remove everything.
  • FORBIDDEN (403) → a code in supplierCodes is not switched on for your account. Switch it on under Suppliers, or leave supplierCodes out.
  • VALIDATION_ERROR → a field name/format is off, or the search asks for something Kitionz does not offer (three or more slices, or a way back to other airports). Read error.details.invalidFields: it names the field.

Step 2 — Fare quote (confirm the price, get a token)

What: re-price the chosen offer against the live supplier and get a signed quoteToken. Why it matters: for about 15 minutes, the token binds this price, offer and passengers, and Kitionz will not book above that price. It does not hold the seat. Send the offerId: the passengers and the trip are the offer’s. adults, children, infants, departureDate, tripType and returnDate are optional; if you send one it must be the offer’s or the number you searched with (another trip → 400 OFFER_TRIP_MISMATCH, another party → 400 PASSENGER_COUNTS_CHANGED). Next: pass the token to Book.

Send { "offerId": "floffer_…" }. A real request and its recorded answer: Fare quote.

Note — per-passenger prices: price.passengerFares has one row per passenger type (passengerType, count). total is the price of all passengers of that type together and perPerson is one passenger’s, to the paisa: two adults on { "passengerType": "ADULT", "count": 2, "perPerson": 5324, "total": 10648 } cost ₹5,324 each. When every row states a total, the rows add up to totalAmount. Charge price.totalAmount, never a sum of perPerson. The booking carries the same price block.

Required: book the passengers you searched with. The quoteToken binds the offer, the passengers and the trip; Book counts the passengers from its passengers list (another party → 409 QUOTE_TOKEN_PAX_MISMATCH: search again with them). There is no supplier field to send: the offerId already names the supplier.

Critical: the price can change here. Always show the customer price.totalAmount from this response, never the search price. priceChanged is true when it differs from the offer’s total at search (searchTotalAmount); Kitionz compares the two for every supplier. With an infant, read price.infantFare.status first: if the infant price was given at only one of the two steps, the difference is the infant fare, not a fare change. If priceChanged is true, tell the customer the price moved and book only if they accept. The quoteToken carries this price. At booking, Kitionz checks again: if the price went up or the seats are gone, nothing is booked (FARE_HIGHER_THAN_QUOTED / INSUFFICIENT_SEATS) and you quote again. If the price moved down, some sellers book at the lower price and some refuse any change with 409 SUPPLIER_FARE_CHANGED_AT_BOOK (nothing booked: quote again) — never rely on either. Seats can also go at the seller itself: then Book answers 201 with a FAILED booking and failure.code SUPPLIER_BOOK_FAILED. The token is valid until quoteTokenExpiresAt (about 15 minutes).

Required — offer & quote lifetimes: two short-lived clocks run here.

  • Search offer, until its expiresAt (about 15 min): after that, quoting or booking its offerId returns OFFER_EXPIRED → re-search. A successful fare quote extends it.
  • quoteToken ~15 min: the signed token expires (quoteTokenExpiresAt). Booking after that returns QUOTE_TOKEN_EXPIRED → re-quote.
  • If the live fare rose above the quote at book time you get FARE_HIGHER_THAN_QUOTED — re-quote and show the new price; never auto-charge the difference.

Quote variants

The fields of this call and a real answer are on its page: Fare quote.

Note — What can go wrong here?

  • OFFER_EXPIRED → the offer’s expiresAt passed. Re-run search, quote a fresh offerId.
  • PASSENGER_COUNTS_CHANGED → you sent other adults / children / infants than the search. error.details.searchedParty and sentParty show both. Search again with the passengers you will book.
  • priceChanged: true on a 200 → the price moved since search. It is not an error: the NEW quoteToken + price are in the body — show the customer the new price before booking with it.
  • KYC_NOT_APPROVED (Live key only) → your org’s KYC isn’t approved yet.

Step 3 — Book

What: place the booking. Why the token: Book requires the quoteToken and takes the price from it. There is no amount field, so a price can’t be made up, changed or replayed. Why idempotency: Kitionz reserves the booking before it asks the supplier to book, and keys it on your Idempotency-Key, so a network retry returns the same booking instead of charging twice.

Send offerId, quoteToken, passengers and your own externalReference, with a new Idempotency-Key header saved with your order before you call. A real request and its recorded answer: Book.

The HTTP status is 201. Decide by data.status and data.nextAction.

Required — passengers:

  • passengerType is "ADULT" (12 or older), "CHILD" (2–11) or "INFANT" (under 2), by the passenger’s age on the date of the last flight (on a round trip, the return flight). Search with that count: an infant who turns 2 before the return flight travels as a CHILD. A passenger whose age does not fit the type is refused before any seller is called with 422 PASSENGER_FIELD_REQUIRED on passengers.N.dateOfBirth — for example “Passenger 2: This infant will be 2 or older on the return flight on 2026-12-20. Search again with this passenger as a child.” Fix it with a new search, not by changing Book: the passengers are bound to the search and the quote. No passenger may be born after the departure date. passengers must hold exactly the passengers you searched with; if you also send adults, children or infants, they must match it. At most 9 adults and children in one booking.
  • Infants sit on an adult’s lap: every INFANT needs guardianPassengerIndex, the position in passengers (0 = the first) of that ADULT. One infant per adult.
  • Names: send firstName and lastName for everyone. A traveller with one name: send it as both.
  • Titles: Mr, Mrs or Ms for adults; Master (boy) or Miss (girl) for children and infants. Kitionz sets gender from the title; with any other title (Dr, Prof), send gender (MALE or FEMALE).
  • Date of birth: a CHILD or INFANT always needs dateOfBirth. A domestic ADULT sent without one is sent to the seller with 1 January of (this year − 20), because some sellers refuse a booking without a date of birth. The booking never shows that date as the passenger’s: it answers dateOfBirth: null with dateOfBirthDefaulted: true (false for everyone else).

Required — international trips: collect what the offer needs before you take payment. On an offer whose isInternational is true, every passenger needs dateOfBirth, passportNumber, passportExpiryDate and nationality. The passport must be valid at least 6 months after the last flight. Send countries as two-letter codes (IN, not Indian). Leave passportIssuingCountry out unless the passport was issued by another country than nationality: Kitionz sends the nationality. A missing field answers 422 PASSENGER_FIELD_REQUIRED, a passport that expires too soon 422 PASSPORT_EXPIRY_TOO_SOON; both list every passenger and field in error.details.invalidFields, and nothing is sent to the seller.

Note — who the seller contacts: the seller receives your organisation’s registered email and mobile as the booking contact (your Kitionz account details), not booker.email; booker.phone is sent only when your organisation has no mobile on record. E-tickets, schedule changes and cancellations from the seller or the airline come to you: pass them on to your traveller. booker is your record of who made the booking; some sellers receive booker.name as the contact name. Phone numbers: an Indian mobile, 10 digits, +91 optional.

Required — other fields:

  • Send only the fields in the reference. Any other field is 400 VALIDATION_ERROR. Seats, meals, extra baggage, payment details, corporate or tour codes and frequent-flyer numbers cannot be sent at booking: no supplier receives them. bookingMode is "INSTANT" (the default: book now) or "HOLD".
  • Idempotency is the Idempotency-Key HEADER (required on /book) — there’s no body field for it. Make a new UUID for each booking, persist it, and send the same value only when you resend the same request.
  • externalReference is your own order number (up to 120 characters). Kitionz stores it and returns it on the booking. Send it on every booking: if you lose Book’s answer, GET /v1/flights/bookings?externalReference=ORDER-1001 finds the booking. Kitionz does not check it for uniqueness.
  • Test is lenient: Kitionz Test Air accepts any title, phone or country. Live sellers refuse what they cannot take (below), so follow these rules in Test too.
  • Names, booker details and passport fields are plain text: no control characters (tab, line feed and carriage return are allowed) and no half of an emoji. Kitionz refuses such a value with 400 VALIDATION_ERROR before anything is booked. A value pasted from a document can hide one — ask the customer to retype the field error.details.invalidFields names.

Critical: reuse the SAME idempotency value on retries. A new key = a new booking. If your /book request times out, send the identical request again with the same key: you get the first booking back, or 409 while it is still running. The booking is reserved before Kitionz asks the supplier to book, so a retry with the same key can never book twice. Book answers 201 when a booking was recorded, even one the supplier refused, except a refusal you can act on (402, 409, a 400 BOOK_… code): that comes as the error, and nothing was booked. Decide by data.status and do what data.nextAction says, not by the HTTP code — and never by pnr (a HELD booking already has a PNR and is not ticketed).

Which id to keep

FromKeepFor
SearchofferId (floffer_…)Fare quote and Book, until the offer’s expiresAt (about 15 minutes)
SearchsearchIdsupport only; never sent back
Fare quotequoteTokenBook, until quoteTokenExpiresAt; one booking per quote
You, before Bookyour Idempotency-Keysending Book again safely after a timeout
You, on BookexternalReferenceyour own order number; finds a booking whose answer you lost
Bookid (bkg_…)every call after booking — key your orders on it
Bookpnrthe airline’s booking code, for check-in and the airline’s website; it can arrive minutes after Book
BooksupplierReferencethe seller’s own booking number: quote it to the seller’s support

Key your orders on id, never on the PNR (two airlines can issue the same PNR). Kitionz does not return e-ticket numbers or the ticket document: get them from the seller named in the booking’s seller.name.

Book variants — confirm now vs hold-then-ticket

The fields of this call and a real answer are on its page: Book.

Note — What can go wrong here?

  • QUOTE_TOKEN_EXPIRED → the token’s ~15 min passed. Re-quote, show the (possibly new) price, book with the fresh token.
  • QUOTE_TOKEN_ALREADY_USED → this token already made a booking: error.details.existingBookingId and existingBookingStatus name it. Read that booking. Quote again and book with a new key only if it is FAILED.
  • IDEMPOTENCY_KEY_REQUIRED → the header is missing. Add Idempotency-Key: <uuid> (and persist the uuid on your order row).
  • SUPPLIER_INSUFFICIENT_BALANCE (402) → your balance with this seller is too low. Nothing was booked. If the seller refused at the booking call, a FAILED booking is left in GET /v1/flights/bookings with the seller in seller.name; if it refused when Kitionz checked the price again, no booking is left. Top up with the seller, then quote and book again with a new Idempotency-Key.
  • SUPPLIER_FARE_CHANGED_AT_BOOK, FARE_HIGHER_THAN_QUOTED, INSUFFICIENT_SEATS (409) → the fare or seats changed just before booking. Nothing was booked. Quote again and show the new price.
  • 400 BOOK_CONTACT_EMAIL_REQUIRED, BOOK_CONTACT_PHONE_UNSUPPORTED, BOOK_PASSENGER_TITLE_UNSUPPORTED, BOOK_PASSENGER_COUNTRY_UNSUPPORTED → this seller cannot take one detail as sent. Nothing was booked. Fix it and book again with a new Idempotency-Key.
  • CAPABILITY_NOT_SUPPORTED, HOLD_NOT_SUPPORTED_FOR_THIS_OFFER (422, Book with bookingMode: "HOLD") → this offer cannot be held. Book with bookingMode: "INSTANT".
  • PASSENGER_FIELD_REQUIRED, PASSPORT_EXPIRY_TOO_SOON (422) → a passenger misses what the offer needs. error.details.invalidFields lists each one.

Step 4 — Check the result

Critical — when to take the money. Take your customer’s payment just before /book (a quote lasts 15 minutes). Then act on the booking:

  • CONFIRMED or HELD → keep the payment.
  • FAILED, or any other 4xx from /book → refund it: nothing was booked.
  • RESERVED or PENDING_CONFIRMATION (nextAction.code: WAIT), a 5xx, no answer, 409 BOOKING_IN_PROGRESS or 409 IDEMPOTENT_REQUEST_IN_PROGRESS (the first request may still book) or 409 QUOTE_TOKEN_ALREADY_USED (a booking from this quote exists, in error.details.existingBookingId) → keep it: the booking may exist. Do not refund and do not book again: send the same request again with the same key, or read the booking, until it reads CONFIRMED or FAILED. Most settle within 10 minutes; still pending after 20 minutes, contact Kitionz support.

Kitionz takes no money for a booking: you pay the seller from your own account with that seller.

What: resolve the real outcome. Every booking carries nextAction (never null): a code, a plain description, and the call to make (method, path) when there is one. Decide by status and nextAction.code — never by pnr:

statusmeaningnextAction.codewhat you do
CONFIRMEDbooked and confirmed by the seller; pnr is setNONEdone — skip Step 5
HELDseats held with a pnr, not ticketedISSUE_TICKETissue the ticket (Step 5) before nextAction.deadlineAt
PENDING_CONFIRMATIONoutcome not known yetWAITread the booking again after nextAction.pollAfterSeconds (below)
PENDING_FULFILMENTa request, nothing bookedSUBMIT_PNRbook on the seller’s site before nextAction.deadlineAt, then POST the PNR to nextAction.path
FAILEDnot booked, nothing chargedSEARCH_AGAINfailure.code and failure.message say why; search and book again

A booking is FAILED only when the supplier clearly refused it (sold out, fare gone) or the request never reached the supplier. When the supplier’s answer is unclear — a timeout, an answer Kitionz cannot read, or the supplier accepted but Kitionz could not save its answer — the booking is PENDING_CONFIRMATION, never FAILED, and Kitionz checks it with the supplier. Do not book that trip again while it is PENDING_CONFIRMATION.

RESERVED reads like PENDING_CONFIRMATION (WAIT). A FAILED booking can have empty passengers and segments; its origin, destination, departureDate, policy and baggage say what was tried. Every status and its next action is in the Flights reference. Never book again while a booking is RESERVED or PENDING_CONFIRMATION.

Critical — Kitionz does not watch confirmed bookings for airline changes yet. SCHEDULE_CHANGED is not produced today. On a confirmed booking, CANCELLED_BY_SUPPLIER appears only after a call that asks the seller, such as POST /v1/flights/bookings/{id}/refresh, and the seller reports the booking cancelled; its nextAction.code is CONTACT_SUPPLIER. No webhook is sent for either. Sellers and airlines send changes to the booking contact — your organisation’s registered email and mobile. To catch changes, watch each booked flight with Flight Status (POST /v1/flight-status/monitors) and refresh the booking the day before departure.

Note — request bookings expire. A PENDING_FULFILMENT request carries its deadline in nextAction.deadlineAt: the earlier of 72 hours (by default) after it was booked and the first flight’s departure. After it, Kitionz may cancel the request at any time: it becomes CANCELLED, and /fulfil then answers 409 BOOKING_NOT_PENDING. Book on the seller’s site (seller.website) straight away — the fare there can go much sooner — with the same flights, date and passengers, then send the airline PNR. Kitionz records the quoted total; it does not see what you paid on the seller’s site.

Read it with Get a booking: its recorded answer is a booking Kitionz is still confirming (PENDING_CONFIRMATION, nextAction.code WAIT). A booking the supplier refused is FAILED, with failure.code (for example SUPPLIER_BOOK_FAILED) and nextAction.code SEARCH_AGAIN: nothing was booked and nothing charged.

The wording of description and failure.message can change. Branch on the codes.

The two failure modes — handle them differently

Critical: Never show the customer an error when a request times out. A real, billable booking may exist behind a timeout or a PENDING_CONFIRMATION.

  • No answer, a timeout or any 5xx from /book: you have no booking id yet. 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 a few seconds and send it again), or 409 QUOTE_TOKEN_ALREADY_USED naming the booking in error.details.existingBookingId — read that booking. Never use a new key.
  • 500 BOOKING_OUTCOME_UNKNOWN: the supplier may have booked, but Kitionz could not record its answer. error.details.bookingId names the booking when there is one — read it. Do not book that trip again; contact Kitionz support with the request_id.
  • PENDING_CONFIRMATION (you got a response): the booking may exist. With a supplierReference the supplier accepted it and Kitionz checks for the PNR; without one, Kitionz settles it with the supplier. Read GET /v1/flights/bookings/{id} every nextAction.pollAfterSeconds; most settle within 10 minutes. No webhook is sent for this change.
  • Still pending after 20 minutes: contact Kitionz support with the booking id and request_id. Keep the customer’s payment and do not book again.

Need a fresh pull from the supplier (not the stored booking)? Use POST /v1/flights/bookings/{id}/refresh — it asks the supplier directly and updates the stored booking. Use it only now and then, for example when your customer asks; for routine waiting, the plain GET is enough, because Kitionz already checks the supplier for you.

Read variants — read once, or wait for a pending booking

The fields of this call and a real answer are on its page: Get a booking.

Note — What can go wrong here?

  • Status stuck in PENDING_CONFIRMATION past 20 minutes → contact Kitionz support with the booking id and request_id; keep the customer’s payment and do not book again.
  • 404 on the booking id → you’re reading with a different key/org than the one that booked, or the id is mistyped (bkg_…).

Step 5 — Ticket (only if HELD)

Only Kitionz Test Air supports holds today; with your Live suppliers a booking never comes back HELD. A HELD booking must be ticketed before a supplier-imposed deadline. If Step 4 shows HELD, issue the ticket:

send POST /v1/flights/bookings/{id}/ticket with a new Idempotency-Key (Ticket a hold).

A CONFIRMED booking needs no ticketing call — skip this step. Asking again for a booking that is already CONFIRMED answers 200 with the booking and does not ask the supplier again. A 202 means the supplier accepted the request and has not finished: read the booking until it is CONFIRMED.

Note — What can go wrong here?

  • nextAction.deadlineAt passed → the seller may have released the seats, and Kitionz does not change the status. Ask the seller; if released, start a fresh search → quote → book.
  • BOOKING_NOT_TICKETABLE (409) → the booking is not HELD (for example FAILED or CANCELLED). Read it; nothing to ticket.
  • TICKETING_REFUSED (422) → the supplier refused to ticket. The booking stays HELD: try again before nextAction.deadlineAt, or contact the seller named in seller.name.

Cancel and refund in Live

Only Kitionz Test Air cancels and refunds confirmed bookings through the API today. On a Live booking, /cancel answers 422 CANCEL_WITH_SUPPLIER. A PENDING_FULFILMENT request can be withdrawn with /cancel on every supplier. With a Live booking:

  • Cancel with the seller named in the booking’s seller.name, by phone or email. The seller applies the fare’s penalty and refunds you to your account with them, on their timeline. Kitionz is not told and does not track the refund.
  • The Kitionz booking stays CONFIRMED: record the cancellation in your own system. If you later call /refresh and the seller reports the cancellation, the booking reads CANCELLED_BY_SUPPLIER — for a cancellation you asked for, that does not mean the airline cancelled.
  • The offer’s capabilities.cancellation says how cancelling works: REALTIME — /cancel cancels through Kitionz (only Kitionz Test Air today); OFFLINE — the seller cancels by phone or email; NONE — Kitionz has no cancellation path for this seller: ask the seller.

Before you go Live

Follow Going Live on the Flights overview: it lists every step, in order. Your integration is ready for Live when:

  • One new Idempotency-Key per write (booking, cancel…), persisted on your side for safe retries.
  • Your screens handle “not stated” (null, NOT_GIVEN): Live sellers leave many values unstated. Test Air does the same, so a screen that works in Test should hold.
  • Your passenger form follows the Live rules (titles, Indian mobile, two-letter countries, passports on international trips): Test Air accepts anything.
  • Handle PENDING_CONFIRMATION by reading the booking until it settles (nextAction.code: WAIT) — this is the difference between a clean integration and lost bookings.

When things go wrong

Every 4xx/5xx has the same envelope, so you can handle errors generically: error.type, error.code, error.message, error.doc_url and error.request_id (see the Introduction).

Give the request_id to support when you need help. Follow doc_url for help with the error: in Test it opens the public Error codes page; in Live, the error’s own page on the kitionz.com console (sign in first). The common booking-path codes and what to do about each:

codewhat it meansyour action
OFFER_EXPIREDthe offer’s expiresAt passedre-search to get a fresh offerId, then re-quote
PASSENGER_COUNTS_CHANGEDother passengers than the searchsearch again with the passengers you will book
QUOTE_TOKEN_EXPIREDthe ~15-min quote window passedre-quote; keep the customer’s selection, show the new price before charging
QUOTE_TOKEN_INVALIDthe token was altered or doesn’t match the requestnever change the token — re-quote fresh
FARE_HIGHER_THAN_QUOTEDthe fare rose above the quote at book timere-quote, show the new price; never auto-charge the difference
SUPPLIER_FARE_CHANGED_AT_BOOKthe seller refused any fare change at book timequote again and show the new price; nothing was booked
SUPPLIER_INSUFFICIENT_BALANCEyour balance with the seller is too lowtop up with the seller (a FAILED booking, when one is left, names it in seller.name), then quote and book again
PASSENGER_FIELD_REQUIREDa passenger misses a field the offer needsfill every field in error.details.invalidFields; collect them before payment
PASSPORT_EXPIRY_TOO_SOONa passport expires less than 6 months after the last flightthe traveller needs another passport for this trip
FARE_QUOTE_UNUSABLEthe supplier’s quote failed Kitionz’s checkchoose another fare or search again; the reason is in error.supplierError.failureClass (below)
FARE_RECHECK_FAILEDthe fare could not be re-confirmed at book timequote again; if the infant price changed, adjust your own infant charge (below)
SUPPLIER_DISABLED_BY_TENANTyour organization has this supplier switched offswitch it on under Suppliers (one switch covers staff + API)
IDEMPOTENCY_KEY_REQUIREDa write arrived without the headersend Idempotency-Key: <uuid> on writes
VALIDATION_ERRORa field is missing or not allowedread error.details.invalidFields: each entry names the field (passengers.0.firstName) and what is wrong; a pasted name can hide a control character — have it retyped
KYC_NOT_APPROVEDyour org isn’t approved for productionorg-level — finish KYC
IP_NOT_ALLOWEDthe caller IP isn’t on the key’s allow-listadd the IP to the key’s allow-list (Live keys only)

FARE_QUOTE_UNUSABLE (422). Kitionz checks every fare quote, and the fare again just before booking. A fare that fails is not quoted or booked, and no money moves. error.supplierError.failureClass says why: TOTAL_NOT_POSITIVE (the total is ₹0 or less, or not a number), CURRENCY_NOT_INR, TOTAL_ABOVE_CEILING (far above any real fare), PRICED_FOR_ANOTHER_PARTY (priced for a different number of passengers: search again with the passengers you will book), PASSENGER_NOT_PRICED (an adult or child has no price), LINES_DO_NOT_ADD_UP (the per-passenger prices do not add up to the total). A missing infant price is never this error: the quote comes back with price.infantFare.status: NOT_GIVEN.

FARE_RECHECK_FAILED (409). Kitionz could not confirm this fare as quoted just before booking. Nothing was booked or charged. The reason is in error.supplierError.failureClass. What to do:

  1. Call /fare-quote again. If it succeeds, book with the new quoteToken.
  2. SUPPLIER_UNAVAILABLE: the supplier is failing right now, so Kitionz did not ask it. Wait about 30 seconds before you try again.
  3. INFANT_PRICE_WITHDRAWN: you were quoted an infant price and the supplier gives none now. The new quote shows infantFare.status: NOT_GIVEN, and its totalAmount covers adults and children only, so add your own infant charge before you book.
  4. INFANT_PRICE_APPEARED: your quote said NOT_GIVEN and the supplier now gives an infant price. The new quote shows PRICED with the infant inside totalAmount, so remove your own infant charge before you book.
  5. If /fare-quote keeps failing, search again or choose another offer.

See the full Error codes reference for every code, its meaning, and the fix.