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 following attributes:

Event metadata

Event metadata may be passed for certain events:

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 options

Often, you'd like your command to be available when the context model meets certain conditions. You can choose which conditions should apply here. Please let us know when you're missing any options!

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 {{ 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

1) User clicks "Install" in App Store 2) User is redirected to callback url hosted by the App. The Authorization Response is send to the App Server. For example:

HTTP/1.1 302 Found
Location: https://your-domain.com/booking_experts/oauth_callback?code=P66tmn20oiwyJRoHsN1iyNOzy37vKpRg8pEapG7s_ps

3) The App can then use the code url parameter to fetch the OAuth tokens. The App sends the Authorization Code Request For example:

POST /oauth/token HTTP/1.1
Host: app.bookingexperts.nl

code=P66tmn20oiwyJRoHsN1iyNOzy37vKpRg8pEapG7s_ps
&grant_type=authorization_code
&redirect_uri=https://your-domain.com/booking_experts/oauth_callback
&client_id=[CLIENT_ID]
&client_secret=[CLIENT_SECRET]

4) Booking Experts server returns the Access Token Response for example:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
  "access_token":"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3",
  "token_type":"Bearer",
  "expires_in":604800,
  "refresh_token":"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk",
  "scope":"create"
}

5) Now you can make API requests to the Booking Experts API for example:

curl --request GET \
  --url https://api.bookingexperts.nl/v3/administrations \
  --header 'accept: application/vnd.api+json' \
  --header 'accept-language: en,nl' \
  --header 'authorization: Bearer [ACCESS_TOKEN]'

6) After a while the access token will expire. In that case you will receive a response from the Booking Experts something like:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token" error_description="The access token expired"
Content-type: application/json
{
  "error": "invalid_token",
  "error_description": "The access token expired"
}

7) In that case you should request a new acces_token using the refresh_token. The App should send a Refresh token request.

POST /oauth/token HTTP/1.1
Host: app.bookingexperts.nl

grant_type=refresh_token
&refresh_token=[REFRESH_TOKEN]
&client_id=[CLIENT_ID]
&client_secret=[CLIENT_SECRET]

8) The Booking Experts server returns a Token response which should be used for new API requests untill the token expires (see step 6.)

{
 "access_token": "BWjcyMzY3ZDhiNmJkNTY",
 "refresh_token": "Srq2NjM5NzA2OWJjuE7c",
 "token_type": "Bearer",
 "expires": 3600
}

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:

Bookings & 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 Bookings. Bookings will be able to contain multiple Reservations, as illustrated in the diagram below.

Booking definition

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

Sideposting

As can be seen in the diagram, a Reservation belongs to an Booking and and Booking 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": {
      "rentable_type": {
        "data": {
          "id": "123",
          "type": "rentable_type"
        }
      }
    }
  }
}

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": {
      "rentable_type": {
        "data": {
          "id": "123",
          "type": "rentable_type"
        }
      }
    }
  }
}

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 booking details

Endpoint: POST reservation

