How NOT to design RESTful APIs by Rob Konarskis

(28 views)

I was helping out a friend who needed to integrate housing availability from a property management system with his client’s website. Luckily, the property management system had an API. Unfortunately, everything about it was wrong.

The goal of this story is not to give a bad advertisement for a used system, but to share how things should NOT be developed, as well as learn the right approaches when it comes to designing APIs.

Assignment

My friend’s client is using Beds24 system to manage their property listings and keep availability in sync across various booking systems (Booking, AirBnB, etc.). They are building a website and would like the search mechanism to only show properties that are available for the selected dates and number of guests. Sounds like a trivial task, since Beds24 provides an API for integrations with other systems. Unfortunately, the developer has managed to make a lot of mistakes when designing it. Let’s walk through these mistakes, see what exactly went wrong and how it should have been done.

Sin 1: Request body format

Since were are only interested in getting availabilities for properties of the client, only /getAvailabilities call was of interest to us. Even though this is a call to get availabilities, in fact this is a POST request since the author decided to accept filters as a JSON body. Below is a list of all possible parameters:

{
    "checkIn": "20151001",
    "lastNight": "20151002",
    "checkOut": "20151003",
    "roomId": "12345",
    "propId": "1234",
    "ownerId": "123",
    "numAdult": "2",
    "numChild": "0",
    "offerId": "1",
    "voucherCode": "",
    "referer": "",
    "agent": "",
    "ignoreAvail": false,
    "propIds": [
        1235,
        1236
    ],
    "roomIds": [
        12347,
        12348,
        12349
    ]
}

Let me go through the JSON object and explain what is wrong with its parameters.

  1. Dates checkIn , lastNight and checkOut are formatted using YYYYMMDD scheme. There is absolutely no reason not to use the standard ISO 8601 format (YYYY-MM-DD) when encoding dates as strings, since this is a widely adopted standard understood and expected by most developers and JSON parsers. In addition to that, the lastNight field seems to be redundant because checkOut is provided and it would always be one day ahead of the last night. Please, always use standard date encoding formats and do not ask the user of the API to supply redundant data.
  2. All Ids, as well as numAdult and numChild fields are numeric, but are encoded as strings. There seems to be no reason to encode them as strings in this particular situation.
  3. We have the following field pairs: roomId and roomIds , as well as propIdand propIds. Not only it is redundant to have the roomId and propIdproperties since roomIds and propIds can be used to pass Ids, but there is also a type issue here. Notice that roomId expects a string, while roomIdsrequires a numeric array. This can cause confusion, parsing issues, and would mean that the back-end itself performs some operations on strings and some on numbers, even though we are talking about the same data.

Please, do not confuse developers with such silly mistakes and try to use standard formatting as well as pay attention to redundancy and field types. Do not just wrap everything in a string.

Sin 2: Response body format

As explained in the previous part about request body format, we are only focusing on the /getAvailabilities call. This time let us have a look at the response body format and see what is so wrong with it. Keep in mind that we are interested in getting the Ids of properties that are available for the given dates and a number of guests. Below are the corresponding request and response bodies:

Request:

{
    "checkIn": "20190501",
    "checkOut": "20190503",
    "ownerId": "25748",
    "numAdult": "2",
    "numChild": "0"
}

Response:

{
    "10328": {
        "roomId": "10328",
        "propId": "4478",
        "roomsavail": "0"
    },
    "13219": {
        "roomId": "13219",
        "propId": "5729",
        "roomsavail": "0"
    },
    "14900": {
        "roomId": "14900",
        "propId": "6779",
        "roomsavail": 1
    },
    "checkIn": "20190501",
    "lastNight": "20190502",
    "checkOut": "20190503",
    "ownerId": 25748,
    "numAdult": 2
}
  1. ownerId and numAdult suddenly become numbers in the response as opposed to them being strings in the request body.
  2. There is no list of properties. Instead, properties are top-level objects with roomId as keys. This means that in order to get a list of available properties, we would need to iterate over all objects, check if certain parameters like roomsavail are present, discard others like checkIn , lastNight , etc. to get a list of properties. Then, we would need to check the value of roomsavail property and if it is more than 0, treat the property as available. But wait a second, have a closer look here: "roomsavail": “0” and here "roomsavail": 1. See the pattern? If there are no available rooms, the value is a string, while if there is a room free, it turns into a number! This would cause a lot of problems in languages enforcing type safety such as Java, since the same property should not be of different types. Please, use proper JSON lists for displaying a collection of data instead of coming up with weird key-value constructs as the one above, and make sure that you don’t change field types from object to object. A correctly formatted response would look like below (note that this format would allow to also get information about rooms without duplicating anything):
{
    "properties": [
        {
            "id": 4478,
            "rooms": [
                {
                    "id": 12328,
                    "available": false
                }
            ]
        },
        {
            "id": 5729,
            "rooms": [
                {
                    "id": 13219,
                    "available": false
                }
            ]
        },
        {
            "id": 6779,
            "rooms": [
                {
                    "id": 14900,
                    "available": true
                }
            ]
        }
    ],
    "checkIn": "2019-05-01",
    "lastNight": "2019-05-02",
    "checkOut": "2019-05-03",
    "ownerId": 25748,
    "numAdult": 2
}

Sin 3: Error handling

Error handling in this API is implemented in the following way: all requests return a response code of 200, even in case of an error. This means that there is no way to distinguish between a successful and unsuccessful response other than parsing the body and checking for presence of error or errorCodefields. The API only has 6 error codes, as seen below:

Error codes of Beds24 API

