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 |
— |
1. Search
slices: 1 for one-way, 2 for a return, up to 6 for multi-city. Each hasorigin,destination(IATA airport or city codes) anddepartureDate(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
offerslist is a normal result (no availability), not an error.warningstells 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.
2. Price (recommended before booking)
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.
firstNamemay include middle names. birthDateis 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 withstatus,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.