NAV
shell

Introduction

Hi! welcome to the Booking Experts App Guide. This explains the basic concepts of a Booking Experts App. If you're looking for the API reference, please see our App Api.

We hope you can find your way around. If not, please reach out to api@bookingexperts.nl. We are happy to help and discuss on new ideas.

The best way to build an integration is by using Booking Experts Apps. You could also use the deprecated Tour operator Api but we strongly advise to build Booking Expert Apps from this point onward.

Booking Experts Apps

We call it Booking Experts Apps because it's a little more than a standard integration. You can create an App for your own use or make it publicly available for other Booking Experts users.

It was born out of the many requests for new integrations we got on a weekly basis. We don't want to limit our users so we thought of a set of API's, tooling etc. to facilitate other parties.

With Booking Experts Apps you can think of Booking Experts as a platform. Booking Experts (without Apps) solves 80% of the common use cases in property rental out of the box. We provide and invest in a good and stable foundation while others can extend Booking Experts to fit their needs.

There are many use cases for which Booking Experts Apps is good fit. Examples:

Some use cases are quite specific. See Guides for examples.

App characteristics

An App is a little more than a one-way integration.

Begin with a basic App

Select all the permissions you need and install your App on a single administration. Grab the API key to start making API calls. Sign up for a Demo if you don't have access to a Booking Experts administration.

See API key

Level up with an OAuth2 flow

Drop the API key and implement the OAuth2 flow and submit your App for validation. Now other Booking Experts users can install your App on their administration.

See OAuth2 flow

Demo

Currently Booking Experts Apps is still on invite only basis. To get access to Booking Experts Apps, please use the form below.

We will give you access to a Demo administration so you can start developing.

Sign up for a Demo

After you have access to your demo you can create Apps through the App Store. Follow the icon in your demo administration:

App Store icon

App Setup

Webhooks

Webhooks can be configured in the admin UI for managing Booking Experts Apps.

You can subscribe to various events that happen in Booking Experts. Think of events like reservation:checked_in, reservation:checked_out, reservation:created, category:updated etc. See the admin UI for a complete list.

Requirements

In order to receive webhook events for a resource, the following conditions apply:

Target URL

When an event is triggered in Booking Experts, the target URL you specified will be called. When you have configured the webhook to wait for confirmation, the target URL will be called until a 200 Success response is returned by your server.

Payload

The webhook payload contains the subject formatted in JSON. So when reservation:checked_in is triggered, you will receive a Reservation resource as documented by the Reservation schema.

Commands

Let users trigger an interaction with your App by pressing a button in the Booking Experts UI.

Example use case: - Add a button called "Open door" to every Reservation detail page that can be triggered by receptionists.

Command responses

A submitted Command will cause a payload of data to be sent from Booking Experts to your App. The App can respond in different ways using the context provided by that payload.

I-frame URL

{ "iframe_url": "http://your-app.com/action-to-perform" }

The url is loaded in an iFrame inside Booking Experts. This gives the user the feeling that it all happens inside one system. Use our the Booking Experts Design System to create UIs by using UI components that are familiar to Booking Experts users.

Note: As a security measure, most browsers will require iframe sources to be on the same domain as the parent domain. If you are planning to use iFrames, please let us know so we can support your domain (for example by adding appropriate CORS headers).

Redirect URL

{ "redirect_url": "http://your-app.com/action-to-perform" }

The user is redirected to this URL. It's the responsibility of the App to provide a way back to Booking Experts.

Notice message

{ "notice": "Great success!" }

The message is shown to the user in a flash message inside Booking Experts.

Alert message

{ "alert": "Something went wrong!" }

The alert is shown to the user in a flash message inside Booking Experts.

Context model

Choose a context model to make commands available for a specific model. For example, when you choose reservation, the command will be available for reservations. Mostly on the #show page (in CRUD terms) but not exclusively.

Activation script

# Show the button when the reservation state is confirmed
reservation.dig(:data, :attributes, :state) == 'confirmed'

