View .mdDownload all for AI

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:

  1. Send issue once, with an Idempotency-Key. If you need to retry (timeout, 5xx), retry with the same key.
  2. 200 with status: issuing is a success. Payment is reserved and the airline is working on it. Show "ticket being issued" to your customer.
  3. Never call issue again 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.)
  4. Wait for the result:
    • Recommended: the flight.booking.ticketed or flight.booking.failed webhook.
    • Fallback: poll GET /flights/bookings/{id}, waiting pollAfterSeconds between calls.
    • Best: both — webhooks for speed, a slow poll (e.g. every 10 minutes) of GET /flights/bookings?needsAction=true as a safety net.
  5. If your issue call timed out with no response, GET the booking: issuing or ticketed means it went through; held means it did not — issue again (same key is fine).
  6. failed is 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_expiring is sent shortly before holdExpiresAt.
  • After holdExpiresAt the booking shows statusReason: hold_expired and nextActions: [search_again]; soon after it turns cancelled (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. openServicingRequests lists requests in progress.
  • Schedule changes by the airline: openScheduleChanges, webhook flight.booking.schedule_changed.
  • Partial changes — passengers[].state is active, cancelled (refunded individually) or moved (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 id and your agencyReference together before calling issue.
  • Use Idempotency-Key on every write and reuse it on retries.
  • Treat issuing as success-in-progress; never re-issue.
  • Drive your UI from nextActions and deadlines.
  • Handle failed (money returned) and cancelled as final.
  • Subscribe to webhooks and run a slow needsAction poll as a safety net.
  • Test every path on your platform's test server before going live.