Skip to main content
Skip table of contents

The Holibob API - Look to Book flow

Introduction

This documentation covers the parts of the Holibob API required to discover, configure and book a single product availability.

The sections that follow detail the simplest API calls necessary to:-

  • Discover Products - You will use the producList query along with filters and pagination to present the consumer with products that match their searches.

  • Display Product Details - You will use the product query to retrieve more data about a single product that the consumer has chosen to view.

  • Request Availability List - You will use the availabilityList query passing a product Id and date range and to discover the dates on which the Product is available to book.

  • Discover and Set Availability Options - You will use the availability query passing the ID that relates to the consumer's selected date in order to discover details of any options that the consumer must select and then provide the system with their chosen responses.

    • StartTime: You may be offered different time for the day at which the product is available.

    • Guide Language: for products that include a guide you may be presented with different languages that they can speak.

    • A variant of the product: You may be offered different variants of the product such as “Standard” or “Premium”.

    • … more: there is no hard limit on the number of options for any given product, you must provide a selection for each option.

  • Discover and Set Availability Pricing Categories We will again use the availability query passing the ID to discover and set the number of pax for each price category such as Adult, Child etc.

  • Create a Booking - You will use the bookingCreate mutation to create a "cart" so we can add one or more availabilities to this Booking.

  • Add our configured Availability to the Booking - You will use the bookingAddAvailability mutation to add the above Availability to the created Booking.

  • OPTIONALLY - You will allow the user to discover further ProductAvailabilities and add these to the same Booking prior to checkout.

  • Checkout - You will use the bookingConfirm mutation to finalise the Booking. At this time the system will confirm the booking with the supplier and, depending on configuration, either we or the system must send confirmation emails and vouchers to the consumer.

  • Retrieve the booking - You can use the booking query to request all of the information relating to the finalised booking including any details you may wish to send to the consumer including vouchers, tickets and all related Product details.

  • Retrieve all bookings for ConsumerTrip or Consumer - You can use the bookingList query to filter bookings by a specific Consumer Trip or Consumer