# Show the button when the reservation state is confirmed and its start date is equal or greater than the current date
reservation.dig(:data, :attributes, :state) == 'confirmed' && reservation.dig(:data, :attributes, :start_date) >= Time.zone.today

# Show the button when the invoice total is greater than 1.0 + 3.0 (4.0)
invoice.dig(:data, :attributes, :total, :value) > 1.0 + 3.0

# Show the button when the invoice number is present and the unpaid total is not 0.0
invoice.dig(:data, :attributes, :invoice_nr).present? && invoice.dig(:data, :attributes, :total_open, :value) != 0.0

# Show the button when the invoice number is blank
invoice.dig(:data, :attributes, :invoice_nr).blank?

When set, this script will be evaluated to determine whether a command should be available to a user. The last line of your script will be the result of the script. When the result is truthy, the command will be enabled.

The following variables will be available to you:

The script language MUST be Ruby. The list of method calls you are allowed to make is limited to the following:

Allowed methods on any object:

== != ! > < >= <= present? blank?

Allowed methods on hash objects:

[] dig

Allowed calculation methods:

+ - * /

Message templates

Define standard templates for communication with a guest.

Booking Experts takes care of delivering the message to the guest. Messages can be delivered by e-mail or SMS. Configured templates can be modified by administrators of an administration to suit their way of communication.

Booking Experts variables

{{ reservation.greeting }},

Welcome to {{ reservation.park.name }}!

The access code for accommodation {{ accommodation.name }} is: {{ access_code }}.

See you!

Within a template, you can use variables that are provided by Booking Experts, which are interpolated when the message is sent. Please see the Templates section of our support documentation for details.

Custom variables

Aside from Booking Experts variables, you can supply your own variables. Upon sending a message the App must provide the values for these custom variables. In the example on the right {{ access_code }} would be a custom veriable passed by the App.

Sending a message using a message template

See POST messages

Validation

Done with making your test integration? Let's book a validation. After validation, your App will be available in the Booking Experts App Store. Users can install your App at the touch of a button. A great way to reach new customers 😉.

A validation can be requested from the App admin page. The only requirement is an implemented OAuth2 flow.

Authentication

API key

Ideal for making one off Apps for your own use.

OAuth2 flow

You can authenticate organizations by using OAuth2. For on the fly needs you can make use of an API key but this has limited access to only the organization and its administrations to which it is registered.

If you want others to make use of your app you will need to support OAuth2. The flow for authenticating organizations via OAuth2 is described in the following diagram:

OAuth2 authentication flow

App API key

The App API key can only be used for the 'App' namespace of the API. This namespace contains endpoints that are not dependent on a specific organization or administration. For example, the App namespace provides the 'App Availabilities' endpoint, which can be used to perform an Availabilty Search over multiple subscriptions at once.

Guides

BookingExperts API client

There is a BookingExperts client example available, written in Ruby. This client supports the following use cases:

Orders & Reservations

Currently, Booking Experts only supports creating reservations for a single accommodation. Support for ordering multiple accommodations is under active development. This will be accomplished by supporting Orders. Orders will be able to contain multiple Reservations, as illustrated in the diagram below.

Order definition

The current API leverages this redesigned structure by already supporting creation of orders with a single reservation via POST reservation. When creation of orders with multiple reservations is added, this will become possible via the endpoint POST order.

Sideposting

As can be seen in the diagram, a Reservation belongs to an Order and and Order belongs to a Customer. As the JSON:API specification does not yet support submission of multiple resources at once, we have introduced the concept of Sideposting. Sideposting will allow you to post new related resources using the standard included array. These resources don't have an ID yet, therefore it is required to link these resources by setting /meta/temp_id. You will also need to define what the system needs to do with these resources by specifying /meta/method (= create or update) on the relationship.

Creating a reservation

Endpoint: POST reservation

