How to Write a Self-Documenting API

Since the launch of Heyzap, we have been collecting a ton of interesting social data around mobile games on Android and iPhone. We think there is a lot of potential in using this data to discover interesting things around games and we wanted to expose that to the world through our API. Even though our mobile apps connect to our internal API, making a new API was not as easy as exposing the internal mobile API, because:

So, when our internal hack day was approaching, I thought it was a perfect idea to try to launch an API and invest some time in learning how best to build it.

You can check out the first version API documentation and go to the root of the API to start exploring.

Here is a cool usage of the API that Chris did for our hack day: Live Check-in map

The Inspiration

Heyzap has previously had several internally and externally facing APIs so I was aware of the mistakes I had made. Here are some that were foremost in my mind:

Before starting the Heyzap API, I spent several hours researching blog posts and look at how other APIs were written. Here are the resources that I drew most inspiration from

Considerations for Self-Documenting APIs

There are several aspects to making an API self-documenting. Here are things we considered

These are discussed more in depth below.

URLs

A lot of thought was put in to the URL structure, and invested a reasonable amount of time in to the routes file before writing a line of code. Here is what we ended up with:

  map.namespace :api do |api|
    api.namespace "v1" do |version|
      # root
      version.connect "", :controller => "base", :action => :index

      version.resources :games, :only => [:index, :show], :member => {:players => :get}, :controller => "games" do |games|
        games.resources :ios, :only => [:index, :show], :collection => {:search => :get, :trending => :get, :popular => :get}
        games.resources :android, :only => [:index, :show], :collection => {:search => :get, :trending => :get, :popular => :get}

        games.resources :activity, :only => [:index], :collection => {:checkins => :get, :questions => :get, :tips => :get}, :controller => "games/activity"
      end

      version.connect "users/:id", :controller => "users", :action => :show
      version.resources :users, :only => [:index, :show], :collection => {"search" => :get},  :member => {:badges => :get, :followers => :get, :following => :get,
                                                           :games => :get, :boss_of => :get}, :controller => "users" do |users|
        users.resources :activity, :only => [:index], :collection => {:checkins => :get, :questions => :get, :tips => :get}, :controller => "users/activity"
      end
      version.resources :activity, :only => [:index, :show], :member => {:checkins => :get, :questions => :get, :tips => :get, :badges => :get, :bossings => :get}, :controller => "activity"
    end
  end

At least 80% of this was written before a line of code was written.

The Resource Roots and Cross Linking

Normally, when I’m designing an API, I don’t put anything in the root URL or in places where I don’t expect to give data. But to make it sufficiently self-document, this API returns URL to relevant API end points wherever possible. This is the root response:

{
    activity_api_url: "http://www.heyzap.com/api/v1/activity",
    games_api_url: "http://www.heyzap.com/api/v1/games",
    users_api_url: "http://www.heyzap.com/api/v1/users"
}

And if you happen to click on the games_api_url you get:

{
    platforms_allowed: {
        android_url: "http://www.heyzap.com/api/v1/games/android",
        ios_url: "http://www.heyzap.com/api/v1/games/ios"
    }
}

In this way, without reading any documentation, you can explore most of the API. You of course need a JSON viewer plugin to do this.

Additionally, whenever a different object is references in the API there is a API URL to it so you can explore in that way, too.

The Short and Long Form Objects

To keep the API readable, all end points that return an array of objects always returns the objects in a short form. It includes a URL always labeled “url” to the full object resource.

This is a practice that Github follows and I really liked how it gave the information necessary at the array level and gave URLs to the full information. Twitter tended to just give an array of raw object IDs which was less self-documenting.

Use of Request and Response Headers

There are pros and cons to the use of request/response headers to convey a lot of information. Strictly speaking one could do all of the following with appropriate headers

Although REST gives this ability, I think at times it can be not very readable and self-documenting. The two that I did do through headers were using status codes and rate limiting, but in both cases the response body also repeated the information.

Some Compromises

To get this done in one day while still being scalable, I only exposed data that has been hit at scale on our mobile apps. This meant that, for example, Activity streams come in iOS or Android but you can’t get a combined activity feed currently. Obviously the alternatives could have been done but it wasn’t within the scope to make and test new ways of accessing the data.

I am not fully happy with the way we handle pagination. Our mobile app uses infinite scrolling everywhere so it only really needs next_page_url. I would have preferred to encourage all people to skip through real pages with readable page numbers.

Initially the API is read only. Setting up write capabilities end-to-end was again not within the scope of a day.

Conclusion

The API is just launching today so I am sure there will be things that we learn in real world usage that we didn’t anticipate. We will probably move over our mobile apps to use this API or something very similar as soon as we add write capabilities.

To start using the API read the API documentation and go to the root of the API to start exploring.

Heyzap is Hiring →