Booking Tickets ​

This guide covers the complete booking process, from creating a booking to receiving confirmed tickets.

Understanding the Booking Lifecycle ​

The booking process includes these stages:

  1. Booking: A temporary reservation that holds your selected offers. Bookings expire if not converted to orders within a set time (check the expiresAt field).

  2. Order: The confirmed purchase. Creating an order pre-books the tickets with the train operator. At this point, tickets are reserved but not yet issued.

  3. Finalized Order: Completes the purchase and triggers ticket issuance. After finalization, tickets are automatically generated.

  4. Tickets Issued: Digital tickets become available for download. This usually happens within 15 minutes of finalization.

Creating a Booking ​

Start by creating a booking from one or more offer IDs. This verifies availability but doesn't reserve them yet.

You can optionally pass passengers placeholders (type, age or birth date, travel cards, corporate codes and cost center) when creating the booking. Names, contact details and documents can't be set here; they are always added afterward with updateBooking. The passenger types, ages or birth dates, travel cards and corporate codes must match those used when fetching the original offer. For YOUTH and SENIOR passengers, you must provide either birthDate or age to validate fare eligibility.

Example: Create a booking
graphql
mutation CreateBooking {
  createBooking(
    offerIds: ["offer_v024z3z6a093q5a4y2r3e0n044"]
    passengers: [
      { type: ADULT }
      { type: YOUTH, age: 22 }
    ]
  ) {
    id
    passengers {
      id
      age
      type
    }
    requirements {
      passportNumber
      dateOfBirth
      nationality
      email
      tel
    }
    selections {
      ... on JourneyOfferSelection {
        offers {
          ...OfferFragment
        }
        journey {
          ... on JourneySelection {
            itinerary {
              ... on Stopover {
                location {
                  name
                }
              }
              ... on SegmentCollection {
                status
                segments {
                  departureAt
                  arrivalAt
                  origin {
                    name
                  }
                  destination {
                    name
                  }
                }
                offers {
                  ...OfferFragment
                }
              }
            }
          }
        }
      }
    }
  }
}

fragment OfferFragment on Offer {
  id
  price {
    amount
    currency
  }
  parts {
    ... on AdmissionPart {
      conditions {
        __typename
        description
      }
      flexibility
      serviceClass
      comfortClass
    }
    ... on ReservationPart {
      conditions {
        __typename
        description
      }
      flexibility
      comfortClass
      accommodation {
        type
      }
    }
  }
}
mutation CreateBooking {
  createBooking(
    offerIds: ["offer_e09033y2v3c5a2x3y1h7w46345"]
    passengers: [
      { type: ADULT }
      { type: YOUTH, age: 22 }
    ]
  ) {
    id
    passengers {
      id
      age
      type
    }
    requirements {
      passportNumber
      dateOfBirth
      nationality
      email
      tel
    }
    selections {
      ... on JourneyOfferSelection {
        offers {
          ...OfferFragment
        }
        journey {
          ... on JourneySelection {
            itinerary {
              ... on Stopover {
                location {
                  name
                }
              }
              ... on SegmentCollection {
                status
                segments {
                  departureAt
                  arrivalAt
                  origin {
                    name
                  }
                  destination {
                    name
                  }
                }
                offers {
                  ...OfferFragment
                }
              }
            }
          }
        }
      }
    }
  }
}

fragment OfferFragment on Offer {
  id
  price {
    amount
    currency
  }
  parts {
    ... on AdmissionPart {
      conditions {
        __typename
        description
      }
      flexibility
      serviceClass
      comfortClass
    }
    ... on ReservationPart {
      conditions {
        __typename
        description
      }
      flexibility
      comfortClass
      accommodation {
        type
      }
    }
  }
}

The booking includes requirements that tell you what passenger information is needed. Different train operators require different details. Some need passport numbers, others just names and dates of birth.

Booking expiration

Bookings expire if not converted to orders. Check the expiresAt field to see how long you have.

Updating Passenger Details ​

Before you can create an order, ensure that all required passenger information is provided. The booking's requirements field shows exactly what's needed.

Contact person