{
  "data": {
    "type": "reservation",
    "attributes": {
      "start_date": "2014-01-08",
      "end_date": "2014-01-15",
      "guest_group": {
        "seniors": 0,
        "adults": 2,
        "adolescents": 0,
        "children": 0,
        "babies": 0,
        "pets": 0
      }
    },
    "relationships": {
      "category": {
        "data": {
          "id": "123",
          "type": "category"
        }
      }
    }
  }
}

For a reservation to be accepted, you need to (at least) specify the following attributes:

A valid example can be seen on the right.

Creating an option

Endpoint: POST reservation

{
  "data": {
    "type": "reservation",
    "attributes": {
      "start_date": "2014-01-08",
      "end_date": "2014-01-15",
      "option_validity": 5,
      "guest_group": {
        "seniors": 0,
        "adults": 2,
        "adolescents": 0,
        "children": 0,
        "babies": 0,
        "pets": 0
      }
    },
    "relationships": {
      "category": {
        "data": {
          "id": "123",
          "type": "category"
        }
      }
    }
  }
}

In Booking Experts, an option is just a kind of reservation. To create an option, you can use the same endpoint and pass the option_validity attribute, which defines the option validity in days.

Creating a reservation with customer and order details

Endpoint: POST reservation

{
  "data": {
    "type": "reservation",
    "attributes": {
      "start_date": "2014-01-08",
      "end_date": "2014-01-15",
      "late_checkout": false,
      "remote_booking_nr": "12345",
      "locale": "en",
      "note": "Please note",
      "currency": "EUR",
      "price_according_to_channel": { "currency": "EUR", "value": "85.75" },
      "guest_group": {
        "seniors": 0,
        "adults": 1,
        "adolescents": 0,
        "children": 1,
        "babies": 0,
        "pets": 0
      },
      "license_plates": ["19XNZ1"]
    },
    "relationships": {
      "category": {
        "data": {
          "id": "123",
          "type": "category"
        }
      },
      "order": {
        "data": {
          "type": "order",
          "meta": {
            "temp_id": "order-id",
            "method": "create"
          }
        }
      },
      "primary_package": {
        "data": {
          "id": "1",
          "type": "primary_package"
        }
      },
      "extra_order_items": {
        "data": [{
          "type": "extra_order_item",
          "meta": {
            "temp_id": "extra-id",
            "method": "create"
          }
        },{
          "type": "extra_order_item",
          "meta": {
            "temp_id": "extra-package-id",
            "method": "create"
          }
        }]
      },
      "cancellation_rules": {
        "data": [{
          "type": "cancellation_rule",
          "meta": {
            "temp_id": "cancellation-rule-1",
            "method": "create"
          }
        },{
          "type": "cancellation_rule",
          "meta": {
            "temp_id": "cancellation-rule-2",
            "method": "create"
          }
        }]
      }
    }
  },
  "included": [{
    "type": "order",
    "attributes": {
      "after_payment_return_url": "https://www.example.com/callback",
      "redeemable_codes": ["DISCOUNT2021"]
    },
    "relationships": {
      "customer": {
        "data": {
          "type": "customer",
          "meta": {
            "temp_id": "customer-id",
            "method": "create"
          }
        }
      }
    },
    "meta": {
      "temp_id": "order-id"
    }
  },{
    "type": "customer",
    "attributes": {
      "title": "family",
      "first_name": "RIV",
      "last_name": "Rowe",
      "email": "leonmcglynn@example.com",
      "phone": "+31612345678",
      "is_company": false,
      "date_of_birth": "1955-05-05",
      "receive_newsletter": false,
      "address": "78229 Marquerite Flat",
      "number": null,
      "postalcode": "03756",
      "city": "Amsterdam",
      "country_code": "NL",
      "has_custom_invoice_details": true,
      "custom_invoice_details": {
        "name": "Ali",
        "email": "mycompany@example.com",
        "address": "Het Eeftink 11-12",
        "postalcode": "7541 WH",
        "city": "Enschede",
        "country_code": "NL"
      }
    },
    "meta": {
      "temp_id": "customer-id"
    }
  },{
    "type": "extra_order_item",
    "attributes": {
      "quantity": 2,
      "guest_answer": "Guest answer"
    },
    "relationships": {
      "extra": {
        "data": {
          "id": "245",
          "type": "extra"
        }
      }
    },
    "meta": {
      "temp_id": "extra-id"
    }
  },{
    "type": "extra_order_item",
    "relationships": {
      "extra": {
        "data": {
          "id": "101",
          "type": "extra_package"
        }
      }
    },
    "meta": {
      "temp_id": "extra-package-id"
    }
  },{
    "type": "cancellation_rule",
    "attributes": {
      "percentage": 33.3,
      "days_before_arrival": 28,
      "administration_costs": { "currency": "EUR", "value": "0.00" }
    },
    "meta": {
      "temp_id": "cancellation-rule-1"
    }
  },{
    "type": "cancellation_rule",
    "attributes": {
      "percentage": 100,
      "days_before_arrival": 14,
      "administration_costs": { "currency": "EUR", "value": "10.00" }
    },
    "meta": {
      "temp_id": "cancellation-rule-2"
    }
  }]
}

