The Booking Experts API is organised around REST and it follows the JSON API specification. The API has predictable, resource-oriented URLs, and uses HTTP response codes to indicate API errors.
Documentation is standardized by using the Open API 3 (OAS3) specification, this allows you to inspect the API using other clients like for example Swagger UI and Postman. The specification is hosted here: https://api.bookingexperts.nl/v3/oas3.json
Responses
The Booking Experts API will always respond with a HTTP status code. The API can return the following codes:
Code | Semantic | Meaning |
---|---|---|
200 | OK | Request was successful |
400 | Bad Request | Parameters for the request are missing or malformed. Body contains the errors. |
401 | Unauthorized | Your API key is wrong |
403 | Forbidden | IP is blacklisted for API usage, see Throttling information |
404 | Not Found | Entity not found |
422 | Unprocessable entity | Saving the entity in the database failed due to validation errors. Body contains the errors. |
429 | Too Many Requests | You're requesting too many kittens! Slow down! |
5XX | Server Errors | Something went wrong on Booking Experts's end. We are probably already busy solving the issue. It's your responsibility to retry the request at a later point. |
//Might produce the following output
{
"errors": [
{
"status": 401,
"code": "RESOURCE_NOT_FOUND",
"title": "Unauthorized error",
"detail": "Please make sure to set the Authorization HTTP header"
}
]
}
Error Codes
Error codes are custom to give you more information. The list below contains the most common errors that can occur.
APPLICATION_HALTED
: Your app is halted and you cannot perform any requests anymoreCONFLICT
: A conflict error occurred during mutationFORBIDDEN_INCLUDE
: An include was specified for which you don't have any permissionFORBIDDEN_INPUT
: Invalid input was specified. See the error message for more detailsINTERNAL_SERVER_ERROR
: An error occurred on our side. We are notified automatically of this.INVALID_ATTRIBUTE
: The attribute value specified is invalidINVALID_FIELDSET
: The fieldset specified is incorrectINVALID_FILTER
: The filter specified is incorrectINVALID_INCLUDE
: The include specified is incorrectINVALID_PAGINATION
: Incorrect pagination parametersINVALID_RELATIONSHIP
: The relationship specified is invalidINVALID_SORTING
: The sorting parameters specified is incorrectINVALID_TOKEN
: The token is invalid er revokedNO_ADMINISTRATION_ACCESS
: You don't have access to the given administrationNO_CHANNEL_ACCESS
: You don't have access to the given channelNO_VALID_SCOPE
: No valid scope was found for the called endpointRATE_LIMITED
: Your request was rate limitedRESOURCE_NOT_FOUND
: The requested resource could not be foundSUBSCRIPTION_CANCELLED
: The current subscription subscription is cancelledUNKNOWN_ATTRIBUTE
: An unknown attribute was specifiedUNKNOWN_FORMAT
: The response format requested is unknownUNKNOWN_RELATIONSHIP
: An unknown relationship was specifiedUNKNOWN_VERSION
: The resource could not be serialized for the given serializer versionUNSUPPORTED_MEDIA_TYPE
: The media type specified is not supported
Accepted Language
You can always pass an Accept-Language header containing a comma separated list of locales. This will limit the result of 'localized' attributes to the locales specified.
x-be-env header
When your application receives a request from Booking Experts, for example when a webhook or command is called, the x-be-env
header is passed. Usually, the value of this header will be 'production', denoting that the request originated from our production environment. For testing purposes however, it might be possible that your app will receive requests from a different environment, for example 'staging'. You can check this header if you want to handle these requests differently.
x-be-signature header
When your application receives a request from Booking Experts, for example when a webhook or command is called, the x-be-signature
header is passed to allow you to verify that the request was sent by our systems. It uses a HMAC hexdigest to compute the hash based on your Client Secret.
To verify a request, your code should look something like this:
- No matter which implementation you use, the hash signature starts with sha256=, using the key of your Client Secret and your payload body.
- Using a plain == operator is not advised. A method like secure_compare performs a "constant time" string comparison, which renders it safe from certain timing attacks against regular equality operators.
- Note that this approach has been taken from the Github webhook docs. There might be more examples on the web for this solution that can be used as inspiration.
def verify_signature(payload_body)
signature = 'sha256=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), ENV['OAUTH_CLIENT_SECRET'], payload_body)
return halt 500, "Signatures didn't match!" unless Rack::Utils.secure_compare(signature, request.env['HTTP_X_BE_SIGNATURE'])
end
Rate limiting
Usage of the Booking Experts API is virtually unlimited. However, to prevent fraud and abuse, requests to the API are rate limited. By default a leaky bucket limiter with a bucket size of 100 that drains in 180 seconds (i.e., a 'drain rate' of 1.8 per second) is used to implement our rate limiting. This means that bursts of up to 100 calls are allowed, but further requests will then be limited to one call per 1.8 seconds because bucket capacity has been reached. After 3 minutes, the bucket is 'empty' again and a new burst of 100 additional calls is possible. Nonetheless, it's best to space out requests, to prevent reaching bucket capacity too quickly and running into the rate limit.
While within the limit, each response contains a X-RateLimit-Limit
and a X-RateLimit-Remaining
header containing the set limit & the remaining allowance in the window. If you exceed the limit (i.e., the bucket capacity has been reached), the API will respond with a 429 Too many requests
response. This response contains a Retry-After
header containing the time (in seconds) after which a new call is allowed.
If your use case requires more lenient rate limits, please contact us at [email protected] to request a higher limit. We will also request a explanation as to why your limit needs to be increased.
More information about the working of leaky bucket limiters can be found at Wikipedia.
Pagination
All collection responses include pagination. In the response body you will find a links
node that contains links to first
, self
, next
, prev
, last
pages. Most responses have 30 records per page.
{
"links": {
"self": "https://api.bookingexperts.nl/v3/administrations/1/reservations?page%5Bnumber%5D=2",
"first": "https://api.bookingexperts.nl/v3/administrations/1/reservations?page%5Bnumber%5D=1",
"last": "https://api.bookingexperts.nl/v3/administrations/1/reservations?page%5Bnumber%5D=14",
"prev": "https://api.bookingexperts.nl/v3/administrations/1/reservations?page%5Bnumber%5D=1",
"next": "https://api.bookingexperts.nl/v3/administrations/1/reservations?page%5Bnumber%5D=3"
}
"data": [...]
}
Sparse field sets
By default every request returns a quite complete set of fields (attributes and relationships). You can limit or expand this default set however. Per record type you can specify which fields to include.
Will return only the name and description fields of every administration. Note that this will also omit defined relationships of the resource.
Includes
Includes are a standard part of the JSON:API specification. Each relationship on a resource can be included. Which relationships a resource has can be determined by looking at its Schema.
As an example: the Reservation resource defines a relationship called extra_order_items
. This means that you can add extra_order_items
to the includes list in the query string. In turn, the ExtraOrderItem has a relationship with an extra
, so you could in that case also include the metadata of the associated extra by specifying extra_order_items.extra
in your includes list. When you only need the ID of a relationship, it is not necessary to include a resource, as the ID is defined within the relationship itself.
Filters
The following attribute filters are available on GET API calls:
filter[attr]=term
: attr = 'term'filter[attr]=~term
: attr ILIKE '%term%'filter[attr]=term,term
: attr IN ('term', 'term')filter[attr]=a..b
: attr1 BETWEEN 'a' AND 'b'
All expressions can be inverted by prefixing a !
, this holds for the entire expression.
OR-filtering
An OR filter can be created by separating attribute names in a filter with a pipe:
filter[attr1|attr2]=term
: attr1 = 'term' OR attr2 = 'term'filter[attr1|attr2]=~term
: attr1 ILIKE '%term%' OR attr2 ILIKE '%term%'filter[attr1|attr2]=term,term
: attr1 IN ('term', 'term') OR attr2 IN ('term', 'term')filter[attr1|attr2]=a..b
: attr1 BETWEEN 'a' AND 'b' OR attr2 BETWEEN 'a' AND 'b'
Security
To be able to fetch resources, either directly or through including related resources, you need the appropriate permissions. Permissions can be defined in you App's settings and they translate into OAuth2 scopes that the user needs to explicitly approve. When you are missing permissions for the call you are executing, an error will be returned like the one on the right.
Note: When you update your App's permissions, these will apply directly when using API keys. When using OAuth2 however, subscribers will need to grant permission again in order for the new permissions to be applied.
Example permissions (scopes):
availability|read
reservation|read
category|read
payment|write
There are also some channel specific permissions. This functionality is meant for tour operators who don't need to access all reservations, but only reservations and related resources that have been created through their own channel:
channel::reservation|read
channel::order|read
channel::customer|read
{
"errors": [{
"status": "403",
"code": "NO_VALID_SCOPE",
"title": "Forbidden",
"detail": "The scopes for your application do not grant you permission to perform this action. One of the following scopes is required: payment|write."
}]
}