Skip to content

Making a booking

Luis Rosales Carrera edited this page Oct 21, 2024 · 42 revisions

TL;DR

For creating a booking, you need to fetch the following information for the request body:

  • tour_id: request to /tours/[tourId] or /tours.
  • option_id: request to /tours or /tours/[tourId]/ or /options/[optionId].
  • date_time: request to /tours/[tourId]/availability to find a date, and with this date, request to /tours/[tourId]/price-breakdown to find the correspondent date_time parameter.
  • category_id: request to /tours/{tour_id}/price-breakdown.
  • retail_price: request to /tours/{tour_id}/price-breakdown.

Then, use the above information to write the body of the POST request @ /bookings.

Step by step

In this guide, we will go through the whole booking flow for an imaginary customer. Even though the tutorial will be going into the details, it is impossible to reflect all the API's possibilities. Please use the API specs for further reading.

Finding an activity

First of all, let's find an activity our imaginary customer would like to book. There are multiple ways to achieve this, but we will focus on the most basic way here, using the q parameter of the /tours endpoint. In this example, we would like to find an activity in Paris. We know our customer speaks English and wants to pay in Euro. Furthermore, we want to look for tours in a specific date range December 25, 2021 - December 30, 2021 and only display tours with a minimum rating of 4.

With the information above, we can now assemble our request URL:

  • https://api.getyourguide.com/1/tours?q=Paris&date[]=2021-12-25T00:00:00&date[]=2021-12-30T23:59:59&rating[]=4&cnt_language=en&currency=eur

The API will return something like this (added the limit parameter, so the tour array returned is not getting too long):

{
    "_metadata": {
        "descriptor": "GetYourGuide AG",
        "method": "getTourByQueryAction",
        "date": "2021-11-04T12:21:43Z",
        "status": "OK",
        "query": "cnt_language=en&currency=eur&date%5B%5D=2021-12-25T00%3A00%3A00&date%5B%5D=2021-12-30T23%3A59%3A59&limit=2&q=Paris&ratig%5B%5D=4",
        "availableLanguages": [
            "en",
            "es",
            "fr",
            "de"
        ],
        "exchange": {
            "rate": 1,
            "currency": "eur"
        },
        "totalCount": 498,
        "limit": 10,
        "offset": 0
    },
    "data": {
        "tours": [
            // ...
            {
                "tour_id": 66985,
                "tour_code": "deprecated",
                "cond_language": [
                    "en",
                    "es",
                    "fr",
                    "de"
                ],
                "title": "Paris: Catacombs Skip-the-Cash-Desk Ticket with Audio Guide",
                "abstract": "Skip the cash desk line at the Paris Catacombs. Discover a darker side to the “City of Lights.” Descend beneath the streets of Paris and listen to commentary from your informative audio guide, available in 4 languages.",
                "bestseller": false,
                "certified": true,
                "has_pick_up": false,
                "overall_rating": 4.4974,
                "number_of_ratings": 7000,
                "pictures": [
                    {
                        "id": 1,
                        "url": "https://cdn.getyourguide.com/img/tour/59cba8cb6b06c.jpeg/[format_id].jpg",
                        "ssl_url": "https://cdn.getyourguide.com/img/tour/59cba8cb6b06c.jpeg/[format_id].jpg",
                        "verified": true
                    }
                ],
                "coordinates": {
                    "lat": 48.85693,
                    "long": 2.3412
                },
                "price": {
                    "values": {
                        "amount": 29
                    },
                    "description": "individual"
                },
                "categories": [
                    {
                        "category_id": 27,
                        "name": "Culture & History"
                    }
                ],
                "locations": [
                    {
                        "location_id": 965,
                        "type": "area",
                        "name": "Ile-de-France",
                        "english_name": "Ile-de-France",
                        "country": "FR",
                        "coordinates": {
                            "lat": 48.680809,
                            "long": 2.50261
                        }
                    }
                ],
                "url": "https://www.getyourguide.com/paris-l16/paris-catacombs-skip-the-line-ticket-t66985/?partner_id=8OXMHTJ&psrc=partner_api&currency=EUR",
                "durations": [
                    {
                        "duration": 1,
                        "unit": "hour"
                    }
                ]
            }
          // ...
        ]
    }
}