Usually, you'll want to add some Customer details. Also, certain Order attributes may be relevant as well, like redeemable codes or an after payment return url. In this case, you will need to use Sideposting to include these details when creating a new reservation.

The example on the right is a more complex example that shows how this can be accomplished This example does not only specify order and customer details, but also includes some ordered extras (ExtraOrderItem) and a couple of custom cancellation rules (CancellationRule). Resource linking has been realized by properly setting temp_id. As these resources are new, the method passed in each relationship is create.

Updating a reservation

Endpoint: PATCH reservation

{
  "data": {
    "id": "3245",  
    "type": "reservation",
    "attributes": {
      "start_date": "2014-01-08",
      "end_date": "2014-01-15",
      "currency": "EUR",
      "guest_group": {
        "seniors": 0,
        "adults": 2,
        "adolescents": 0,
        "children": 0,
        "babies": 0,
        "pets": 0
      }
    },
    "relationships": {
      "category": {
        "data": {
          "id": "123",
          "type": "category"
        }
      },
      "order": {
        "data": {
          "id": "3245",        
          "type": "order",
          "meta": {
            "method": "update"
          }
        }
      }
    }
  },
  "included": [{
    "id": "3245",  
    "type": "order",
    "attributes": {
      "redeemable_codes": ["DISCOUNT2021", "FREEFORALL"]
    }
  }]
}

A reservation and its order and customer details can be updated much in the same way as creating reservations. Of course, for existing resources you are now required to pass their actual ID in stead of their temp_id. If you like to update a related resource, you need to explicitly specify this by setting /meta/method to update on the relationship. As an example, consider the code on the right. It shows an example of how to update the redeemable codes array of the order of a reservation.

Cancelling a reservation

Endpoint: POST cancel

{
  "data": {
    "id": "3245",  
    "type": "reservation",
    "attributes": {
      "cancel_date": "2014-01-05",
      "cancel_reason": "CORONA",
    }
  }
}

A reservation can be cancelled by sending an existing reservation resource. Optionally, you can pass the cancel_date and cancel_reason attributes to set a custom date and reason for cancelling.

Access control

Access control systems can build an App to provide access to guests or accommodation owners of an administration. Using webhooks, access can automatically be granted at the appropriate time for reservations. Furthermore, commands can be used for manual creation of access cards or exemptions.

Setup

Nearly every app will have some form of authentication. You can easily create a Settings page for your app by defining it as a command using the context model subscription:

Settings command example

{ "iframe_url": "http://your-app.com/settings" }

When called, you can then respond with a redirect to your Settings page. An example response can be seen on the right.

Automatically granting access for reservations

Typically, the following webhooks are needed for automatically granting access for reservations:

Manually granting access for reservations

