View .mdDownload all for AI

Flight booking flow

 search ──► price (optional) ──► hold ──► issue ──► ticketed
   │            │                  │         │
   │            │                  │         └─► issuing ──(webhook/poll)──► ticketed | failed
   │            │                  └─► cancel (free, before holdExpiresAt)
   │            └─► priceChanged: show the new price first
   └─► offers expire (expiresAt): search again
Step Endpoint Scope Money
Search POST /flights/search flights:read —
Re-price one offer POST /flights/offers/{offerId}/price flights:read —
Fare rules GET /flights/offers/{offerId}/fare-rules flights:read —
Hold POST /flights/bookings flights:book nothing charged
Issue POST /flights/bookings/{id}/issue flights:book amountDue taken from wallet
Cancel a hold POST /flights/bookings/{id}/cancel flights:book nothing charged
Read GET /flights/bookings/{id}, GET /flights/bookings flights:read —
  • slices: 1 for one-way, 2 for a return, up to 6 for multi-city. Each has origin, destination (IATA airport or city codes) and departureDate (yyyy-MM-dd, local).
  • passengers: adults (1–9), children (2–11 years), infants (under 2, not more than adults).
  • Optional: cabin (economy, premium_economy, business, first), directOnly, preferredAirline, nationality, domestic, currency, maxResults (1–200, default 50).
  • Offers are sorted cheapest first. Each fare family (brand) of the same flights is its own offer.
  • An empty offers list is a normal result (no availability), not an error. warnings tells you when some airlines did not answer.

Offers expire. Use an offerId before its expiresAt (usually 15–30 minutes). An expired or unknown offer returns flights.offer_expired / flights.offer_not_found: search again.

Only live searches count towards your search quotas and look-to-book ratio. Cache results for identical searches.

POST /flights/offers/{offerId}/price asks the airline for the current price of one offer. If priceChanged is true, the offer now carries the new price — show it to your customer. Booking then uses the new price.

GET /flights/offers/{offerId}/fare-rules returns refund/change/no-show conditions (penalties) and, when available, the airline's full rule text. Show at least refundability and baggage before the customer pays.

3. Hold

POST /flights/bookings with the offerId, the passengers (same counts and types as the search), a contact and optionally your agencyReference. An Idempotency-Key header is required.

Passenger rules:

  • Names in Latin letters exactly as on the passport. firstName may include middle names.
  • birthDate is required for children and infants (and recommended for all).
  • On international trips a passport (document) is required, valid beyond the travel date.
  • The first adult is the lead passenger.

Result: 201 with the booking in status held. Common errors:

Code Meaning What to do
flights.price_changed (409) The airline repriced above tolerance. Nothing was booked. Price or search again, confirm the new price, hold again.
flights.fare_not_available (409) Sold out since the search. Search again.
flights.offer_already_booked (409) You already held this offer (with a different Idempotency-Key). Use the existing booking: context.bookingId.
flights.passenger_data_invalid (422) Names, dates or documents rejected. Fix the fields in errors.
flights.duplicate_booking (409) Same passenger on the same flight already. Use or cancel the existing booking.
flights.supplier_timeout / supplier_unavailable Airline did not answer. retryable: true. Retry with the same Idempotency-Key.

4. Issue

POST /flights/bookings/{id}/issue with a new Idempotency-Key. Before that, you can check GET /account/balance.

Outcome Status Meaning
200, status: ticketed Done pnr, ticketNumbers, passengers[].ticketNumber filled.
200, status: issuing Waiting on the airline Wait for the webhook or poll — see Managing booking states.
402 account.insufficient_balance Not enough money context.shortfall says how much. The hold is kept; top up and issue again before holdExpiresAt.
409 flights.hold_expired Too late Search again.
409 request.invalid_state_transition Wrong status context.currentStatus and context.allowedActions tell you what is possible.

Calling issue on a booking that is already issuing or ticketed returns the booking (200) and never tickets twice.

5. Cancel a hold

POST /flights/bookings/{id}/cancel releases the seats of a held booking; nothing is charged. A ticketed booking cannot be cancelled this way (flights.use_post_booking_request): request a refund or void instead — see Refunds, changes & schedule changes.

6. Read and list

  • GET /flights/bookings/{id} — the full booking with status, nextActions, deadlines, passengers, slices, price and open servicing.
  • GET /flights/bookings/{id}/events — the timeline (held, payment reserved, ticketed, cancellations, servicing, schedule changes).
  • GET /flights/bookings — newest first, filters: status, needsAction=true, agencyReference, pnr, createdFrom/createdTo, departureFrom/departureTo, page, pageSize (≤ 100).
  • GET /flights/bookings/{id}/documents/e-ticket — PDF, once ticketed.