Exactly one passenger must be marked as the contact person, with both email and tel. On a booking split across cost centers, each cost center needs its own contact person. The contact person's details are forwarded to the train operator, who contacts them if there are any issues.

Travel cards and corporate codes can't be changed

A booking is priced on its passengers' travel cards and corporate codes, so leave travelCards and corporateCodes out when updating passengers. To book with a different card or code, create a new booking from a fresh offer. See Travel Cards and Corporate Codes.

Example: Updating passenger details
graphql
mutation UpdatePassengers {
  updateBooking(
    id: "booking_x2y0603380x4y295q0c134d641"
    passengers: [
      {
        id: "bf684b18-f767-4b18-a790-e7e50d81db32"
        email: "jane.doe@example.com"
        tel: "+46701234567"
        isContactPerson: true
        birthDate: "1980-02-15"
        nationality: "NL"
        firstName: "Jane"
        lastName: "Doe"
      }
    ]
  ) {
    id
    passengers {
      ... on FullPassenger {
        id
        isContactPerson
        email
        tel
        birthDate
        nationality
        firstName
        lastName
        title
      }
    }
  }
}
mutation UpdatePassengers {
  updateBooking(
    id: "booking_x2y0603380x4y295q0c134d641"
    passengers: [
      {
        id: "bf684b18-f767-4b18-a790-e7e50d81db32"
        email: "jane.doe@example.com"
        tel: "+46701234567"
        isContactPerson: true
        birthDate: "1980-02-15"
        nationality: "NL"
        firstName: "Jane"
        lastName: "Doe"
      }
    ]
  ) {
    id
    passengers {
      ... on FullPassenger {
        id
        isContactPerson
        email
        tel
        birthDate
        nationality
        firstName
        lastName
        title
      }
    }
  }
}

Selecting Offers ​

The offers you pass to createBooking via offerIds are used as the initial selection. Each journey segment may have additional offers available with different fare types or service levels. You can change these selections by updating the booking (updateBooking):

Example: Select specific offers
graphql
mutation SelectOffer {
  updateBooking(
    id: "booking_x2y0603380x4y295q0c134d641"
    selections: [{ offer: { id: "offer_c2z1325741r3x39194q7k44263" } }]
  ) {
    selections {
      ... on JourneyOfferSelection {
        offers {
          id
          price {
            amount
            currency
          }
          parts {
            ... on AdmissionPart {
              flexibility
              serviceClass
              comfortClass
            }
            ... on ReservationPart {
              flexibility
              comfortClass
              accommodation {
                type
              }
            }
          }
        }
      }
    }
  }
}
mutation SelectOffer {
  updateBooking(
    id: "booking_x2y0603380x4y295q0c134d641"
    selections: [{ offer: { id: "offer_54a5f2z613e5s6z5q0z6w5b3t3" } }]
  ) {
    selections {
      ... on JourneyOfferSelection {
        offers {
          id
          price {
            amount
            currency
          }
          parts {
            ... on AdmissionPart {
              flexibility
              serviceClass
              comfortClass
            }
            ... on ReservationPart {
              flexibility
              comfortClass
              accommodation {
                type
              }
            }
          }
        }
      }
    }
  }
}

Offer Sets ​

Some offers cannot be selected on their own: they belong to a set that spans several parts of a journey. Those parts are the journey's segment collections, each one a series of segments ticketed together. One rule covers every set: select an offer from it for every collection it covers, or nothing from it at all. A set can cover any number of a journey's collections.

What a set looks like ​

On some journeys the set is one priced combination holding a single offer per collection. A true return ticket is priced as a whole, so it arrives as a full-price offer on one direction and a companion offer at no extra cost on the other: those exact offers are the selection.

On other journeys the trip is only sold whole, and every offer of every collection shares one set. You still choose freely there: pick whichever offer you like on each collection, but cover them all. What the set forbids is leaving a collection out, not switching between the offers on one.

Offers belonging to a set carry memberOf with the id of that set; ordinary offers, which combine freely, leave it null. The field is also how you tell that an offer costing nothing is part of a set whose fare is charged on another collection, rather than a free ticket you can add on its own.

