Create a flight booking

Create a flight booking, optionally attaching a stored travel document and selecting protection-plan quotes.

Failure contract after the provider accepts the create: every validation that can produce a 4xx runs before the single provider create call. Once the provider has accepted the create, a failure in a later step (for example correlating the new booking to a trip) never surfaces as another definitive 4xx — it maps to a 5xx, or to 409 when it is a genuine conflict. A client that receives a definitive 4xx (anything other than 401, 408, 409, or 429) can safely assume no provider booking was created. What the provider itself guarantees when it persists a booking and then answers 4xx is not independently verified by Mercury.

Replacement booking after a selection-hold renewal: for a guest checkout session whose hold was renewed (POST /v1/checkout/sessions/current/selection-hold), create the replacement with fareId set to the renewal's renewedFareId. It is accepted even though the session already has a bookingId: when that earlier booking was superseded by the renewal, the new booking becomes the session's bookingId. The superseded booking is abandoned, not voided, and POST /v1/{vertical}/bookings/{booking_id}/confirm rejects it with 409 BOOKING_FARE_SUPERSEDED. A booking created on any other fare does not replace the session's bookingId.

Guest checkout traveler details: for a booking created in a guest checkout session, a companion passenger's birthday and nationality and the booking phone that the request omits are filled from the details the guest captured in that session. Values sent in the request always take precedence, and a signed-in traveler's booking is never altered.

No per-session concurrency guard: Mercury applies no per-checkout-session single-flight lock on create. Two concurrent creates for the same guest checkout session (for example two browser tabs) can both reach the provider; only the provider's own idempotency/state-machine behaviour — not yet verified from Mercury — can prevent two bookings from being created.

Recent Requests
Log in to see full request history
TimeStatusUser Agent
Retrieving recent requests…
LoadingLoading…
Body Params

Flights-only booking creation request.