Using commands, you can also add buttons to reservations to allow users of the system to manually manage access for a reservation.

Energy management

{
  "data": [
    {
      "type": "measurement",
      "attributes": {
        "timestamp": "2017-05-23T11:54+02:00",
        "utility": "electricity",
        "rate": null,
        "value": 49.9
      }
    },
    {
      "type": "measurement",
      "attributes": {
        "timestamp": "2017-05-23T11:54+02:00",
        "utility": "gas",
        "rate": null,
        "value": 40.584
      }
    },
    {
      "type": "measurement",
      "attributes": {
        "timestamp": "2017-05-24T11:54+02:00",
        "utility": "electricity",
        "rate": null,
        "value": 52.9
      }
    },
    {
      "type": "measurement",
      "attributes": {
        "timestamp": "2017-05-24T11:54+02:00",
        "utility": "gas",
        "rate": null,
        "value": 45.223
      }
    }
  ]
}

Energy management system Apps are usually implemented in the same way as Access Control Apps: there is some initial configuration, after which all booked accommodations are managed by listening to Reservation webhook events.

Supplying measurements

As it is possible in Booking Experts to store accommodation measurements, an Energy Management App is also able to send measurements back to Booking Experts. This can be realized by creating a special command that willl only be called by Booking Experts when an App is defined as an Energy system.

The command itself must conform to the following specifications:

Payments

Payment service providers can build an App to handle payments. There are a couple of requirements. See below the headers below.

Settings

Apps will most likely have to be configured with api keys and other credentials. In this case, we recommend adding a command with identifier settings and context model payment_request to allow the user to configure this credentials for a specific payment_handler. The user can create multiple payment_handlers within a single administration for a single payment service provider app, each with different configurations, so we recommend you to store these credentials together with a payment handler id.

Initiate payment

Payload for the endpoint of the initiate_payment command

{
  "data": {
    "id": "541",
    "type": "payment_request",
    "attributes": {
      "price": { "currency": "EUR", "value": "312.56" },
      "locale": "nl",
      "description": "Rent for reservation B123"
    },
    "relationships": {
      "payment_handler": {
        "data": {
          "id": "24",
          "type": "payment_handler"
        }
      },
      "debtor": {
        "data": {
          "id": "11",
          "type": "debtor"
        }
      },
      "administration": {
        "data": {
          "id": "3",
          "type": "administration"
        }
      }
    }
  }
}

Response to the initiate_payment command, that will redirect the customer to the given url.

{ "redirect_url": "https://mypaymentprovider.com/start_payment/2abe47d423f0" }

Endpoint: POST payment_results

{
  "data": {
    "type": "payment_result",
    "attributes": {
      "amount": {
        "currency": "EUR",
        "value": "312.56"
      },
      "result": "success",
      "enable_recurring_payments": false
    },
    "relationships": {
      "payment_request": {
        "data": {
          "id": "541",
          "type": "payment_request"
        }
      }
    }
  }
}

Apps that act as a payment provider must provide an command with identifier initiate_payment and context model payment_request. This command will be invoked when a user makes an online payment.

The response of this command must be a redirect URL to a payment page when no issues occur, otherwise it should return a notice or an alert.

When you receive a status update on the payment from the psp, e.g. when the payment is successful or failed, you must invoke the payment result endpoint to update the payment state of the payment request. After having done this, the user must be redirected to the url passed as the return_url parameter to the initate_payment command.

To the right, there is an example of a payment_request, sent to the endpoint handling the initiate_payment command, followed by the response to this command, and an example of the payment_result that the app sent when a payment arrives. See POST payment_results for more information about payment_results.

Recurring payments

Apps that support recurring payments must provide commands with identifiers initiate_initial_recurring_payment, initiate_recurring_payment and delete_mandates, and respective context models of payment_request, payment_request and debtor.

Initiate initial recurring payment

Endpoint: POST payment_results