Let's say our imaginary customer conveniently would like to book the tour with the ID 66985. Now we have two options, depending on the type (complexity) of your integration:

  1. The booking and checkout process will happen via the GetYourGuide marketplace
  2. The booking and checkout process will happen via the API

Option 1 - Booking and checkout via the GetYourGuide marketplace

For Option 1, we are basically done. We redirect the customer to the URL that is provided in the url field of the API response, i.e.:

  • https://www.getyourguide.com/paris-l16/paris-catacombs-skip-the-line-ticket-t66985/?partner_id=AABBCCDD&psrc=partner_api&currency=EUR

This URL links to the activity detail page on the GetYourGuide marketplace. It is automatically populated with your partner_id, and the booking will be attributed to your account so that you will receive your commission.

Option 2 - Booking and checkout via the API

While Option 1 was easy, let us now look at the more advanced approach of Option 2. Even though we have the tour_id of the activity for our customer, we are not even close to having the information we need to place an actual booking (adding it to a cart). We first have to understand that booking tours via the API is impossible, but only tour options. Think of tours as "generic" products and of tour options as the discrete products we can book. In that regard, the actual availability and pricing information is bound to individual tour options. Knowing that, we now have to look up the available tour options for our tour_id. For that we hit the corresponding /tours/{tours_id} endpoint, like so:

  • https://www.getyourguide.com/1/tours/66985&cnt_language=en&currency=eur&preformatted=home

Ensure you include the parameter preformatted with the value home when requesting. This parameter allows for retrieving a comprehensive tour information composition encompassing various tour options.

However, if your objective is to solely access detailed information about a specific tour option, utilize /options/{option_id} endpoint.

The response will look like this:

{
  "_metadata":{
    //...
  },
  "data":{
    "tours":[
      {
        "tour_id":66985,
        "tour_code":"deprecated",
        "cond_language":[
          "en",
          "es",
          "fr",
          "de"
        ],
        "title":"Paris: Catacombs Skip-the-Cash-Desk Ticket with Audio Guide",
        "abstract":"Skip the cash desk line at the Paris Catacombs. Discover a darker side to the “City of Lights.” Descend beneath the streets of Paris and listen to commentary from your informative audio guide, available in 4 languages.",
        "bestseller":false,
        "certified":true,
        "has_pick_up":false,
        "overall_rating":4.4974,
        "number_of_ratings":7000,
        "tour_options":[
          {
            "option_id":265866,
            "tour_id":66985,
            "title":"Paris: Catacombs Skip-the-Cash-Desk Ticket with Audio Guide",
            "description":"Tickets for under 18s must be bought on site.",
            "meeting_point":"Please go directly with your voucher to the entrance of the catacombs.",
            "duration":1,
            "duration_unit":"hour",
            "cond_language":{
              "language_audio":[
                "es",
                "en",
                "fr",
                "de"
              ],
              "language_booklet":[
                
              ],
              "language_live":[
                
              ]
            },
            "booking_parameter":[
              {
                "name":"language",
                "mandatory":true
              },
              {
                "name":"supplier_requested_question",
                "description":" Please provide the dates of birth of everyone in your group and the full names of everyone in your group.",
                "mandatory":true
              },
              {
                "name":"hotel",
                "mandatory":true
              }
            ],
            "questions":{
              "booking_questions":[
                {
                  "question":"booking_customer_accommodation",
                  "description":"",
                  "mandatory":true
                }
              ],
              "participant_questions":[
                {
                  "question":"traveler_dates_of_birth",
                  "description":"Please provide the dates of birth of everyone in your group.",
                  "mandatory":true
                },
                {
                  "question":"traveler_names",
                  "description":"Please provide the full names of everyone in your group.",
                  "mandatory":true
                }
              ],
              "free_text_question":{
                
              },
              "conduction_language":{
                "mandatory":true
              }
            },
            "services":{
              "prt":false,
              "stl":true,
              "wlc":false
            },
            "coordinate_type":"exact",
            "coordinates":{
              "lat":48.83383930000001,
              "long":2.3321611000000075
            },
            "price":{
              "values":{
                "amount":29
              },
              "description":"individual"
            },
            "free_sale":true
          }
        ]
      }
    ]
  }
}

