Managing booking states
Airline bookings are not instant or final. Tickets are sometimes confirmed minutes later, holds expire, airlines change schedules, refunds need a quote. This page explains how to keep your system in step with every booking — without guessing.
The golden rule: every booking response tells you what is happening (status, pendingReason), what you may do next (nextActions), until when (deadlines), and how long to wait (pollAfterSeconds). Build your logic on those fields rather than on assumptions.
Statuses
status |
isFinal |
Meaning | Typical nextActions |
|---|---|---|---|
held |
no | Seats held, nothing paid yet. | issue, cancel — both until deadlines.holdExpiresAt |
issuing |
no | Money reserved; the airline is issuing tickets. | wait |
ticketed |
no | Tickets issued. Can still change through servicing. | download_ticket, request_void (until deadlines.voidUntil), request_refund, request_change, accept_quote, reply_schedule_change |
completed |
yes | The journey is over. | — |
cancelled |
yes | Hold released or expired, or tickets cancelled/refunded. | — |
failed |
yes | Could not be ticketed. Any money taken was returned to your wallet. | search_again |
held ──issue──► issuing ──► ticketed ──► completed
│ │ │
│ └─► failed └─(refund/void)─► cancelled
└──cancel / hold expired──► cancelled
statusReason adds detail when there is some, e.g. hold_expired.
New statuses or reasons may be added in the future; treat unknown values as "not final, check nextActions".
The booking response, annotated
{
"id": "7c1e0a52-…",
"status": "issuing",
"isFinal": false,
"pendingReason": "supplier_confirmation_pending",
"pollAfterSeconds": 30,
"nextActions": [ { "action": "wait", "method": null, "href": null, "deadline": null } ],
"deadlines": { "holdExpiresAt": null, "voidUntil": null },
"openServicingRequests": [],
"openScheduleChanges": [],
"lastEventAt": "2026-11-20T10:31:02Z"
}
| Field | Use |
|---|---|
nextActions[] |
The only actions allowed now: action, the HTTP method and href (relative to the base URL) to call, and the deadline after which it is no longer possible. If an action is not in the list, don't offer it to your user. |
deadlines.holdExpiresAt |
Issue before this, or the seats are released (flight.booking.hold_expiring webhook comes shortly before). |
deadlines.voidUntil |
Until when a ticket can usually be voided without airline penalty (end of the issue day). |
pendingReason |
Why a non-final booking is waiting (below). |
pollAfterSeconds |
When polling, wait at least this long before the next GET. Null when there is nothing to wait for. |
lastEventAt |
Timestamp of the latest change — handy to detect changes cheaply. |
Pending reasons
pendingReason |
Status | What is happening | What you do |
|---|---|---|---|
supplier_confirmation_pending |
issuing |
The airline has not confirmed the tickets yet. Usually seconds to a few minutes. | Wait. Webhook flight.booking.ticketed / failed, or poll every pollAfterSeconds (30 s, later 120 s). |
manual_ticketing_queue |
issuing |
Still not confirmed after 20 minutes; our ticketing desk took over. You also get flight.booking.attention_required. |
Keep waiting (poll every 300 s). Tell your customer the ticket is being finalised. Contact support if urgent. |
awaiting_quote_decision |
ticketed |
A refund/void/change request has a quote waiting for your answer. | accept_quote (see nextActions) or decline, before the quote's deadline. |
servicing_in_progress |
ticketed |
A servicing request is being handled by the desk. | Wait for flight.post_booking_request.updated. |
awaiting_schedule_change_reply |
ticketed |
The airline changed the schedule and needs acceptance. | reply_schedule_change before its deadline, or request a refund/change. |
Asynchronous ticketing — the rules
Issuing is where money moves, so it is where integrations most often go wrong. Follow these rules and you can never double-ticket or lose a booking:
- Send
issueonce, with anIdempotency-Key. If you need to retry (timeout, 5xx), retry with the same key. 200withstatus: issuingis a success. Payment is reserved and the airline is working on it. Show "ticket being issued" to your customer.- Never call
issueagain with a new key to "push" it. (If you do, you simply get the current booking back — but it signals a bug in your flow.) - Wait for the result:
- Recommended: the
flight.booking.ticketedorflight.booking.failedwebhook. - Fallback: poll
GET /flights/bookings/{id}, waitingpollAfterSecondsbetween calls. - Best: both — webhooks for speed, a slow poll (e.g. every 10 minutes) of
GET /flights/bookings?needsAction=trueas a safety net.
- Recommended: the
- If your
issuecall timed out with no response,GETthe booking:issuingorticketedmeans it went through;heldmeans it did not — issue again (same key is fine). failedis final and safe: the reserved money is back in your wallet. Offer your customer another flight.
Your work queue: needsAction
GET /flights/bookings?needsAction=true lists the bookings that are waiting on you or on the airline — held bookings to issue or cancel before their deadline, bookings still issuing, quotes to decide and schedule changes to answer. Check it regularly (or build a screen for your staff on it); an empty list means nothing is pending.
Hold expiry
flight.booking.hold_expiringis sent shortly beforeholdExpiresAt.- After
holdExpiresAtthe booking showsstatusReason: hold_expiredandnextActions: [search_again]; soon after it turnscancelled(flight.booking.cancelled). - An expired hold cannot be issued (
flights.hold_expired). Search again.
After ticketing
A ticketed booking can still change:
- Servicing — refunds, voids, date changes: see Refunds, changes & schedule changes.
openServicingRequestslists requests in progress. - Schedule changes by the airline:
openScheduleChanges, webhookflight.booking.schedule_changed. - Partial changes —
passengers[].stateisactive,cancelled(refunded individually) ormoved(split to another booking).
The timeline
GET /flights/bookings/{id}/events returns everything that happened, oldest first — useful for support and for auditing:
[
{ "type": "booking.held", "at": "2026-11-20T10:25:14Z", "actor": "agency_api", "note": null },
{ "type": "ticketing.started", "at": "2026-11-20T10:26:02Z", "actor": "agency_api", "note": "Payment reserved from the wallet; the airline is issuing." },
{ "type": "booking.ticketed", "at": "2026-11-20T10:27:40Z", "actor": "system", "note": "PNR X7K2QP" }
]
Checklist for a robust integration
- Store
idand youragencyReferencetogether before callingissue. - Use
Idempotency-Keyon every write and reuse it on retries. - Treat
issuingas success-in-progress; never re-issue. - Drive your UI from
nextActionsanddeadlines. - Handle
failed(money returned) andcancelledas final. - Subscribe to webhooks and run a slow
needsActionpoll as a safety net. - Test every path on your platform's test server before going live.