Extends :class:CreateBookingRequest with flight-specific fields:

  • documentId — optional Mercury UUID of a stored travel document
    from GET /v1/profile/documents. The document is validated for
    ownership and its internal ID is forwarded as travel_document_id
    on the booking holder passenger.
  • insuranceQuotes — optional list of Mercury protection-plan UUIDs
    obtained from GET /v1/flights/fares/{fareId}/protection-plans
    (MER-321). UUIDs are translated to provider quote IDs at the route
    boundary; a stale or unrecognised UUID results in 410 QUOTE_EXPIRED.
    Resolved quote IDs are forwarded inline on the booking-create request
    so the protection plan binds atomically.
  • ancillaries — typed as :class:FlightAncillarySelectionIn (via
    :class:FlightAncillarySelectionMixin, listed first so it wins over
    :class:CreateBookingRequest's untyped list[dict[str, object]] | None in the MRO) instead of an opaque {[key: string]: unknown}
    blob.
  • seats — typed seat selection per trip and leg, forwarded upstream
    when present; omitted from the upstream body when absent so seatless
    bookings stay byte-identical.

This schema is intentionally limited to POST /v1/flights/bookings
so that documentId, insuranceQuotes, the typed ancillaries
shape, and seats do not appear in the OpenAPI schemas for stays,
trains, or cars.

string
required

Locked fare ID from the search session.

passengers
array of objects

Passenger details. Exactly one adult must have isHolder=true.

Passengers
extraServices
array of objects
deprecated

Deprecated: send all ancillary selections via ancillaries. Still accepted for back-compat; ancillaries takes precedence.

Extraservices
array | null

Unified ancillary selection input. Takes precedence over extraServices when both are provided. On booking-create, Mercury books exactly these selections — the same ids calculate-costs already validated resolve here as long as the fare has not expired. On calculate-costs, totalConfirmed reflects every selection priced; whether a per_passenger ancillary's contribution is already multiplied by party size is provider-computed and not independently asserted by Mercury — treat totalConfirmed as authoritative rather than re-deriving a per-passenger split from it. Use passengerCount for a client-side per-passenger display only.

requestServices
array of objects
deprecated

Deprecated (cars only): pay-at-desk selections now belong in the unified ancillaries field, which Mercury routes by ancillary kind. Still accepted for back-compat.

Requestservices

Optional trip to associate this booking with. When provided, a TripSegment record is persisted linking the booking to the trip.

Whether this is a personal (non-business) booking.

Contact phone number for the booking.

Invoice profile to use for this booking. Omitting the key and sending an explicit null are equivalent: Mercury resolves both to the account's first available invoice profile (the same list GET /v1/invoice-profiles/booking returns) before the request reaches the provider, so neither shape is ever forwarded to the provider as a null or missing value. A client that has already defaulted this field to null (for example a generated TypeScript client with no way to omit an optional key it has serialized) still gets auto-resolution — it does not need to strip the key to opt in. When the account has no invoice profile at all, booking creation fails with a 422 NO_INVOICE_PROFILE error before any provider call is made — no booking is created and no fiscal documents are issued. Do not assume a newly created guest-checkout account (from the magic-link exchange) has a default invoice profile at the moment of its first booking — Mercury's own account provisioning does not create one, so that first booking should be expected to hit this error unless a real invoiceProfileId is supplied explicitly. Whichever profile is resolved cannot be changed after create — there is no way to point an existing booking at a different profile. Whether an edit to this profile (PATCH /v1/invoice-profiles/{id}) made after this booking is created — including appending an emailRecipients entry — is reflected in the invoice this booking eventually generates is not currently documented or guaranteed. Do not design a flow that depends on a post-create edit reaching this booking's invoice.

Billing address to associate with this booking. Omitting the key and sending an explicit null are equivalent, following the same null-as-absent rule as invoiceProfileId: both are resolved from the resolved invoice profile's default billing address before the request reaches the provider. That defaulting applies to stays and flights; for cars and trains the key is left off the request to the provider entirely rather than sent as null.

A flight booking needs a billing address to be confirmed. The provider requires one to finalize and reports it missing only at POST /v1/flights/bookings/{id}/confirm, by which point the booking already exists and can never be confirmed. So when a flight create resolves no billing address — none sent here, and the resolved invoice profile has no default — it fails with 422 BILLING_ADDRESS_REQUIRED (recoveryAction: FIX_INPUT, field error on this field) before any provider call, and no booking is created. Send a billingAddressId from GET /v1/billing/addresses, or an invoiceProfileId whose defaultBillingAddressId is set. A newly provisioned guest-checkout account has neither, so a guest flight booking must supply this field. Cars, trains and stays are not subject to this check.

object | null

Answers to the account's custom fields for this vertical, keyed by definition key (from GET /v1/custom-fields). Values: string for text/single_select (option key) and date (YYYY-MM-DD), list of option keys for multi_select. Answers are validated against the account's definitions; any invalid answer fails the request with a field-level 422. An empty value ("" or []) means unanswered and is stripped at the boundary, so it is treated identically to omitting the key.

length ≤ 2000

A message for the approver, for a traveler whose company requires one on bookings that need approval (see approvalMessageRequired on GET /v1/profile/identity). Passed on to the approver, stored on the booking and returned as approvalMessage on its detail. Optional; blank counts as missing. When the company requires one and it is missing, create fails with 422 APPROVAL_MESSAGE_REQUIRED.

Mercury UUID of a stored travel document (from GET /v1/profile/documents). When provided the document is validated for ownership and forwarded as travel_document_id on the holder passenger.

array | null

Optional list of protection-plan Mercury UUIDs from GET /v1/flights/fares/{fareId}/protection-plans. UUIDs are translated to provider quote IDs before forwarding.

array | null

Seat selection per trip and leg. Same typed shape as calculate-costs seats. When omitted, the seats key is not sent upstream.

Headers

Client-generated idempotency key (UUID v4 recommended). Required on all booking mutation requests. Whether a repeated key replays the first response depends on the operation; see its description and the idempotency and retries guide.

Responses

401

Missing or invalid authentication, or the provider rejected the forwarded travel-document credential

403

User lacks booking capability for this vertical, or the provider denied the travel-document lookup (resolved deny)

404

Fare not found or expired, or tripId inaccessible

409

Booking already exists (conflict), or a colleague document selection token is no longer eligible (reload the options)

410

Fare has expired (refresh search)

422

Validation error, invalid documentId, missing passenger document, missing header, APPROVAL_MESSAGE_REQUIRED, or SEAT_REQUIRED

503

Booking or travel document service not configured

Language
Credentials
LoadingLoading…
Response
Click Try It! to start a request and see the response here! Or choose an example:
application/json