{
  "data": {
    "type": "payment_result",
    "attributes": {
      "amount": {
        "currency": "EUR",
        "value": "312.56"
      },
      "result": "success",
      "enable_recurring_payments": true
    },
    "relationships": {
      "payment_request": {
        "data": {
          "id": "541",
          "type": "payment_request"
        }
      }
    }
  }
}

This command will be invoked when an owner wants to give a mandate for automatic recurring payments.

The response of this command must be a redirect URL to a payment page when no issues occur, otherwise it should return a notice or an alert.

When the mandate is succesful you must communicate this using the payment_result endpoint and enable recurring payments by setting the enable_recurring_payments parameter.

After having done this, the user must be redirected to the url passed as the return_url parameter to the initate_payment command.

All in all it is very similar to the initiate_payment command, except for having to set the enable_recurring_payments property as shown in the example.

Initiate recurring payment

Response to the initiate_recurring_payment command if successful

{ "data": { "success": true } }

Response to the initiate_recurring_payment command if failed

{ "data": { "success": false, "error": "no_mandate" } }

This command will be invoked whenever a recurring payment is made. This always happens without any user involvement. Instead of a redirect you must return the json shown to the right. If unsuccessful, you may return one of the following errors:

Delete mandates

Response to the delete_mandates command if successful

{ "data": { "success": true } }

Response to the delete_mandates command if failed

{ "data": { "success": false, "error_message": "Mandate does not exist" } }

This command will be invoked whenever an owner want to delete their mandate. In that case you could delete the mandate in the psp. You must return the json shown to the right.

Sync payment methods

Response to the sync_payment_methods [command](#commands]

{
  "data": {
    "payment_methods": [ "ideal", "bank_transfer" ]
  }
}

Apps may provide a command with identifier sync_payment_methods and a payment_handler context model. If this command is present the user managing the payment service provider cannot configure which payment methods are supported in BE.

This command will be invoked with a payment handler whenever a payment handler is created, and periodically afterwards. Whenever it is called you should return a list of supported payment methods for the payment handler like shown in the example on the right. Valid payment methods are:

Redirect to psp backoffice

Payload for the endpoint of the redirect_to_psp_backoffice command

{
  "data": {
    "id": "541",
    "type": "payment_request",
    "attributes": {
      "price": { "currency": "EUR", "value": "312.56" },
      "locale": "nl",
      "description": "Rent for reservation B123"
    },
    "relationships": {
      "payment_handler": {
        "data": {
          "id": "24",
          "type": "payment_handler"
        }
      },
      "debtor": {
        "data": {
          "id": "11",
          "type": "debtor"
        }
      },
      "administration": {
        "data": {
          "id": "3",
          "type": "administration"
        }
      }
    }
  }
}

Response to the redirect_to_psp_backoffice command, that will redirect the customer to the given url.

{ "redirect_url": "https://mypaymentprovider.com/show_payment/2abe47d423f0" }

Apps may provide a command with identifier redirect_to_psp_backoffice and a payment_request context model. If this command is present, payment requests in BE will have a link the user can click that invokes this command. The response of this command must be a redirect to the page that shows this payment request in the psp backoffice, so the user can inspect the payment's status in there. See the example on the right.

Master price lists

Each Category in Booking Experts is associated to a Master price list. A master price list can consist of simple (night) prices or complex (LOS) prices. To support dynamic price updates, apps can manage these master price lists if they have the appropriate permission (master_price_list|write). This guide explains how you can define and update prices of master price lists.

Creating a master price list

An app can create master price lists using the endpoint POST master_price_list. Only a name is required, but you do not need to provide any prices here (although this is possible). The administration can associate this price list to any existing category.

NOTE With the permission master_price_list|write, any master price list within the administration can be updated. However, the recommendation is to create a separate price list instead to ensure manually defined price lists are not overwritten by accident.

Sideposting prices

Prices of master price lists can only be created or updated in bulk by using Sideposting. Sideposting will allow you to define prices using the standard JSONAPI included array. Prices are upserted by checking for existing resources based on date and length_of_stay (for complex prices). You don't need to specify an ID for these prices, but in order to link these resources in your input, you need to define /meta/temp_id. Also /meta/method must be set to create as a logical consequence of the missing IDs.

Creating a master price list

Endpoint: POST master_price_list

The example on the right created a new Master price list with name 'My master price list'. The master price list will be returned in the response if successful.

{
  "data": {
    "type": "master_price_list",
    "attributes": {
      "name": "My master price list"
      }
    }
  }
}