Read memberOf where you read the offers themselves, on the segment collections returned by journeyOffer, so you can group them before you select anything:

graphql
itinerary {
  ... on SegmentCollection {
    offers {
      id
      price {
        amount
        currency
      }
      memberOf {
        id
      }
    }
  }
}
itinerary {
  ... on SegmentCollection {
    offers {
      id
      price {
        amount
        currency
      }
      memberOf {
        id
      }
    }
  }
}

A set id names the product its offers are part of, for as long as those offers last. It is not an address: selections are always made with an offer's own id, and a set id is never sent back. Two products priced identically are two sets, so equal prices are no reason to expect equal ids. A fresh search also returns fresh offers, which name their product with a new id. Don't store a set id or compare it against one from an earlier search.

Selecting from a set ​

A set is checked against the booking as a whole, so send your picks for every collection it covers in one call: one offerIds list when creating a booking, one selections list when updating one. Covering the set one collection at a time leaves the booking incomplete in between, and each call is rejected.

Some collections have no offers outside a set at all: everything available for them belongs to one. Such a collection can only be booked through the set: there is no offer outside the set to choose instead, and leaving it unbooked is rejected as well.

A whole round trip can be like that. Some routes are sold only as a return, so every offer on both directions belongs to a set, and each set is a priced pair: one offer and its companion on the other direction. Pick one set and select both its halves in one call: there is no way to buy just one direction, and no way to book the trip without a set.

Selecting part of a set is rejected: see OFFER_SET_INCOMPLETE and OFFER_SET_CONFLICT. Where the rest of a set is no longer on offer at all, creating a booking is rejected as OFFER_SET_UNAVAILABLE.

Selecting Seat Preferences ​

Some offers include place properties: options for seat preferences, sleeping accommodations, or special requirements. These might include:

  • Window or aisle seat preference
  • Upper or lower berth (for night trains)
  • Special meal requirements
  • Accessibility needs

Some place properties are required (you must select an option), while others are optional. Check the required field to see which ones you need to set. Use updatePart to set place properties.