Questions

The questions property allows you to include relevant questions for which answers are required during the booking process.

Unlike the rigid booking_parameter structure, the questions property provides enhanced flexibility for crafting personalized questions, improves user experience through intuitive inquiries, allows easy adjustments as needs change without affecting core code, and offers a future-proof solution that evolves with advancing booking systems.

Let's delve into the four key components that constitute the questions parameter:

  1. booking_questions: Gather booking-related specifics for the whole group, such as accommodation preferences, medical needs, etc.
  2. participant_questions: Collect participant-specific information like dietary restrictions, skill levels, and age, ensuring customized bookings.
  3. free_text_question: Capture open-ended responses for information beyond predefined options, enhancing supplier communication.
  4. conduction_language: Specify preferred communication languages for effective interaction during the booking process.

To illustrate how structured questions come together, let's explore an example of how these questions will be exposed.

Let’s remember from the example above, a small part of the tour option information from GET /options/{option_id} endpoint.

{
  "option_id":265866,
  "title":"Paris: Catacombs Skip-the-Cash-Desk Ticket with Audio Guide",
  "questions":{
    "booking_questions":[
      {
        "question":"booking_customer_accommodation",
        "description":"",
        "mandatory":true
      }
    ],
    "participant_questions":[
      {
        "question":"traveler_dates_of_birth",
        "description":"Please provide the dates of birth of everyone in your group.",
        "mandatory":true
      },
      {
        "question":"traveler_names",
        "description":"Please provide the full names of everyone in your group.",
        "mandatory":true
      }
    ],
    "free_text_question":{
      
    },
    "conduction_language":{
      "mandatory":true
    }
  }
}

We see that to book this activity successfully. The user needs to answer the three mandatory questions (booking_customer_accommodation, traveler_dates_of_birth, traveler_names) as well as the preferred conduction_language. We will see how to pass the answer to these questions in the Posting a booking section of this guide.

Booking parameters (Deprecated)

Note: The information provided in this section is based on a deprecated feature and will no longer be supported. It is recommended to use the questions field to specify booking details.

This particular product only has one tour option so we will pick this one. With this option_id, we can now fetch the availabilities and pricings from the corresponding endpoints. However, there is one more step we have to consider before. Notice the object booking_parameter, which is part of the tour option:

"booking_parameter":[
              {
                "name":"language",
                "mandatory":true
              },
              {
                "name":"supplier_requested_question",
                "description":" Please provide the dates of birth of everyone in your group and the full names of everyone in your group.",
                "mandatory":true
              },
              {
                "name":"hotel",
                "mandatory":true
              }
]

This object defines additional information that needs to be passed in the POST @ /bookings request. We can see that we need to pick a language for this tour option. The available languages can be found in the object cond_language:

"cond_language": {
    "language_audio": [
        "es",
        "en",
        "fr",
        "de"
    ],
    "language_booklet": [],
    "language_live": []
}

So, this tour option has audio guides in multiple languages available. We will see how to pass this information later in this guide's Posting a booking section.

