Skip to content

Making a booking

Luis Rosales Carrera edited this page May 2, 2023 · 43 revisions

TL;DR

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

  • option_id: query from /tours/[tourId]/options
  • start_time: query from /options/[optionId]/availabilities (Deprecated)
  • date_time: query from /tours/[tourId]/price-breakdown (ETA 01.05.2023)
  • retail_price and pricing categories: query from /options/[optionId]/pricings (Deprecated)
  • retail_price and pricing categories: query from /tours/[tourId]/price-breakdown (ETA 01.05.2023)

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 not possible to reflect all the possibilities the API provides in it. 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&ratig[]=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 to 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 just redirect the customer to the URL that is provide 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 you will receive your commission. Pretty easy, right?

Option 2 - Booking and checkout via the API

While Option 1 was easy, it was also pretty boring. So let us now look on the more advanced approach of Option 2. Even though we have the tour_id of the activity 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 it not possible to book tours via the API, but only tour options. Think of tours as a "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}/options endpoint, like so:

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

The response will look like this:

{
    "_metadata": {
        // ...
    },
    "data": {
        "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
                    }
                ],
                "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
            }
        ]
    }
}

Booking parameters

This particular product only has one tour option, so we will just 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
    }
]

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

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

So for this tour option there are audio guides in multiple languages available. We will see how to pass this information later in the Posting a booking section of this guide.

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 for which the price is calculated. In the example above, we calculate the total price for 4 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. Instead, the price_summary.retail_price can be directly introduced in the next stage (/bookings POST endpoint)

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 8 adults and 2 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 given tour option has either group pricing or individual pricing. They cannot have both at the same time. However, it is possible to mix the two types of options in a single tour. 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).

Selecting the time slot and price (Deprecated)

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 /options/{option_id}/availabilities endpoint:

  • https://api.getyourguide.com/1/options/265866/availabilities?cnt_language=en&currency=eur&date[]=2021-12-25T00:00:00&date[]=2021-12-30T23:59:59

And get this response:

{
    "_metadata": {
        // ...
    },
    "data": {
        "availabilities": [
            {
                "start_time": "2021-12-26T11:00:00",
                "pricing_id": 400677,
                "vacancies": 99,
                "discount": 20
            },
            {
                "start_time": "2021-12-26T14:00:00",
                "pricing_id": 400677,
                "vacancies": 99
            },
            // ...
        ]
    }
}

Our customer chooses the first time slot at 2021-12-26T11:00:00, since there is a discount of 20% associated with it. We will need to save the start_time, pricing_id and discount for later. Now we can find out the final retail price for the booking. For this we send a request to the /options/{option_id}/pricings endpoint:

  • https://api.getyourguide.com/1/options/265866/pricings?cnt_language=en&currency=eur
{
    "_metadata": {
        // ...
    },
    "data": {
        "pricing": [
            {
                "pricing_id": 400677,
                "total_minimum_participants": 1,
                "total_maximum_participants": 9,
                "categories": [
                    {
                        "id": 781254,
                        "name": "Adults",
                        "min_age": 18,
                        "stand_alone": true,
                        "addon": false,
                        "scale": [
                            {
                                "min_participants": 1,
                                "max_participants": 100,
                                "retail_price": 29,
                                "net_price": 27.55,
                                "type": "pax"
                            }
                        ]
                    }
                ]
            }
        ]
    }
}

As a response we get the pricing objects for this tour option. There is the possibility for multiple pricing objects in a single tour option, notice that pricing[] is an array. This is why you need the pricing_id from the /options/{option_id}/availabilities endpoint, to select the correct one for the given time slot. Within a single pricing object we have we have different pricing categories. Pricing categories are basically PAX types (e.g. Adults, Children, Seniors ...). In this case we only have one option, Adults for a retail_price of 29 €. The retail_price and net_price are related like so:

  • net_price = retail_price x (100 - your_commission_rate)/100

Now we are almost ready to place our first booking into a cart. We only need to note down the pricing category_id 781254 and calculate the discounted price (yes, we really have to do that ... ):

  • price = retail_price x (100 - discount)/100

Posting a booking

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

From (ETA 01.05.2023)

Field Value Origin
option_id 265866 /tours/{tour_id}/options
date_time 2023-03-24T00:00:00 /tour/{tour_id}/price-breakdown
category_id 1 /tours/{tour_id}/price-breakdown
price_summary.retail_price 28.20 /tours/{tour_id}/price-breakdown

Deprecated:

Field Value Origin
option_id 265866 /tours/{tour_id}/options
start_time 2021-12-26T11:00:00 /options/{option_id}/availabilities
category_id 781254 /options/{option_id}/pricings
price 28.20 /options/{option_id}/pricings

Additionally we need to pass the booking_parameter "language" to the booking. Note that value_1 is the name of the array 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
        }
    }
}

In case anything does not work out (e.g. the price is not calculated correctly), 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.

The checkout of the cart is, aside from the credit card encryption, very straightforward and will therefore not be discussed in a separate guide.

Clone this wiki locally