Updating a master price list's simple prices

Endpoint: PATCH master_price_list

The example on the right updates a Master price list with some simple prices. The master price list will be returned in the response if successful.

{
  "data": {
    "id": "1",
    "type": "master_price_list",
    "relationships": {
      "simple_prices": {
        "data": [{
          "type": "simple_price",
          "meta": {
            "temp_id": "price1",
            "method": "create"
          }
        }, {
          "type": "simple_price",
          "meta": {
            "temp_id": "price2",
            "method": "create"
          }
        }]
      }
    }
  },
  "included": [{
    "type": "simple_price",
    "attributes": {
      "date": "2014-01-01",
      "price": {
        "currency": "EUR",
        "value": "12.75"
      }
    },
    "meta": {
      "temp_id": "price1"
    }
  }, {
    "type": "simple_price",
    "attributes": {
      "date": "2014-01-02",
      "price": {
        "currency": "EUR",
        "value": "14.15"
      }
    },
    "meta": {
      "temp_id": "price2"
    }
  }]
}

Updating a master price list's complex prices

Endpoint: PATCH master_price_list

The example on the right updates a Master price list with some complex prices. The master price list will be returned in the response if successful.

{
  "data": {
    "id": "1",
    "type": "master_price_list",
    "relationships": {
      "complex_prices": {
        "data": [{
          "type": "complex_price",
          "meta": {
            "temp_id": "price1",
            "method": "create"
          }
        }, {
          "type": "complex_price",
          "meta": {
            "temp_id": "price2",
            "method": "create"
          }
        }]
      }
    }
  },
  "included": [{
    "type": "complex_price",
    "attributes": {
      "arrival_date": "2014-01-01",
      "length_of_stay": 7,
      "price": {
        "currency": "EUR",
        "value": "12.75"
      }
    },
    "meta": {
      "temp_id": "price1"
    }
  }, {
    "type": "complex_price",
    "attributes": {
      "arrival_date": "2014-01-15",
      "length_of_stay": 5,
      "price": {
        "currency": "EUR",
        "value": "14.15"
      }
    },
    "meta": {
      "temp_id": "price2"
    }
  }]
}

Tour operators

A tour operator App is usually responsible for the following actions:

This guide is applicable to Channel management as well.

Relevant permissions

A tour operator will usually need the following permissions:

Relevant webhooks

A tour operator will usually need the following webhooks:

Creating a channel

{
  "data": {
    "type": "channel",
    "attributes": {
      "name": "TourOperator.com",
      "kind": "tour_operator",
      "available_currencies": ["EUR"]
    }
  }
}

Channels in Booking Experts are used to track the origin of reservations. A channel can also store the pricing type used. We currently support 'simple prices' (night prices) and 'complex prices' (LOS prices). When an organization subscribes to your App, it is possible to associate a channel to each administration of the organization you get access to. However, it is also possible for the App to create a new channel instead.

To create a channel, you will need the channel|write permission. Please see the POST channels endpoint for more information. An example can be seen on the right.

Pushing availability and prices

Availability and prices for a category can be fetched in a couple of ways. The way to go is dependent on the information you need and the pricing type that you use. You will need the avaiability|read permission for these endpoints.

Note Currently, when receiving a category_reindexed event, you need to manually figure out which prices have changed if you only want to store differences in availability and prices. This can easily be achieved by storing the last processed response and comparing this to the current response.

Pushing updates to Booking Experts