Also, It is important to introduce the hotel where we will stay during the day (e.g., for an activity with pick-up), as well as answer some questions required by our supplier. (If there is any question, they should be visible inside the supplier_requested_question field.

Selecting the time slot and price

Before we can fetch the retail price, we need to know the exact time slot the customer wants. This is because prices are bound to the individual time slots of a given option_id. Please understand that all other prices, such as the one from the /tours endpoint, are not the final retail price and should be displayed as "from XX.YY EUR".

To fetch the availabilities we call the /tours/{tour_id}/availability endpoint:

  • https://api.getyourguide.com/1/tours/66985/availability?cnt-language=en

And get this response:

{
	"tour_id": 66985,
	"participants_range": {
		"min": 1,
		"max": 20
	},
	"categories": [
		{
			"ticket_category": "adult",
			"name": "Adults",
			"age_range": {
				"min": 1,
				"max": 20
			},
			"independently_bookable": true
		}
	],
	"addons": [
		{
			"id": 1934,
			"name": "Free drinks",
			"max_quantity": 10
		}
	],
	"available_dates": [
		{
			"date": "2023-03-16"
		},
		{
			"date": "2023-03-17"
		}
	],
	"update_timestamp": "2023-01-23T04:56:07"
}

The available dates and categories for a tour can be found in the payload above. In the example provided, the customer can select for the given tour only adult as the available ticket category and the dates "2023-03-16" or "2023-03-17" as available dates (We will provide you with all available dates for a tour over the next 16 months). Additionally, customers can select an add-on for the tour (e.g., Add-on “Free drinks” with id: 1934).

Once the customer has selected all the elements they wish to book, we can determine the final retail price as well as the price breakdown by sending a request to /tours/{tour_id}/price-breakdown endpoint:

  • https://api.getyourguide.com/1/tours/66985/price-breakdown

After sending the request, the API will respond with a detailed breakdown of the final retail price for all available time slots grouped by tour option. Each time slot will include the total retail price in price_summary if a valid pricing configuration is found or a specific unavailability reason in unavailability_reason if the tour option cannot be booked at that particular time slot with the configuration provided.

Example of pricing configuration found:

{
  "options":[
    {
      "id":265866,
      "opening_hours":[
        {
          "from":"14:00",
          "to":"18:00"
        }
      ],
      "time_slots":[
        {
          "date_time":"2023-03-24T00:00:00",
          "price_breakdown":{
            "price_summary":{
              "retail_price":100,
              "currency":"EUR"
            },
            "individuals":[
              {
                "independently_bookable":true,
                "max_age":99,
                "min_age":13,
                "price":{
                  "retail_price":25,
                  "currency":"EUR"
                },
                "quantity":4,
                "category_id":1,
                "ticket_category":"adult"
              }
            ]
          },
          "vacancies":20
        }
      ]
    }
  ]
}

If a valid pricing configuration is found, we will have all the necessary inputs to proceed with the booking. These inputs include the category_id delivered in the price breakdown and the quantity of participants from which the price is calculated. In the example above, we calculate the total price for four adults. So, the category_id: 1 is for the ‘adult’ ticket category, and quantity: 4 is for the number of participants requested.

Additionally, the payload received from /tours/{tour_id}/price-breakdown endpoint may include information regarding any discounts available for the tour option. In this case, the discount percentage does not need to be applied to the retail price in price_summary.retail_price as the API is already calculating it.

Considerations when retrieving time slot and price:

If the price configuration is not found, we will provide an unavailability_reason payload, exposing why a valid pricing configuration couldn’t be found. So, the customer needs to readjust the input parameters to retrieve a valid pricing configuration.

{
	"options": [
		{
			"id": 265866,
			"opening_hours": [
				{
					"from": "10:00",
					"to": "18:00"
				}
			],
			"time_slots": [
				{
					"date_time": "2023-03-08T00:00:00",
					"unavailability_reason": {
						"reason_group": "no_availability"
					},
					"vacancies": 20
				}
			]
		}
	]
}

If a valid pricing configuration is found, the calculated price will always be linked to the number of participants and the time slot. Different total retail prices may be calculated depending on the time slot. (e.g., for the same tour option, time slots may have different prices. Because it might be that one timeslot has a discount, and the other doesn't. In that case, the retail_price will be different).

Booking a group:

When booking a tour that offers group pricing, the total price of the activity must be calculated based on the number of participants in the group. To calculate the total price, you should first retrieve the available ticket categories for a tour option by calling the endpoint /tours/{tour_id}/availability. If the ticket category for the activity is "group", you can then proceed to calculate the total price based on the number of participants in the group.

To calculate the total price, you should make a request to the second endpoint /tours/{tour_id}/price-breakdown with the following information:

{
	"base_data": {
		"currency": "USD"
	},
	"data": {
		"date": "2023-09-01",
		"participants": {
			"adult": 8,
			"child": 2
		}
	}
}

In this example, the request is for a group of eight adults and two children. The API will calculate the total price based on the number of participants entered and return it in the price_summary.retail_price field. The price breakdown for each participant will be reflected in the group_breakdown and additional_pax_breakdown structures.

It is important to note that any tour option has either group or individual pricing. They cannot have both at the same time. However, mixing the two types of options in a single tour is possible. In this case, the categories you receive from the /tours/{tour_id}/availability endpoint will reflect the individual pricing configuration since it is more detailed than the group pricing configuration.

If you would like to get pricing for a group at the day-breakdown endpoint, the price breakdown you get is determined by the pricing configuration of the tour option. You should still request the breakdown in the same way, e.g., with {"adults": 8}, and the API will use the pricing configuration to determine how the participants will be split into one or more groups.

Example of price breakdown response for a group booking:

{
	"options": [
		{
			"id": 731278,
			"time_slots": [
				{
					"date_time": "2023-12-21T09:00:00",
					"price_breakdown": {
						"price_summary": {
							"retail_price": 1377,
							"currency": "EUR"
						},
						"groups": [
							{
								"additional_pax_breakdown": {
									"price": {
										"retail_price": 172,
										"currency": "EUR"
									},
									"quantity": 4,
									"ticket_category": "group"
								},
								"group_breakdown": {
									"max_group_size": 4,
									"price": {
										"retail_price": 689,
										"currency": "EUR"
									},
									"quantity": 4,
									"ticket_category": "group"
								}
							}
						]
					}...

Note: If the additional_pax_breakdown structure exists, it indicates that the number of participants introduced exceeds the maximum number of participants allowed for a flat price. As a result, a breakdown of additional pricing per participant is provided. In the example above, the additional_pax_breakdown.price.retail_price is 172 EUR, representing the participant price for participants outside the group flat calculation.

The total retail price is calculated as follows: Total retail price = 1377 EUR = (172 EUR x 4 = pax_breakdown) + (689 EUR group_breakdown).

Posting a booking

We now have all the necessary information to POST a booking. As a reminder, we got the following pieces of information:

Field Value Origin
option_id 265866 /tours/{tour_id}/options
date_time 2023-03-24T00:00:00 /tours/{tour_id}/price-breakdown
category_id 1 /tours/{tour_id}/price-breakdown
price 23.95 /tours/{tour_id}/price-breakdown

Also, to book this activity successfully, the user needs to answer the three mandatory questions (booking_customer_accommodation, traveler_dates_of_birth, traveler_names) and the preferred conduction_language.

When you've successfully gathered all the necessary information to proceed with a booking, you'll utilize the POST /bookings endpoint to make the reservation. It's important to address the mandatory questions within the questions parameter during this stage. These questions are divided into two levels: booking level and participant level.

Here's an example illustrating how the new structure works in practice:

We can assemble our request body for the POST request:

  • https://api.getyourguide.com/1/bookings
{
  "base_data":{
    "cnt_language":"en",
    "currency":"eur"
  },
  "data":{
    "booking":{
      "bookable":{
        "option_id":265866,
        "datetime":"2021-12-26T11:00:00",
        "price":56.40,
        "categories":[
          {
            "category_id":1,
            "number_of_participants":2
          }
        ],
        "questions":{
          "booking_questions":{
            "booking_customer_accommodation":{
              "value":"The Hoxton, Rome"
            }
          },
          "participant_questions":[
            {
              "category_id":1,
              "participant_answers":{
                "traveler_date_of_birth":{
                  "value":"1994-08-01"
                },
                "traveler_name":{
                  "first_name":"John",
                  "last_name":"Doe"
                }
              }
            },
            {
              "category_id":1,
              "participant_answers":{
                "traveler_date_of_birth":{
                  "value":"2000-09-28"
                },
                "traveler_name":{
                  "first_name":"Mary",
                  "last_name":"Doe"
                }
              }
            }
          ],
          "conduction_language":{
            "type":"language_audio",
            "value":"en"
          }
        }
      }
    }
  }
}

To learn more about the input validations configured for these questions, please refer to the Questions Validations page. For a successful booking, we get the following response:

{
    "_metadata": {
        // ...
    },
    "data": {
        "bookings": {
            "shopping_cart_id": 290561411,
            "shopping_cart_hash": "XD93MUY6LTAJKUZHI2F9KHYYWS40T4MX",
            "booking_id": 170250302,
            "booking_hash": "7N1WE8IQU3PI4QJXH1LLSMCDAMJ4QS2GX",
            "status": "temp",
            "return_code": 0
        }
    }
}

If anything does not work out (e.g., the price is incorrectly calculated), you will receive an error response detailing which field contains invalid information.

Posting a booking (Deprecated)

Note: The information provided in this section is based on a deprecated feature and will no longer be supported. It is recommended to use the questions field to specify booking details. If you already use it, please continue to the next section.

We now have all the necessary information to POST a booking. As a reminder, we got the following pieces of information:

Field Value Origin
option_id 265866 /tours/{tour_id}/options
date_time 2023-03-24T00:00:00 /tours/{tour_id}/price-breakdown
category_id 1 /tours/{tour_id}/price-breakdown
price 23.95 /tours/{tour_id}/price-breakdown

Additionally, we must pass the booking_parameter "language" to the booking. Note that value_1 is the array's name in the cond_language object, and value_2 is the actual language. All other possible booking parameters only require value_1 to contain the required information.

With this information, we can assemble our request body for the POST request:

  • https://api.getyourguide.com/1/bookings
{
    "base_data": {
        "cnt_language": "en",
        "currency": "eur"
    },
    "data": {
        "booking": {
            "bookable": {
                "option_id": 265866,
                "datetime": "2021-12-26T11:00:00",
                "price": 28.20,
                "categories": [
                    {
                        "category_id": 781254,
                        "number_of_participants": 1
                    }
                ],
                "booking_parameters": [
                    {
                        "name": "language",
                        "value_1": "language_audio",
                        "value_2": "en"
                    }
                ]
            }
        }
    }
}

For a successful booking, we get the following response:

{
    "_metadata": {
        // ...
    },
    "data": {
        "bookings": {
            "shopping_cart_id": 329056148,
            "shopping_cart_hash": "XD93MUY6LTAJKUZHI2F9KHYYWS40T4MT",
            "booking_id": 170250302,
            "booking_hash": "7N1WE8IQU3PI4QJXH1LLSMCDAMJ4QS2GF",
            "status": "temp",
            "return_code": 0
        }
    }
}

If anything does not work out (e.g., the price is incorrectly calculated), you will receive an error response detailing which field contains invalid information.

Some final remarks

In your first booking of a session, a new cart gets created for a new booking. You can add more bookings to the same cart by passing the shopping_cart_id in the POST @ /bookings request as part of the booking object.

You may pass the additional string parameter external_reference_id as part of the bookable object. This string will be attached to the booking and is available in your analytics. A use case for this field would be to pass your internal booking reference number.

Aside from the credit card encryption, the cart's checkout is very straightforward and will not be discussed in a separate guide.