-
Notifications
You must be signed in to change notification settings - Fork 2
Making a booking
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.
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.
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¤cy=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¤cy=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¤cy=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:
- The booking and checkout process will happen via the GetYourGuide marketplace
- The booking and checkout process will happen via the API
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¤cy=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.
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¤cy=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
}
]
}
]
}
}
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:
- booking_questions: Gather booking-related specifics for the whole group, such as accommodation preferences, medical needs, etc.
- participant_questions: Collect participant-specific information like dietary restrictions, skill levels, and age, ensuring customized bookings.
- free_text_question: Capture open-ended responses for information beyond predefined options, enhancing supplier communication.
- 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.
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.
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).
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.
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.
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.
Copyright 2024 GetYourGuide GmbH.