To create, update or cancel reservations, you can use the Channel Reservations endpoint. You will need the reservation|write permission to do this. More information on how to create or update reservations van be found in the Reservations guide.

When things go wrong

Occasionally, problems may occur when keeping reservations in sync. For example, a conflict may occur when a certain period has already been booked in Booking Experts. For these cases, it is possible to send an internal message to the administration. You can use the POST internal_message endpoint. You will need the internal_message|write permission to do this.

Upgrading

This guide contains details on how to upgrade from the Tour operator API (V2) to the App API (V3).

Authentication

The Tour operator API authenticates using an API key for an Agency. Access to multiple administrations is realized by associating channels with the Agency. For the App API, you will need an App to be able to connect. Furthermore, organizations need to install your App, resulting in a Subscription. Each subscription will provide you with an API key or OAuth2 token that can be used to fetch metadata that is part of the organization.

Channel Access

In the App API, there are two ways in which channels can be associated to a Subscription:

Fetching prices & availability

The App API has introduced a 'Channel' namespace for fetching prices and availability. These API calls require you to specify a channel_id, which must be the ID of a channel that has been associated with your App.

Previewing & creating Reservations

Reservations must be created via the Channel namespace, requiring you to pass a channel_id. The Tour operator API only knows the concept of a Reservation, while the App API also introduces the concept of an Order and a Customer. Where originally you would be able to specify customer and order data on the Reservation itself, you will now need to split these. The attribute names are mostly the same though. Please see the Orders & Reservations guide for examples.

A Reservation preview can be created in exactly the same way as creating a Reservation, but it uses a different API endpoint.

Available add-ons (discount cards / primary packages / extras)

Available add-ons can be fetched by including the optional relationships available_discount_cards, available_primary_packages and available_extras when fetching (previewing) a Reservation. The included resources will contain the price of the add-on when it is added to the Reservation.

In the Tour operator API, extras needed to be ordered by using the chosen_extras attribute. In the App API, this is handled via the extra_order_items relationship. To add an extra, you need to specify an ExtraOrderItem that has a relationship to an Extra.

Options

Options are very similar to Reservations, that's why there is no special endpoint anymore for creating Options. To create an Option, you should just create a Reservation in which the attribute option_validity is specified.

The 'All Reservations' Endpoint

The Tour operator API has an experimental endpoint to retrieve all reservations of an administration. The App API just exposes this outside of the Channel namespace in GET reservations. You will need the reservation|read permission to use this. Note that you will also need the order|read and customer|read permissions to be able to read personal details.

Blog

This blog will elaborate on changes made to the App API.

Renaming Tags and Custom Attributes to Amenities

Date: June 21, 2021 - Affected versions: 0.0.4

Within BE, there has always been a lot of confusion regarding the term 'Tag'. Externally, they were always translated as 'Features' or 'Amenities', but internally they were represented by tags. Over time, the need for assigning values to Tags was introduced, for example to store the number of square meters when the Tag 'Garden' was associated to an accommodation. These were externally exposed as 'Custom Attributes', while they were still stored as Tags in the database.

As of App API release 0.0.4, Tags in BE will be internally and externally available as Amenities instead. If you are using Tags in the App API, you should rename all occurrences of Tags or Custom Attributes to Amenities after this release. Please see the Release Notes for a full list of changes. To allow for a smooth transition, support for Tags will remain in place until the date of removal as mentioned in the Release Notes has passed.

NOTE The TourOperator API will remain unaffected.

Release Notes

All notable changes to this API will be documented here.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[0.4.0] - 2021-09-08

Removed

[0.3.1] - 2021-09-07

Fixed

[0.3.0] - 2021-09-01

Added

[0.2.0] - 2021-08-26

Added

[0.1.0] - 2021-08-25

Added

[0.0.4] - 2021-07-28

Deprecated

[0.0.3] - 2021-06-10

Added

[0.0.2] - 2021-01-05

Added

[0.0.1] - 2020-07-13

Added