Travel Cards
Many travellers hold a card that changes what they pay: a discount card like a BahnCard or a Carte Avantage, an operator's loyalty card, a railcard in the UK, or a rail pass such as Interrail. The API calls all of these travel cards. You attach them to passengers, and the train operators price the journey accordingly.
What a Travel Card Does
A travel card changes which fares a passenger is entitled to. There are two kinds of effect:
- Reduction and loyalty cards lower the fare. The passenger is priced on the tariff the card entitles them to, and the ticket names the card. A UK 16-25 Railcard or a Swedish senior concession works this way.
- Rail passes change the product. A passholder already holds the right to travel, so operators sell them a passholder product instead: a seat reservation, or a reduced passholder fare where the operator requires one. With an Interrail Pass attached, the offers you get back are those passholder products.
Either way the card is part of the price. That is why a card is attached both when you ask for offers and when you create the booking, and why it can't be changed afterwards.
Discovering Available Cards
Use travelCardOptions to list the cards your account can use. Each option has a code, which is what you send along with a passenger, a display name, and inputFields describing any extra data the card needs.
Example: List available travel cards
graphql
query GetTravelCardOptions {
travelCardOptions {
code
name
inputFields {
fieldName
requirement
}
}
}query GetTravelCardOptions {
travelCardOptions {
code
name
inputFields {
fieldName
requirement
}
}
}json
{
"data": {
"travelCardOptions": [
{
"code": "aatc_interrail",
"name": "Interrail Pass",
"inputFields": [
{ "fieldName": "identifier", "requirement": "CONDITIONAL" }
]
},
{
"code": "aatc_bahncard_25",
"name": "BahnCard 25",
"inputFields": [{ "fieldName": "identifier", "requirement": "ALWAYS" }]
},
{
"code": "aatc_uk_16_25",
"name": "UK 16-25 Railcard",
"inputFields": []
},
{
"code": "aatc_swe_senior",
"name": "Senior in Sweden",
"inputFields": []
}
]
}
}{
"data": {
"travelCardOptions": [
{
"code": "aatc_interrail",
"name": "Interrail Pass",
"inputFields": [
{ "fieldName": "identifier", "requirement": "CONDITIONAL" }
]
},
{
"code": "aatc_bahncard_25",
"name": "BahnCard 25",
"inputFields": [{ "fieldName": "identifier", "requirement": "ALWAYS" }]
},
{
"code": "aatc_uk_16_25",
"name": "UK 16-25 Railcard",
"inputFields": []
},
{
"code": "aatc_swe_senior",
"name": "Senior in Sweden",
"inputFields": []
}
]
}
}The requirement on each input field tells you how to treat it:
ALWAYS: provide the field whenever the card is attached.CONDITIONAL: the field can be left out to begin with. Whether it is needed depends on what ends up in the booking, and the API tells you when you create it. The Interrail Pass identifier works this way, see Fields Some Cards Need.OPTIONAL: the field is never required.
The list depends on your account
Some cards are only offered to accounts that sell the operator they belong to. Read the list from the API rather than hardcoding it, and let your users pick from what comes back.
Pricing with a Travel Card
Attach cards to passengers in the travelCards field of PassengerPlaceholderInput. Cards ride along wherever you pass passengers: journeyOffer and combinedJourneyOffer for pricing, journeys when you ask for availability, and createBooking.
Each passenger carries their own cards. In a party where only one traveller holds a railcard, attach it to that passenger alone and the rest are priced on the regular fare. A card the operator doesn't apply is ignored and that passenger gets the regular fare. A code that isn't in the catalogue is rejected when you create the booking.
Send the same cards when you create the booking as you sent when you priced it. The passengers are compared on their card codes as well as their type and age, and a booking whose cards differ from the offer's is refused with PASSENGER_MISMATCH — a passholder fare can't be sold to a passenger without the pass, nor a regular fare to one who is booked as a card holder. The identifier is not compared, so you can price without it and supply it when you book.
Example: Get offers for card holders
graphql
subscription GetJourneyOffer {
journeyOffer(
journeyId: "e47442bf-cef1-4fe3-b7e8-667765fd583c"
passengers: [
{ type: ADULT, travelCards: [{ code: "aatc_uk_16_25" }] }
{ type: ADULT }
]
) {
status
itinerary {
... on SegmentCollection {
offers {
id
price {
amount
currency
}
parts {
... on AdmissionPart {
flexibility
serviceClass
comfortClass
}
... on ReservationPart {
flexibility
comfortClass
accommodation {
type
}
}
}
}
}
}
}
}subscription GetJourneyOffer {
journeyOffer(
journeyId: "7720147b-c0c8-4588-bdfd-117921adf8f1"
passengers: [
{ type: ADULT, travelCards: [{ code: "aatc_uk_16_25" }] }
{ type: ADULT }
]
) {
status
itinerary {
... on SegmentCollection {
offers {
id
price {
amount
currency
}
parts {
... on AdmissionPart {
flexibility
serviceClass
comfortClass
}
... on ReservationPart {
flexibility
comfortClass
accommodation {
type
}
}
}
}
}
}
}
}graphql
subscription GetJourneyOffer {
journeyOffer(
journeyId: "a20803a2-7d0b-497b-a355-4f783cc322f7"
passengers: [{ type: ADULT, travelCards: [{ code: "aatc_interrail" }] }]
) {
status
itinerary {
... on SegmentCollection {
offers {
id
price {
amount
currency
}
parts {
... on AdmissionPart {
flexibility
serviceClass
comfortClass
}
... on ReservationPart {
flexibility
comfortClass
accommodation {
type
}
}
}
}
}
}
}
}subscription GetJourneyOffer {
journeyOffer(
journeyId: "0fe6c96e-22d0-459f-b65c-5c9d8dae92f9"
passengers: [{ type: ADULT, travelCards: [{ code: "aatc_interrail" }] }]
) {
status
itinerary {
... on SegmentCollection {
offers {
id
price {
amount
currency
}
parts {
... on AdmissionPart {
flexibility
serviceClass
comfortClass
}
... on ReservationPart {
flexibility
comfortClass
accommodation {
type
}
}
}
}
}
}
}
}Creating the Booking
Create the booking with the same passengers you priced, cards included. The booking is priced on them, so the offer you selected and the passengers you book must agree. Cards come back on every passenger of the booking, so you can show the traveller what was applied.
Example: Create a booking for card holders
graphql
mutation CreateBooking {
createBooking(
offerIds: ["offer_j297s5e5n0v3w5v126r174q3f2"]
passengers: [
{ type: ADULT, travelCards: [{ code: "aatc_uk_16_25" }] }
{ type: ADULT }
]
) {
id
passengers {
id
type
travelCards {
code
name
identifier
}
}
}
}mutation CreateBooking {
createBooking(
offerIds: ["offer_y571p462f087t2f0325633r410"]
passengers: [
{ type: ADULT, travelCards: [{ code: "aatc_uk_16_25" }] }
{ type: ADULT }
]
) {
id
passengers {
id
type
travelCards {
code
name
identifier
}
}
}
}graphql
mutation CreateBooking {
createBooking(
offerIds: ["offer_m0f6y017g5p4g5q4j0a5n623n0"]
passengers: [
{
type: ADULT
travelCards: [{ code: "aatc_interrail", identifier: "PXK7Q2" }]
}
]
) {
id
passengers {
id
type
travelCards {
code
name
identifier
}
}
}
}mutation CreateBooking {
createBooking(
offerIds: ["offer_x4g4n5z3c4q626d0k260t4r5y2"]
passengers: [
{
type: ADULT
travelCards: [{ code: "aatc_interrail", identifier: "PXK7Q2" }]
}
]
) {
id
passengers {
id
type
travelCards {
code
name
identifier
}
}
}
}Fields Some Cards Need
Most cards need nothing but their code. A card with inputFields needs more, and identifier is the common one, the number on the card or pass.
The Interrail Pass is where this matters most. Its identifier is CONDITIONAL: you can price journeys without it, so a traveller can browse before they dig out their pass. Whether it is needed to book depends on what the booking contains: the operators involved and the kinds of train, a high-speed reservation for example. There is no way to tell in advance, and you don't have to. Create the booking with the cards as the traveller holds them. If it succeeds, nothing more is needed. If the identifier is required, the API answers with TRAVEL_CARD_FIELD_REQUIRED and names the field:
json
{
"errors": [
{
"message": "Travel card identifier is required.",
"extensions": {
"code": "TRAVEL_CARD_FIELD_REQUIRED",
"args": {
"field": "identifier"
}
}
}
]
}{
"errors": [
{
"message": "Travel card identifier is required.",
"extensions": {
"code": "TRAVEL_CARD_FIELD_REQUIRED",
"args": {
"field": "identifier"
}
}
}
]
}Nothing is booked by the failed attempt. Collect the field named in args.field and create the booking again with the same offer ids, while the offers are still valid. If your interface already asks card holders for their number, send it from the start and skip the round trip.
Travel Cards Are Fixed at Creation
A booking is priced on its passengers' cards, so the cards can't change once the booking exists. Leave travelCards out when updating passengers and fill in names, contact details and documents only. To book with a different card, or for a traveller who turns out not to hold one, price the journey again with the right passengers and create a new booking from that offer.
The rule covers every kind of card, a discount card as much as a rail pass. Sending cards that differ from the ones the passenger already holds answers with TRAVEL_CARDS_IMMUTABLE and none of the passenger changes in the call are saved:
json
{
"errors": [
{
"message": "Travel cards can't be changed on an existing booking. Search again with the right cards and create a new booking.",
"extensions": {
"code": "TRAVEL_CARDS_IMMUTABLE"
}
}
]
}{
"errors": [
{
"message": "Travel cards can't be changed on an existing booking. Search again with the right cards and create a new booking.",
"extensions": {
"code": "TRAVEL_CARDS_IMMUTABLE"
}
}
]
}Re-sending the cards a passenger already holds is accepted and changes nothing. They are compared on code and identifier alone and the order doesn't matter, though sending a card twice when the passenger holds it once counts as a change; the display name that comes back on a booking's cards is not part of the comparison and the input doesn't take it, so send only code and identifier. Everything else counts as a change and is rejected: a different identifier, an added or removed card, an empty list or null for a passenger who holds cards, and a card on a passenger who was booked without one. That last case is the one to watch, since attaching a pass here would leave a booking priced as a regular ticket while carrying a passholder's card.
Using the Embeds
The embeds take the same passenger objects as the API, so travel cards work there too. Set the passengers property on the element from JavaScript with travelCards on each passenger, and the embed prices and books with the cards attached. See the passengers attribute of the Journey List for details.
js
const list = document.querySelector('aa-journey-list')
list.passengers = [{ type: 'ADULT', travelCards: [{ code: 'aatc_interrail' }] }]const list = document.querySelector('aa-journey-list')
list.passengers = [{ type: 'ADULT', travelCards: [{ code: 'aatc_interrail' }] }]Next Steps
- Getting Offers for how offers and passengers work in general
- Booking Tickets for the rest of the booking flow
- Booking Rail Passes for selling the pass itself
- Error Codes for the errors a booking can raise