Talking to hostile APIs
Tags: api, ruby, programming, webdev
Published: 2023-09-13 18:43:03

APIs allow us to communicate with third party software in order to use their services and data. In today's software, it's almost impossible not needing to use one, one way or another. For example, an MVP [(What is an MVP?)](https://sinaptia.dev/posts/what-is-an-mvp) for an e-commerce might want to process payments using Stripe. Or an application in the hospitality industry might need to pull (or push) data from a Property Management System.

So, what is a hostile API and how to deal with them?

A hostile API is an API that gets in your way, that fights you when you're trying to use it. That seems to be against you using it.

In an MVP we're currently building, we need to use this third party application (which I won't mention for obvious reasons: not cool). For the scope of this MVP, we're using this API mostly to pull and push information through, but we don't need to store anything yet. This third party application, in the hospitality industry, manages listings and reservations to those listings. And those are managed by owners. So, in [Ruby on Rails's](https://rubyonrails.org/) terms we can say that:

an owner has many listings (and a listing belongs to an owner)
a listing has many reservations (and a reservation belongs to listing)
an owner has many reservations through listings.
Keep this in mind for the next section.

How is this API being hostile then? The first sign of hostility is the authentication. They would give you a username and a password and you would use them to obtain a token, valid for 24 hours. Then you would authenticate to the rest of the API endpoints with that token. So far so good, normal procedure. The problem is that they would only allow you to create 5 tokens a day. In my opinion, this is completely silly for a number of reasons:

while it's a good practice to cache access tokens, it's uncomfortable to try the API in the console for example.
once consumed, either because you didn't cache the token by mistake, because you were testing in the console, or whatever other reason, you could create a new set of credentials (a new application in their terms) and get your token quota back. Why give me only 5 tokens then if I can create and discard applications? Or accumulate them if I'm careless? Why can't I generate as many access tokens as I want? Or at least have more generous limits?
This is not the end of the world, one can easily work with this API by having a token method that would return the token from the cache, like this (the example is using [httparty](https://github.com/jnunemaker/httparty)):

```ruby

class HostileApi
  include HTTParty

  base_uri "hostileapi.com/api/v1" # redacted for obvious reasons!

  headers Accept: "application/json", Authorization: -> { "Bearer #{token}" }

  def owner(id)
    return if id.blank?

    response = self.class.get "/owners/#{id}"

    if response.success?
      response
    end
  end

  # more methods here

  private_class_method

  def self.token
    Rails.cache.fetch("hostile-token", expires_in: 24.hours) do
      response = HTTParty.post "https://hostileapi.com/oauth2/token", body: {client_id: ENV["HOSTILE_CLIENT_ID"], client_secret: ENV["HOSTILE_CLIENT_SECRET"]}

      response["access_token"]
    end
  end
end
So... it landed a few punches at us. But we fought back.

```

Remember that an owner has many listings and a listing has many reservations, and so an owner has many reservations through listings? What if part of your requirements are showing all the reservations for an owner? A good API would give you the option of filtering reservations by owner. With a hostile API... not so fast! To do so with a hostile API you would first need to fetch all the listings (that are paginated to add more complexity) and then all the reservations for all the listings, which can become extremely slow. Or search a specific listing and then fetch all its reservations. The problem here is that you need to design your MVP around the hostile API's poor design choices.

Again, it's not the end of the world, but adds unnecessary complexity and can significantly affect performance if the solution is not designed having this in mind.

Last but not least, a third argument against hostile APIs: documentation. This hostile API as well as other hostile APIs lacks of extensive, well written, documentation. Writing good documentation is challenging and a topic on its own, but the developer experience is incredibly better when working with an API like Stripe's vs one that only gives you a 2 pages pdf with some incomplete, ill formatted json responses.

There's not much we can do as users of such APIs but to defend ourselves: decouple our code from the hostility using well-isolated client libraries, hide the tricky parts and the problematic interactions behind a sane API so the rest of the team don't accidentally spend all the tokens.

And, on the other hand, if your app provides a public API, avoid impractical limitations or constraints. You want people using your API, don't punish them for doing so! Be open minded and always put yourself in your users' shoes, and try very hard to pick the simpler alternatives.

So now you know, a hostile API will behave in a way you need to fight against it to use it. And please, if you ever provide an open API, don't be hostile!