Please, consider not using this approach of returning a response code 200(success) when something went wrong, unless it is the standard way to go in your API framework. It is a good practice to make use of standard HTTP error codes, which are recognized by most clients and developers. For example, error code 1009 in the screenshot above should be replaced with a 401 (Unauthorized) HTTP code. It makes life easier if the API client could know upfront whether to parse the body or not, and how to parse it (as a data object or error object). In cases where errors are application-specific, returning a 400 (Bad request) or 500 (server error) with an appropriate error message in the response body is preferred.

Whichever error handling strategy is chosen for a given API, just make sure it is consistent and according to the widely adopted HTTP standards. This would make our lives easier.

Sin 4: “Guidelines”

Below are the API use “guidelines” from the documentation:

Please observe the following guidelines when using the API

1. Calls should be designed to send and receive only the minimum required data.

2. Only one API call at a time is allowed, You must wait for the first call to complete before starting the next API call.

3. Multiple calls should be spaced with a few seconds delay between each call.

4. API calls should be used sparingly and kept to the minimum required for reasonable business usage

5. Excessive usage within a 5 minute period will cause your account to be blocked without warning.

6. We reserve the right to disable any access we consider to be making excessive use of the API functions at our complete discretion and without warning.

While points 1, 4 make sense, I can’t agree that others do. Let me explain why.

2. In case you are building a REST API, it is supposed to be stateless, there should be no state present at any point in time. This is one of the reasons why REST is useful in cloud applications. Stateless components can be freely redeployed if something fails, and they can scale according to load changes. Please make sure that when designing a RESTful API, it is really stateless and developers do not need to care about such things as “one request at a time”.

3. This is an ambiguous and very weird guideline. Unfortunately, I could not figure out a reason the author has created this “guideline”, but it gives a feeling that there is some processing done outside of the request itself, therefore making calls right after each other could put the system in some incorrect state. Also, the fact that the author says “a few seconds” provides no specific information about the actual time between the requests.

5 & 6. Again, it is not explained what is “excessive” in this case. Is it 10 requests per second or 1? Also, certain websites would have a huge amount of traffic, and blocking them from using the API just because of that without any warning might make developers move away from such a system. Please, be specific when making such guidelines and think about the users when coming up with rules like these.

Sin 5: Documentation

This is how the API documentation looks like:

Beds24 API documentation look&feel

 

The only problems here are readability and overall feel. The same documentation could have looked way better if the author would have used markdown instead of custom non-styled HTML. For the sake of this post, I created a better version with the help of Dilliger in under 2 minutes. Here is the result:

Styled version of the documentation

 

Please, use tools to create API documentation. For simple docs, a markdown file like the one above can be enough, while for larger and more feature-rich documentation, tools like Swagger or Apiary are better to be used.

Here is a link to the Beds24 API docs for those who want to have a look at it themselves.

Sin 6: Security

The API documentation states the following about all of the endpoints:

To use these functions, API access must be allowed in the menu SETTINGS >> ACCOUNT >> ACCOUNT ACCESS.

However, in reality anyone can make a request to fetch data without passing any credentials for certain calls, like get availability of a certain property. This is stated in a different part of the documentation:

Most JSON methods require an API key to access an account. The API code can be set at the menu SETTINGS >> ACCOUNT >> ACCOUNT ACCESS.

In addition to the miscommunication about authentication above, the API key is actually something that the user needs to create themselves (actually type the key manually, no autogeneration whatsoever) and it should be between 16 and 64 characters long. Allowing the user to create the key themselves could potentially lead to very insecure keys that can be easily guessed, or certain formatting issues since any string can be entered in that field in account settings. In worst cases, it could also become an open door for SQL injection or other types of attacks. Please, never let the user create API keys. Always provide them with immutable autogenerated keys instead, with an ability to invalidate it when needed.

For those requests that are actually authenticated, we have a different problem: authentication token has to be sent as part of the request body as seen below (from documentation):

Beds24 API authentication example

 

Having authentication token inside request body means that the server would need to first parse the request body, extract the key, perform authentication, and then decide what to do with the request: execute it or not. In case of a successful authentication, there is no overhead involved as the body would have been parsed anyway. In case authentication failed, the server did all the work described above just to extract the token, spending precious processing time. A better approach instead would be to send an authentication token as a request header, using Bearer authentication scheme or similar. This way, the server would only need to parse the request body in case of a successful authentication. Another reason to use a standard authentication scheme like a Bearer token is simply because most developers are familiar with it.

Sin 7: Performance

Last but not least, it would take on average a little over 1 second for a request to complete. With modern applications, such delays may not be acceptable. Therefore, take performance into account when designing APIs.


Despite all of the issues with the API explained above, it does the job. However, it takes several times longer for developers to understand and implement it, as well as forces to write more complex solutions to trivial problems. Therefore, please think about developers implementing your API before releasing it. Make sure the documentation is complete, clear and well-formatted. Check if your resource names follow conventions, that your data is well structured, easy to understand and use. Also, pay attention to security and performance, do not forget to implement error handling correctly. If all of the above is taken into account when designing an API, then there would be no need for weird “guidelines”, like in the example earlier.

As already mentioned, the goal of this post was not to make you never use Beds24 or any similar system because their API is not implemented correctly. The target was to increase the quality of software products by sharing a poor example and explaining how it could have been done better. Hopefully, this post would make someone pay more attention to software development best practices and make software systems better. Till next time!


???? Read this story later in Journal.

Originally posted: https://blog.usejournal.com/how-not-to-design-restful-apis-fb4892d9057a

February 18, 2019
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
© HAKIN9 MEDIA SP. Z O.O. SP. K. 2023