Example: Select seat preferences
graphql
query GetBooking {
  node(id: "booking_x2y0603380x4y295q0c134d641") {
    ... on Booking {
      selections {
        ... on JourneyOfferSelection {
          offers {
            parts {
              ... on ReservationPart {
                id
                placeProperties {
                  id
                  type
                  required
                  availableOptions {
                    id
                    value
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
query GetBooking {
  node(id: "booking_x2y0603380x4y295q0c134d641") {
    ... on Booking {
      selections {
        ... on JourneyOfferSelection {
          offers {
            parts {
              ... on ReservationPart {
                id
                placeProperties {
                  id
                  type
                  required
                  availableOptions {
                    id
                    value
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
graphql
mutation UpdatePart {
  updatePart(
    id: "a22c823e-716d-4bac-9a0e-d98df1b34211"
    booking: "booking_x2y0603380x4y295q0c134d641"
    placeProperties: [{
      propertyId: "cf6c8ddc-2aa6-4d3f-b68b-9751f8862d85",
      optionId: "e1c6a75a-7c9d-4369-a75a-33489e65a2ed"
    }]
  ) {
    selections {
      ... on JourneyOfferSelection {
        offers {
          parts {
            ... on ReservationPart {
              id
              placeProperties {
                selectedOption {
                  id
                  value
                }
              }
            }
          }
        }
      }
    }
  }
}
mutation UpdatePart {
  updatePart(
    id: "66ccc52d-2c19-4bce-aaa4-8b4d920c147e"
    booking: "booking_x2y0603380x4y295q0c134d641"
    placeProperties: [{
      propertyId: "709f3d92-25dd-4b06-9997-1841c60c7d7d",
      optionId: "65e39f60-d9a1-434c-8597-083328dfbe7f"
    }]
  ) {
    selections {
      ... on JourneyOfferSelection {
        offers {
          parts {
            ... on ReservationPart {
              id
              placeProperties {
                selectedOption {
                  id
                  value
                }
              }
            }
          }
        }
      }
    }
  }
}

Choosing How Tickets Are Delivered ​

Some offers can be issued in more than one way, either as an e-ticket or as a "Ticket on Departure" reference collected from a machine at the departure station. An offer that can be delivered carries a ticketDelivery field describing the choice:

  • availableOptions lists the ways this offer's tickets can be issued. It is never empty, but it can hold a single option. In that case delivery is already settled and there is nothing for the passenger to choose. Show it as information rather than as a picker.
  • selectedOption is the method that will be booked. In a booking a default is selected from the start, until you record a different choice. On a search result it is null.

An offer with no ticketDelivery at all (the field is null) carries no delivery information. The operator decides, and there is nothing to record. The companion offer of a return fare from a combined journey offer is the exception. It costs nothing extra and has no ticketDelivery, because the full-price offer's delivery covers both directions. If the full-price offer, which shares the companion's memberOf.id (see Offer Sets), has a ticketDelivery, record the choice there.

Record a choice with setTicketDelivery. It takes the offer id and one of the methods from that offer's availableOptions, and returns the updated offer:

Example: Read the options and record a choice
graphql
query GetBooking {
  node(id: "booking_x2y0603380x4y295q0c134d641") {
    ... on Booking {
      selections {
        ... on JourneyOfferSelection {
          offers {
            id
            memberOf {
              id
            }
            ticketDelivery {
              availableOptions {
                method
              }
              selectedOption {
                method
              }
            }
          }
        }
      }
    }
  }
}
query GetBooking {
  node(id: "booking_x2y0603380x4y295q0c134d641") {
    ... on Booking {
      selections {
        ... on JourneyOfferSelection {
          offers {
            id
            memberOf {
              id
            }
            ticketDelivery {
              availableOptions {
                method
              }
              selectedOption {
                method
              }
            }
          }
        }
      }
    }
  }
}
graphql
mutation SetTicketDelivery {
  setTicketDelivery(
    offerId: "offer_x7y531454571n1e2m0q70325s2"
    method: TICKET_ON_DEPARTURE
  ) {
    id
    ticketDelivery {
      selectedOption {
        method
      }
    }
  }
}
mutation SetTicketDelivery {
  setTicketDelivery(
    offerId: "offer_w2t7v2n7f540g07004x4b5h5f2"
    method: TICKET_ON_DEPARTURE
  ) {
    id
    ticketDelivery {
      selectedOption {
        method
      }
    }
  }
}

The returned offer is the whole change, so there is nothing else in the booking to refetch. A call addressed to a return fare's companion offer is rejected as UNAVAILABLE_TICKET_DELIVERY_METHOD.

Two singles selected separately are two offers and are set separately. Where the operator delivers them together, they must hold the same method, or creating the order is refused as TICKET_DELIVERY_CONFLICT.

Picking a method that is not among the offer's availableOptions is rejected as UNAVAILABLE_TICKET_DELIVERY_METHOD. An offer id that matches no offer is rejected as OFFER_NOT_FOUND.

If the selected option is TICKET_ON_DEPARTURE, the collection reference arrives with the finalized order. See Ticket Collection References.

Using the Offer Selection Embed ​

For a ready-made user interface, consider using the Offer Selection Embed. The embed provides an interface for selecting an offer, place properties and ancillaries, so you can quickly set up a booking flow on your website without writing any custom UI code.

html
<aa-offer-selection
  publicApiKey="your-api-key-here"
  booking="booking_x2y0603380x4y295q0c134d641"
  offer="offer_46t7z3z4g701b684e4n766c524"></aa-offer-selection>
<aa-offer-selection
  publicApiKey="your-api-key-here"
  booking="booking_x2y0603380x4y295q0c134d641"
  offer="offer_v1y3n28764m452x617j0f0t375"></aa-offer-selection>

Creating and Finalizing Orders ​

Once your booking has all passenger details and selected offers, create an order with createOrder. This pre-books the tickets with the train operator and is the same first step for every payment method:

graphql
mutation CreateOrder {
  createOrder(booking: "booking_x2y0603380x4y295q0c134d641") {
    id
    status
    reference
  }
}
mutation CreateOrder {
  createOrder(booking: "booking_x2y0603380x4y295q0c134d641") {
    id
    status
    reference
  }
}

Offers that the operator delivers together must hold the same ticket delivery method; see Choosing How Tickets Are Delivered.

Creating an order can take up to 30 seconds because it pre-books tickets with train operators. Use WebSocket connections to avoid timeouts and see progress updates.

How you complete the order depends on your payment method:

Payment Methods ​

There are three ways to pay for an order. Most API integrations use the wallet:

  1. Wallet (Prepaid Credits): Most common for API integrations. You maintain a balance of credits that are used for orders. Credits are deducted when you finalize an order.

  2. Payment Gateway: For end-customer payments. Redirects customers to All Aboard's payment page to pay, then finalizes the order automatically. See Payments for details.

  3. Invoice: Available for vetted commercial clients only. Orders are invoiced monthly.

Finalizing with Wallet or Invoice ​

For wallet credits or invoice billing, finalize the order yourself with finalizeOrder. This completes the purchase and triggers ticket issuance. Finalization happens immediately; there's no payment redirect.

Example: Finalize order (wallet/invoice)
graphql
mutation FinalizeOrder {
  finalizeOrder(order: "order_s0s6n362z5m6p0c5k106j7k5k1") {
    id
    status
    reference
  }
}
mutation FinalizeOrder {
  finalizeOrder(order: "order_x336v2f674b223g564q3k023z4") {
    id
    status
    reference
  }
}

Paying with the Payment Gateway ​

If you're collecting payment from end customers, pass the order's id to createPayment as orderId instead of finalizing it yourself. Because you already hold that id, interpolate it into your successUrl so the return page can look up the order. The customer pays on All Aboard's hosted page and the order is finalized automatically:

Example: Create payment (gateway)
graphql
mutation CreatePayment {
  createPayment(
    orderId: "order_abc123"
    successUrl: "https://example.com/success/order_abc123"
    cancelUrl: "https://example.com/cancel"
  ) {
    url
  }
}
mutation CreatePayment {
  createPayment(
    orderId: "order_abc123"
    successUrl: "https://example.com/success/order_abc123"
    cancelUrl: "https://example.com/cancel"
  ) {
    url
  }
}

Redirect the customer to the returned URL. After payment, they're redirected back to your successUrl, and the order is finalized automatically.

See Payments for complete details on the payment gateway flow.

Retrieving Tickets ​

Once an order is finalized, tickets are automatically issued. Fetch them using the node query. Most tickets are available within 15 minutes, but some take longer: train operators need time to generate and deliver them.

Tickets come in different formats depending on the operator:

PDF Tickets ​

Most tickets are PDF files that passengers can download and show on their phone or print. These are the most common format.

Some operators require passengers to check in online before departure. The API provides a check-in URL where passengers complete the process and receive their tickets.

Ticket Collection References ​

Some UK operators issue "Ticket on Departure" references. Passengers use these at station ticket machines to collect physical tickets.

Example: Get tickets from finalized order
graphql
query GetTickets {
  node(id: "order_e73150a4c5n0y7e1y6m4b633c6") {
    ... on Order {
      items {
        ... on JourneyOrderItem {
          resources {
            ... on PdfTicket {
              url
            }
            ... on CheckInResource {
              url
            }
            ... on TicketOnDeparture {
              reference
              description
            }
          }
        }
      }
    }
  }
}
query GetTickets {
  node(id: "order_p777e1x0a7x1v1g2k2d2a6n3j1") {
    ... on Order {
      items {
        ... on JourneyOrderItem {
          resources {
            ... on PdfTicket {
              url
            }
            ... on CheckInResource {
              url
            }
            ... on TicketOnDeparture {
              reference
              description
            }
          }
        }
      }
    }
  }
}

Ticket availability

Tickets may not be available immediately after finalization. Poll the order every few minutes until resources appear. Most tickets are available within 15 minutes, but some can take longer.

The Full Flow ​

This page covers creating and completing a booking. For the whole flow from search to tickets, including payment, see Booking Flows.