It is possible to use the booking query passing the bookingId to retrieve all information for vouchers and tickets. Be aware however that there is a short period after using the `bookingConfirm is which the booking will be in a PENDING state whilst all checks are completed with the supplier. We will poll the booking query until this state changes

Fetch Product List - The basics

The productList query is used to obtain a list of Products based on filters and pagination

Each product will have a unique id that you will use later to retrieve its Availabilities.

Query

productList query
CODE
query {
  productList(
    filter: {placeName: "Edinburgh"},
  ) 
  {
    nodes {
      id
      name
      guidePriceFormattedText
    }
  }
}

There are several hundred values that may be retrieved for a Product. These values are nested. As is always the case with GraphQL the structure of the data returned in the response is exactly the structure that you asked for in the request.

Example Responses

productList example response
CODE
{
  productList:{
    nodes:[
      {
        id:"e33751d7-485b-44a4-ad8f-2637ff749353"
        name:"Walk Through Test Product"
        guidePriceFormattedText:NULL
      },
      {
        id:"f3ce919a-fee1-11e9-b729-06829e32ec62"
        name:"JK Rowling's Edinburgh & the writing of Harry Potter"
        guidePriceFormattedText:NULL
      },
      {
        id:"595ba897-e850-4264-a677-804bc1c71935"
        name:"Cycle tour to the coast"
        guidePriceFormattedText:"€ 49.00"
      },
      
      ... etc
      
    ]
  }
}

In this simple example, we passed a filter for PlaceName="Edinburgh" and so the system returned a list of products that match that filter.

It is possible to pass many different filters and combine them.

The example asked only for id, name and guidePriceFormattedText and so that is what was returned.

There are however several hundred other attributes of the Product that may have been requested. Note too that guidePrice and guidePriceCurrency could have been asked for separately and that the guidePriceFormattedText is provided only for convenience.

Guide Prices

The guidePrice will be an APPROXIMATE Adult price for the next nearest availability of the product and is provided only as a guide and a from price. The true price of a product may vary by date and other criteria and will be returned only when a call is made to the availabilityList query.

Images

Each product will have one o more images available as may be requested with the imageList property inside of the product nodes. Each image has a unique ID. You can request the image from our global CDN in any format (WebP, PNG, JPG etc) and at any resolution. It is also possible to specify the aspect ration that you will present the image in. The CDN will perform any transformations required and return an image in exactly the resolution, format and shape required by your consumers.

Descriptions

Each product will have a description and may have many other elements of content such as inclusions, exclusions and itinerary. Each content item may be requested in one of nine supported languages including Arabic and may be requested in either PLAIN_TEXT, HTML or JSON format

Fetch Availability List for a given product

Once a consumer has indicated an interest in a given product you will call the availabilityList query passing the productId and a filter that includes startDate and endDate to determine on what days the product is available. This will return an array of dates that have availability.

In this document we use the format $variableName to indicate some value that you may be managing in state either as a direct input from the consumer or from the session that the consumer is part of.

Typically these state values will have been determined from the result of a previous interaction with the API.

Query

availabilityList query
CODE
query {
  availabilityList(
    productId: "$productId", 
    filter: {
      startDate: "$startDate", 
      endDate: "$endDate",
    }
  ) 
  {
  nodes {
    id
    date
    guidePriceFormattedText
  }
}

Example Response

availabilityList Response
CODE
availabilityList:{
  nodes:[
    {
      id:"2baea4d1-1bd8-44e9-b948-ba4a5cf8dd9f",
      date:"2023-04-06",
      soldOut:false,
      guidePriceFormattedText:"€ 206.00",
    },
    {
      id:"9e2bb7f0-cb8e-4cad-935a-20dd27d194c6",
      date:"2023-04-07",
      soldOut:false,
      guidePriceFormattedText:"€ 206.00",
    }
  ]
}

Fetch and respond to Options for a single Availability

Each Availability returned in the previous query has a unique id and relates to a given date in the calendar. Dates that are not returned have zero availability and must not be offered to the consumer. These are cache values and are only guaranteed to live for 24-hours which is typically a lot longer than would ever be required.

You will use the availability query to both retrieve details of the availability AND update details of the availability.

Using a query to mutate data in a GraphQL API is unusual but not outside of the specification. The pattern is used only in this part of the API as it is significantly easier to work with that using a combination of queries and mutations as is common elsewhere.

You must make iterative calls to the availability query as you inform the system of choices the consumer has made for

  • options - returned to you as an array of questions each with an array of possible answers

  • pricingCategories - returned to you as an array of categories containing details of pricing, restrictions such as maxAllowed and interdependencies such as the required ratio of Adult to Child

You are required to present the options to the consumer and submit their responses to the system. Each submission of an option or pricingCategory response may result in changes to other options already presented or yet to be discovered.

It is only possible to add the Availability to a Booking once all required options have been submitted and pricingCategory units set

Fetch the initial optionList

Initial Query

Fetch the option list from the availability query
CODE
query walkThrough {
  availability(id: "{{ state.availabilityId }}") {
    id
    optionList {
      nodes {
        id
        label
        dataType
        dataFormat
        availableOptions {
          label
          value
        }
        answerValue
        answerFormattedText
      }
    }
  }
}

Initial Response

Response containing the initial option list
JSON
{
  "availability": {
    "id": "2baea4d1-1bd8-44e9-b948-ba4a5cf8dd9f",
    "optionList": {
      "nodes": [
        {
          "id": "12a06400-b7ed-4a70-ac0f-7e95258b465c",
          "label": "Ticket Type",
          "dataType": "OPTIONS",
          "dataFormat": null,
          "availableOptions": [
            {
              "label": "Premium",
              "value": "ffcdea9d-acd3-4c38-b49a-4f8b42bca0f8"
            },
            {
              "label": "Standard",
              "value": "f3ef9c0d-e8f4-4597-8ef6-8418a0a3ea73"
            }
          ],
          "answerValue": null,
          "answerFormattedText": null
        }
      ]
    }
  }
}

Setting the answer for the initial option(s)

In the above, there is a single option requiring the consumer to select either the Premium or Standard tour. You must present this option to the consumer and require a response. When the user has made their choice you will call the query again as below to change the data in the availability.

Answering the first options group

Send the initial response(s) to the system
CODE
query {
  availability(id: $availabilityId, 
    input: {   
      optionList: 
			[
        {
          id: "c0e212a6-3247-472e-9939-ecd79d180a2f", 
          value: "4c4f056f-7a28-4955-811d-5cd1786619ee"
		}
      ]
    }
  ) {
    optionList {
      isComplete
    }
  }
}

Response informs options are NOT complete

Check the status of isComplete
CODE
{
	"data": {
		"availability": {
			"optionList": {
				"isComplete": false
			}
		}
	}
}

We will see that the response to incomplete is FALSE and so whilst we did answer all the options that were initially presented we an now assume that there is at least one new option that must be iterated.

In this case we chose a particular version of the Product and so now the system can present us with the startTime options

Repeat the initial call for any further options

Query

Fetch the optionList again
CODE
query {
  availability(id: $availabilityId) {
    id
    optionList {
      nodes {
        id
        label
        dataType
        dataFormat
        availableOptions {
          label
          value
        }
        answerValue
        answerFormattedText
      }
    }
  }
}

Example Response

Response with more options
CODE
{
	"data": {
		"availability": {
			"id": "711a0648-215e-43f5-9d17-8a376b62b92e",
			"optionList": {
				"nodes": [
					{
						"id": "6358fbc1-6db7-4363-ab1d-c4b2bf232fe1",
						"label": "Ticket Type",
						"dataType": "OPTIONS",
						"dataFormat": null,
						"availableOptions": [
							{
								"label": "Premium",
								"value": "ffcdea9d-acd3-4c38-b49a-4f8b42bca0f8"
							},
							{
								"label": "Standard",
								"value": "f3ef9c0d-e8f4-4597-8ef6-8418a0a3ea73"
							}
						],
						"answerValue": "f3ef9c0d-e8f4-4597-8ef6-8418a0a3ea73",
						"answerFormattedText": "Standard"
					},
					{
						"id": "9c35c8b0-5aa6-46d4-af15-af51186881f6",
						"label": "Start Time",
						"dataType": "OPTIONS",
						"dataFormat": null,
						"availableOptions": [
							{
								"label": "09:00",
								"value": "9f25d83f-1f64-4f7b-b99e-600f290d5e89"
							},
							{
								"label": "11:00",
								"value": "16bc80e9-40c3-4580-9647-1f5da3228413"
							},
							{
								"label": "17:00",
								"value": "8df28cb8-16bc-4dbf-a962-d3858786a373"
							}
						],
						"answerValue": null,
						"answerFormattedText": null
					}
				]
			}
		}
	}
}

We will confirm at lines 24 and 25 that our initial option choice has been added to the Availability but observe at line 28 there is a new option for Start Time

We must again therefore submit the response to this option

Setting the answers for any additional options

Query

Sent the response for the Start Time options
CODE
query holibob($availabilityId: String!) {
  availability(id: $availabilityId, 
    input: {   
      optionList: 
			[
        {
          id: "9c35c8b0-5aa6-46d4-af15-af51186881f6", 
          value: "16bc80e9-40c3-4580-9647-1f5da3228413"
		}
      ]
    }
  ) {
    optionList {
      isComplete
    }
  }
}

Example Response

check the completion state of the availability
JSON
{
	"data": {
		"availability": {
			"optionList": {
				"isComplete": true
			}
		}
	}
}

Great! we now receive confirmation that isComplete = true and so we have satisfied all options.

Of course, this may not have been the case. It is dependent on each product exactly how many times this process may require to be iterated.

Fetch and respond to Pricing Categories for a single Availability

Now that we have confirmation that all options are satisfied it is possible to request the pricingCategory details for the Availability.

Requesting pricingCategories for an Availabilirty that we have not satisfied all options on will always return a null pricingCategory node. This is because the available pricingCategories are dependant on the options

Fetch the Pricing Categories for the Avaialbility

Query

Fetch the initial pricingCategories for the Availability
CODE
query {
	availability(id: $availabilityId) {
		id
		maxParticipants
		minParticipants
		pricingCategoryList {
			nodes {
				id
				label
				minParticipants
				maxParticipants
				maxParticipantsDepends {
					pricingCategoryId
					multiplier
					explanation
				}
				units
				unitPrice {
					netFormattedText
					grossFormattedText
				}
				totalPrice {
					grossFormattedText
				}
			}
		}
	}
}

Example Response

Check the pricingCategories available and the prices and restrictions that apply
CODE
{
	"data": {
		"availability": {
			"id": "711a0648-215e-43f5-9d17-8a376b62b92e",
			"maxParticipants": 50,
			"minParticipants": null,
			"pricingCategoryList": {
				"nodes": [
					{
						"id": "ba9cc115-0eca-40f1-a953-caebb8e7695c",
						"label": "Adult",
						"minParticipants": null,
						"maxParticipants": null,
						"maxParticipantsDepends": null,
						"units": 0,
						"unitPrice": {
							"netFormattedText": "€ 136.40",
							"grossFormattedText": "€ 136.70"
						},
						"totalPrice": {
							"grossFormattedText": "€ 0.00"
						}
					},
					{
						"id": "845a8e9c-1d22-4861-9821-5161b3938125",
						"label": "Child",
						"minParticipants": null,
						"maxParticipants": null,
						"maxParticipantsDepends": null,
						"units": 0,
						"unitPrice": {
							"netFormattedText": "€ 85.30",
							"grossFormattedText": "€ 85.40"
						},
						"totalPrice": {
							"grossFormattedText": "€ 0.00"
						}
					}
				]
			}
		}
	}
}

We will observe that there are two pricing categories for this option. They are

  • Adult

  • Child

Note that the units for each pricingCategory are initially set to zero.

We must now present these pricingCategories to the consumer and allow them to modify the value for units.

There will typically be are also some restrictions on the values that can be set as determined by each of the following

  • overall

    • minParticipants - If set, you are required to book at least this number of PAX regardless of their category.

    • maxParticipants - if set you are required to book no more than this number of PAX regardless of their category.

  • per category

    • minParticipants - if set you are required to book at least this number of the given category.

    • maxParticipants - If set, you are required to book no more that the given number of this category

    • maxParticipantsDepends - If set, this will indicate a relationship between categories such as the ration of Child to Adult and you must honour this requirement

It is important to ensure that the consumer can not breach these restrictions as doing so will result in an invalid availability that can not be added to a booking.

In order to set the values for units We will make one or more calls to the Availability to set the values.

Setting the units for each requested Pricing Category

Send the units to the availability

Send the required units for each pricingCategory
CODE
query  {
	availability(
		id: $availabilityId
		input: {
			pricingCategoryList: [
				{ id: $pricingCategoryId1, units: $pricingCategoryUnits1 }
				{ id: $pricingCategoryId2, units: $pricingCategoryUnits2 }
			]
		}
	) 
	{
	totalPrice {
    	grossFormattedText
	}
    isValid
  }
}

Example Response

Check the total proce and the validity of the availability
CODE
{
	"data": {
		"availability": {
			"totalPrice": {
				"grossFormattedText": "€ 358.80"
			},
			"isValid": true
		}
	}
}

We will observe that the total gross formatted price has been updated to reflect the units we have set and that the isValid has returned as true indicating that the options we have requested are valid.

It is important to note that the total price returned may not be the sum and multiplication of the initial per category prices that where initially observed. This is because some products offer volume discounts and so the per category price for certain categories may have been reduced. There are additional fields available on the Availability query that can be used to return this information to the consumer

Creating a booking

Now that you have a valid Availability object you must create a Booking and then add the Availability to this booking before you can begin the checkout flow.

We create a booking with a call to the bookingCreate mutation

Create a new Booking

Mutation

Create a new Booking (cart)
CODE
mutation {
  bookingCreate (
		input: {
			autoFillQuestions: true
		}
	)
  {
    id
    state
    isComplete
    paymentState
  }
}

Example Response

Retrieve the id and other status information
CODE
{
	"data": {
		"bookingCreate": {
			"id": "ffd05542-f38e-446b-9861-35ecd389c3b3",
			"state": "OPEN",
			"isComplete": false,
			"paymentState": "ON_ACCOUNT"
		}
	}
}

We strongly recommend passing the autoFillQuestions = true option to the bookingCreate mutation. Many suppliers will configure optional questions on a product or on the checkout flow and these can present additional friction for the consumer. By using the autoFillQuestions options we are asking the system to bypass these additional questions resulting in less details for the consumer to complete.

The response will contain the unique booking ID just created along with any other state information we have requested.

This booking does not yet include our opinionated availability and so the next step is to add the availability to the booking by making a call to the bookingAddAvailability mutation

Create a new Booking with Consumer Trip

We also support the association of a Consumer Trip on booking with optional supporting fields for both the ConsumerTrip and the Consumer . See below for a basic example

Mutation

Create a new Booking (cart)
CODE
mutation {
  bookingCreate (
    input: {
      consumerTrip: {
        selector: {
          type: "reference",
          value: "<STRING REFERENCE>"
        }
      }
    }
  )
  {
    code
    consumerTrip{
      id
    }
  }
}

Example Response

Retrieve the id and other status information
CODE
{
  "data": {
    "bookingCreate": {
      "code": "BXJJWN",
      "consumerTrip": {
        "id": "831c4974-044d-4591-af55-d3c45756e8e0"
      }
    }
  }
}

Add your Availability to your Booking

Request

Add the availability to the booking (cart)
CODE
mutation {
  bookingAddAvailability(
    input: {
      bookingSelector: {id: $bookingId}, 
      id: $availabilityId
    }
  ) 
  {
    isComplete
  }
}

Example Response

Check the status of isComplete
CODE
{
	"data": {
		"bookingAddAvailability": {
			"isComplete": false,
		}
	}
}

We will observe that isComplete = false. This will always be the case for a newly created booking and indicates that there are unsatisfied questions on the booking that we must respond to before it is possible to commit the booking

Input answers to the booking

In a process similar to that used to update the Availability you must now:-

  • Retrieve full details of the booking’s questions

  • Answer each question that is present

  • Iterate the process until isComplete = true

A booking has two optional static values that we recommend you always query and respond to. These are

  • leadPassengerName - You may set this to any string value. We recommend that you do so in the format “<familyName>, <givenName>” and that you capitalise the <familyName>

  • partnerExternalReference - You may set this to any string. We recommend that you use this to store your own unique reference for this booking. You may use this value later to retrieve a booking based on your value

In addition there are likely to be one or more booking questions that must be answered. Questions may exist at three different levels within the booking. These are

  • The Booking - These questions apply to the whole of the booking.

  • Each Availability - These questions apply to the whole of the availability. As it is possible to add more than one availability to a booking there may be different questions contained in each availability.

  • Each Person within each Availability - These quesrions relate to each individual added to the given Availability. If say, you have added 2 Adults and one Child to the booking there may be questions for each of the three individuals and each must be presented to the consumer and the answes submitted to the system.

Structure of a question list

Request

Request the data for a questionList
CODE
questionList {
  nodes {
	id
	label
	type
	dataType
	dataFormat
	answerValue
	isRequired
  }
}

Example Response

The returned data
CODE
"nodes": [
  {
	"id": "203be336-b549-4779-8365-784895913c88",
	"label": "First name",
	"type": "NAME_GIVEN",
	"dataType": "TEXT",
	"dataFormat": null,
	"answerValue": null,
	"isRequired": true
  }
]

The following values are queried and returned for each question

  • id - a unique id for this question that will be used when providing a response

  • label - the label to be displayed to the consumer. Labels will be returned in the requested language

  • type - an enumeration for the question type

  • dataType - the type of data that is expected [TEXT, DATE, OPTIONS etc]

  • dataformat - the restrictions on the format of the data that must be returned eg [“EMAIL“, “PHONE”]

  • answerValue - the value of any answer that has already been provided

  • isRequired - indicates that the question must be satisfied before the booking can be committed

We will now issue a query to retrieve all of the questions for the booking

Fetch the booking questions

Request

Fetch the booking Questions
CODE
query holibob($bookingId: String!) {
  booking(id: $bookingId) {
    id
    code
    leadPassengerName
    partnerExternalReference
    state
    isSandboxed
    paymentState
    partnerChannelBookingUrl
    
    questionList {
      nodes {
        id
        label
				 autoCompleteValue
				 type
        dataType
        dataFormat
        answerValue
        isRequired
      }
    }
		
    availabilityList {
      nodes {
        id
        date
        product {
          id
          name
        }
        questionList {
          nodes {
            label
            dataType
            dataFormat
            answerValue
            isRequired
          }
        }
        personList {
          nodes {
            id
            pricingCategoryLabel
            isQuestionsComplete
            questionList {
              nodes {
                label
                answerValue
              }
            }
          }
        }
      }
    }
  }
}

Example Response

Booking data with questionLists
CODE
{
	"data": {
		"booking": {
			"id": "f47e2915-1124-4482-875c-7ced18997403",
			"code": "PSMF4W",
			"leadPassengerName": null,
			"partnerExternalReference": null,
			"state": "OPEN",
			"isSandboxed": true,
			"paymentState": "REQUIRED",
			"partnerChannelBookingUrl": "https://demo.booking.sandbox.holibob.tech",
			"questionList": {
				"nodes": [
					{
						"id": "203be336-b549-4779-8365-784895913c88",
						"label": "First name",
						"autoCompleteValue": null,
						"type": "NAME_GIVEN",
						"dataType": "TEXT",
						"dataFormat": null,
						"answerValue": null,
						"isRequired": true
					},
					{
						"id": "2cb6b546-63f1-4d0f-af3c-39e14408890a",
						"label": "Last name",
						"autoCompleteValue": null,
						"type": "NAME_FAMILY",
						"dataType": "TEXT",
						"dataFormat": null,
						"answerValue": null,
						"isRequired": true
					},
					{
						"id": "00a2ed5c-de09-419e-a48b-7a8cd43b6067",
						"label": "Email Address",
						"autoCompleteValue": null,
						"type": "EMAIL_ADDRESS",
						"dataType": "TEXT",
						"dataFormat": "EMAIL_ADDRESS",
						"answerValue": null,
						"isRequired": true
					},
					{
						"id": "16fb1dd1-d4f5-4d4c-abe7-0ce150568e1e",
						"label": "Phone Number",
						"autoCompleteValue": null,
						"type": "PHONE_MAIN",
						"dataType": "TEXT",
						"dataFormat": "PHONE_NUMBER",
						"answerValue": null,
						"isRequired": true
					}
				]
			},
			"availabilityList": {
				"nodes": [
					{
						"id": "711a0648-215e-43f5-9d17-8a376b62b92e",
						"date": "2023-04-07",
						"product": {
							"id": "e33751d7-485b-44a4-ad8f-2637ff749353",
							"name": "Walk Through Test Product"
						},
						"questionList": {
							"nodes": []
						},
						"personList": {
							"nodes": [
								{
									"id": "3e4db2d9-d20d-414d-97c9-d50b100e9237",
									"pricingCategoryLabel": "Adult",
									"isQuestionsComplete": true,
									"questionList": {
										"nodes": []
									}
								},
								{
									"id": "4b41ad76-d665-40a5-b70e-79c89dba75b5",
									"pricingCategoryLabel": "Child",
									"isQuestionsComplete": true,
									"questionList": {
										"nodes": []
									}
								},
								{
									"id": "4b5bd9f2-3a8d-4e17-9839-1d4545d0b5ab",
									"pricingCategoryLabel": "Adult",
									"isQuestionsComplete": true,
									"questionList": {
										"nodes": []
									}
								}
							]
						}
					}
				]
			}
		}
	}
}

We will observe in the example response that the questionList contained directly in the single availability node and in each of the person nodes is returned empty in this example but this will not be the case for many products and so it is important to always issue a full query to establish the full list of questions.

We are now in a position to present the nested list of questions to the consumer and seek their responses.

We will then send the consumers responses to the system

Send the Answers for each Booking Question

Request

Send answers to the system
CODE
query {
  booking(
    id: $bookingId 
    input: {

      leadPassengerName: "BRYCE Graeme Allan", 
      reference: "12345"
      
      answerList: [ 
        {questionId: $questionId1, value: "Graeme"}
        {questionId: $questionId2, value: "Bryce"}
        {questionId: $questionId3, value: "graeme@holibob.tech"}
        {questionId: $questionId4, value: "+447745056933"}
      ]
    }
  ) {
    canCommit
  }
}

Example Response

Check the value of canCommit
CODE
{
	"data": {
		"booking": {
			"canCommit": true
		}
	}
}

We will confirm that all questions on the booking have been satisfied by requesting the canCommit value on the response. When this is true we are able to proceed to commit the booking.

Commit Booking

Our booking is now fully populated with a single opinionatedAvailability and all of the answers to required questions but the booking remains in the OPEN state and has not yet been committed.

The final step in this process is to commit the booking

We must however consider the matter of payment.

Typically, an API partner will be configured for a payment state of “ON_ACCOUNT” and so any booking that has a fully satisfied set of options and questions can immediately be committed. This will cause the system to process the booking and complete the following steps

  • Send the booking to the supplier

  • Retrieve any tickets issued by the supplier

  • Compile a single PDF voucher containing all details of the booking including the embedding of any tickets

  • Optionally, subject to configuration, send a confirmation email to the Consumer or Booking Agent containing a copy of the voucher

It is assumed that if your API payment is set to ON_ACCOUNT then you will already have arranged to take payment from the consumer for the gross amount of the booking.

When you make a call to the bookingCommit mutation you are confirming your obligation to pay for the Booking and your will be invoices in due course.

To commit a booking we simply make a call to the bookingCommit endpoint passing the id of the booking.

Commit the booking

Request

Commit the booking
CODE
mutation holibob($bookingId: String!) {
  bookingCommit(bookingSelector: {id: $bookingId }) 
  {
    state
    voucherUrl
  }
}

Example Response

Check the state of the booking and the voucherUrl
CODE
{
	"data": {
		"bookingCommit": {
			"state": "PENDING",
			"voucherUrl": null
		}
	}
}

We will observe that the status of the booking has changed to “PENDING” indicating that the system is performing all necessary steps to communicate with the supplier and compile the voucher. We will also observe that in the initial response, the voucherUrl is null.

There are several ways in which We will establish that a booking has completed processing. These are

  • You provide a webhook that Holibob will call upon completion of the processing

  • You poll the booking until such time as the status changes from “PENDING”

Poll for the state of the Booking

Request

Poll the state of the booking
CODE
mutation holibob($bookingId: String!) {
  bookingCommit(bookingSelector: {id: $bookingId }) 
  {
    code
    state
    voucherUrl
  }
}

Example Response

Check the state of the booking and the voucherUrl
CODE
	"data": {
		"booking": {
			"code": "ABC123"
			"state": "CONFIRMED",
			"voucherUrl": "https://s3.eu-west-1.amazonaws.com/tech.holibob.files/bookingVouchers/9fd195cd-f814-4700-8940-79679e0713fe/voucher.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA4H5CGSAPISTGYLFI%2F20230410%2Feu-west-1%2Fs3%2Faws4_request&X-Amz-Date=20230410T091131Z&X-Amz-Expires=10000&X-Amz-Signature=24dbde04c1c865e7a24111a8d183d8b4244bb7aadffb2df0bd5baf14878d50d7&X-Amz-SignedHeaders=host"
		}
	}
}

Note that for security reasons the voucherUrl is temporary and you should not persist the URL to you storage.

You can of course retrieve the PDF and persist this to storage.

If at any time you need a fresh URL for the voucher you can issue the same query again.

The query is able to return everything that is linked to the Booking, however, in this case you would not want such extensive data.

The query in this example requests the key details of the booking including the following:

  • code - this is a unique consumer facing code that is equivalent to the ID of the booking and typically used when communicating with customer service representatives.

  • state - indicates if the booking is OPEN, CONFIRMED, PENDING or FAILED.

  • voucherUrl - a single use URL that may be used to retrieve the PDF of the voucher. The single PDF will have any tickets embedded within it as may be required by the supplier.

Retrieve all bookings for ConsumerTrip or Consumer

You can identify the consumer and consumerTrip that a booking relates to by using one of the following filters on the bookingList query

  • consumerTripExternalReference - filter bookings related to a specific ConsumerTrip reference

  • consumerExternalReference - filter bookings related to a specific Consumer reference

Request

BookingList for specific ConsumerTrip
CODE
query BookingList {
  bookingList(
    filter: { consumerTripExternalReference: "<STRING REFERENCE>" }
  ) {
    recordCount
    nodes {
      code
      id
      consumerTrip {
        id
        partnerExternalReference
        consumer {
          id
          partnerExternalReference
          familyName
        }
      }
    }
  }
}

Example Response

List of Bookings
CODE
{
  "data": {
    "bookingList": {
      "recordCount": 2,
      "nodes": [
        {
          "code": "BXJJWN",
          "id": "c65b7727-0442-4c3f-95cb-6fff980cb721",
          "consumerTrip": {
            "id": "831c4974-044d-4591-af55-d3c45756e8e0",
            "partnerExternalReference": "<STRING REFERENCE>",
            "consumer": {
              "id": "e4e314c2-8627-439c-bbd2-0ac4d1d80877",
              "partnerExternalReference": "yyyy-yyyy-yyyy-yyyy",
              "familyName": "smith"
            }
          }
        },
        {
          "code": "3FR4ZF",
          "id": "afdf25d4-76d1-4855-8bef-b866b56bc03e",
          "consumerTrip": {
            "id": "831c4974-044d-4591-af55-d3c45756e8e0",
            "partnerExternalReference": "<STRING REFERENCE>",
            "consumer": {
              "id": "e4e314c2-8627-439c-bbd2-0ac4d1d80877",
              "partnerExternalReference": "yyyy-yyyy-yyyy-yyyy",
              "familyName": "smith"
            }
          }
        }
      ]
    }
  }
}
JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.