{
  "data": {
    "type": "reservation",
    "attributes": {
      "start_date": "2014-01-08",
      "end_date": "2014-01-15",
      "late_checkout": false,
      "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": {
      "rentable_type": {
        "data": {
          "id": "123",
          "type": "rentable_type"
        }
      },
      "booking": {
        "data": {
          "type": "booking",
          "meta": {
            "temp_id": "booking-id",
            "method": "create"
          }
        }
      },
      "primary_package": {
        "data": {
          "id": "1",
          "type": "primary_package"
        }
      },
      "extra_packages" : {
        "data": [{
          "id": "101",
          "type": "extra_package"
        }]
      },
      "extra_order_items": {
        "data": [{
          "type": "extra_order_item",
          "meta": {
            "temp_id": "extra-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": "booking",
    "attributes": {
      "remote_booking_nr": "12345",
      "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": "booking-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": "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 Booking 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 booking 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": {
      "rentable_type": {
        "data": {
          "id": "123",
          "type": "rentable_type"
        }
      },
      "booking": {
        "data": {
          "id": "3245",
          "type": "booking",
          "meta": {
            "method": "update"
          }
        }
      }
    }
  },
  "included": [{
    "id": "3245",
    "type": "booking",
    "attributes": {
      "redeemable_codes": ["DISCOUNT2021", "FREEFORALL"]
    }
  }]
}

A reservation and its booking 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 booking of a reservation.

Payment of reservations

To initiate a payment of a Reservation, you should look at the Booking of the Reservation. When this Booking can be paid, it will have a pay_url link to which the user should be redirected. After payment, the user will be redirected back to the after_payment_return_url that was set when the Booking was created. A query parameter status will be passed that can have the following values: success, open_but_confirmed, failure, failure_final or cancelled.

Error codes

When mutating reservations fail, error messages usually contain a detailed error code in meta.error, which you can use to generate a custom error message.

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

{
  "command": "measurements",
  "back_url": null,
  "locale": "en",
  "subscription_id": "1",
  "record": {
    "data": {
      "id": "1",
      "type": "rentable_identity",
      "attributes": {
        "name": "1"
      }
    }
  },
  "start_date": "2017-05-23",
  "end_date": "2017-05-24"
}
{
  "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": "low",
        "value": 52.9
      }
    },
    {
      "type": "measurement",
      "attributes": {
        "timestamp": "2017-05-24T11:54+02:00",
        "utility": "gas",
        "rate": "high",
        "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:

An example request and response can be seen on the right. When your endpoint is called, you will receive the metadata of the RentableIdentity and the desired start_date and end_date of the measurements that are requested by the user.

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_handler to allow the user to configure these 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 back_url parameter to the initiate_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 initiate_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.

Reviews

Endpoint: POST review

Creating reviews

A review can be created for a given ReviewForm. Your Review input should therefore also correspond to this form. Each ReviewQuestion of the ReviewForm must have a ReviewAnswer. This can be achieved by using Sideposting: the ReviewAnswers are sideposted in the included object. An example Review can be seen on the right. Each ReviewAnswer must have a relationship to a ReviewQuestion belonging to the ReviewForm. 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 on the relationship.

{
  "data": {
    "type": "review",
    "attributes": {
      "name": "M Rowe",
      "locale": "nl",
      "stayed_nights": 7,
      "stayed_date": "2014-01-15",
      "original_identifier": "review-1"
    },
    "relationships": {
      "rentable_type": {
        "data": {
          "id": "1",
          "type": "rentable_type"
        }
      },
      "review_answers": {
        "data": [
          {
            "type": "review_answer",
            "meta": {
              "temp_id": "open-answer-id",
              "method": "create"
            }
          },{
            "type": "review_answer",
            "meta": {
              "temp_id": "boolean-answer-id",
              "method": "create"
            }

          },{
            "type": "review_answer",
            "meta": {
              "temp_id": "rating-answer-id",
              "method": "create"
            }
          },{
            "type": "review_answer",
            "meta": {
              "temp_id": "not-applicable-answer-id",
              "method": "create"
            }
          }
        ]
      }
    }
  },
  "included": [{
    "type": "review_answer",
    "attributes": {
      "answer": "It was great"
    },
    "relationships": {
      "review_question": {
        "data": {
          "id": "1",
          "type": "review_question"
        }
      }
    },
    "meta": {
      "temp_id": "open-answer-id"
    }
  },{
    "type": "review_answer",
    "attributes": {
      "boolean_answer": "true"
    },
    "relationships": {
      "review_question": {
        "data": {
          "id": "2",
          "type": "review_question"
        }
      }
    },
    "meta": {
      "temp_id": "boolean-answer-id"
    }
  },{
    "type": "review_answer",
    "attributes": {
      "rating": 8
    },
    "relationships": {
      "review_question": {
        "data": {
          "id": "3",
          "type": "review_question"
        }
      }
    },
    "meta": {
      "temp_id": "rating-answer-id"
    }
  },{
    "type": "review_answer",
    "attributes": {
      "not_applicable": true
    },
    "relationships": {
      "review_question": {
        "data": {
          "id": "4",
          "type": "review_question"
        }
      }
    },
    "meta": {
      "temp_id": "not-applicable-answer-id"
    }
  }]
}

Master price lists

Each type 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 type.

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 type 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 rentable_type|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 can 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 Booking and a Customer. Where originally you would be able to specify customer and booking data on the Reservation itself, you will now need to split these. The attribute names are mostly the same though. Please see the Booking & 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 booking|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 Categories to RentableTypes

Date: October 31, 2022 - Affected versions: 0.45.0

Within BE, accommodations (and other rentals) are referred to as RentableIdentities, and specific setups of accomodations are referred to as Rentables. Rental segments are referred to as RentableSegments. On the other hand, the type of an accommodation was referred to as Category. For consistentency, we decided it would be better to rename Categories to RentableTypes.

Henceforth, as of App API release 0.45.0, Categories in BE will be internally and externally available as RentableTypes. If you are using RentableTypes in the App API, you should rename all occurrences of Categories to RentableTypes after this release, including any Category-related endpoints. There are also some other Category-related attributes and query parameters that need to be renamed. Please see the Release Notes for a full list of changes. To allow for a smooth transition, support for Categories (and derived attributes and query parameters) will remain in place until the date of removal as mentioned in the Release Notes has passed.

NOTE The TourOperator API will remain unaffected.

Renaming AccommodationTypes to RentableSegments

Date: July 5, 2022 - Affected versions: 0.33.0

Within BE, accommodations (and other rentals) are referred to as RentableIdentities, and specific setups of accomodations are referred to as Rentables. On the other hand, the rental segment an accommodation belongs to was referred to as AccommodationType. For consistentency, we decided it would be better to rename AccommodationTypes to RentableSegments.

Henceforth, as of App API release 0.33.0, AccommodationTypes in BE will be internally and externally available as RentableSegments. If you are using AccommodationTypes in the App API, you should rename all occurrences of AccommodationTypes to RentableSegments after this release. There are also some other AccommodationType-related attributes that need to be renamed. Please see the Release Notes for a full list of changes. To allow for a smooth transition, support for AccommodationTypes (and derived attributes) will remain in place until the date of removal as mentioned in the Release Notes has passed.

NOTE The TourOperator API will remain unaffected.

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.74.0] - 2023-05-00

Added

[0.73.0] - 2023-05-03

Added

[0.72.0] - 2023-04-26

Added

[0.71.0] - 2023-04-20

Added

[0.70.0] - 2022-04-20

Added

[0.69.0] - 2023-04-17

Added

[0.68.0] - 2023-04-12

Added

[0.67.0] - 2023-04-03

Added

[0.66.0] - 2023-03-29

Added

[0.65.0] - 2023-03-17

Added

[0.64.0] - 2023-03-13

Added

Deprecated

[0.63.0] - 2023-03-08

Added

[0.62.0] - 2023-03-06

Added

[0.61.0] - 2023-03-02

Added

[0.60.0] - 2023-02-23

Added

[0.59.0] - 2023-02-21

Added

[0.58.0] - 2023-02-21

Added

[0.57.0] - 2023-02-03

Added

[0.56.0] - 2023-01-16

Added

[0.55.0] - 2022-12-22

Added

[0.54.0] - 2022-12-19

Added

[0.53.0] - 2022-12-15

Added

[0.52.0] - 2022-12-14

Added

[0.51.0] - 2022-12-07

Added

[0.50.0] - 2022-11-21

Added

[0.49.0] - 2022-11-09

Added

[0.49.0] - 2022-11-09

Added

[0.48.0] - 2022-11-03

Added

[0.47.0] - 2022-11-01

Deprecated

Date of removal for abovementioned deprecations: 01-11-2023.

[0.46.0] - 2022-11-01

Added

[0.45.0] - 2022-10-31

Deprecated

Date of removal for abovementioned deprecations: 31-10-2023.

[0.44.0] - 2022-10-11

Added

[0.43.0] - 2022-10-04

Added

[0.42.0] - 2022-09-27

[0.41.0] - 2022-09-21

Added

[0.40.0] - 2022-09-14

Added

[0.39.0] - 2022-09-13

Fixed

[0.38.0] - 2022-09-12

Added

[0.37.0] - 2022-08-08

Added

[0.36.0] - 2022-07-28

Added

[0.35.0] - 2022-07-20

Added

[0.34.0] - 2022-07-06

Added

[0.33.0] - 2022-07-05

Deprecated

Date of removal for abovementioned deprecations: 31-10-2023.

[0.32.0] - 2022-07-01

Added

[0.31.0] - 2022-06-15

Added

[0.30.0] - 2022-06-09

Added

[0.29.0] - 2022-05-31

Added

[0.28.0] - 2022-05-30

Added

[0.27.0] - 2022-05-23

Added

[0.26.0] - 2022-05-05

Added

[0.25.0] - 2022-04-29

Added

[0.24.0] - 2022-04-06

Added

[0.23.0] - 2022-04-05

Deprecated

Fixed

[0.22.0] - 2022-03-30

Added

[0.21.0] - 2022-03-22

Added

Deprecated

[0.20.0] - 2022-03-11

Added

[0.19.0] - 2022-02-11

Added

[0.18.0] - 2022-01-26

Added

[0.17.0] - 2022-01-25

Added

[0.16.0] - 2022-01-21

Added

[0.15.0] - 2022-01-19

Added

[0.14.0] - 2022-01-18

Added

[0.13.0] - 2022-01-05

Added

[0.12.0] - 2021-12-16

[0.11.0] - 2021-12-07

[0.10.0] - 2021-11-29

[0.9.0] - 2021-11-22

Added

[0.8.0] - 2021-11-16

Added

[0.7.0] - 2021-10-19

Added

[0.6.0] - 2021-10-13

Added

[0.5.0] - 2021-10-11

Added

Fixed

[0.4.0] - 2021-09-08

Added

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

Fixed

[0.0.2] - 2021-01-05

Added

[0.0.1] - 2020-07-13

Added