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:
- The app and API requirements have been evolving iteratively. This has meant that it would not be intuitive if it were exposed and almost impossible to document.
- Some parts of the app hit API end points that would not scale if hit on mass.
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:
- APIs are hit in ways that you don’t expect and so every single end point needs to be scalable.
- It is easy to keep adding new parameters to API responses, which crowds the API.
- Getting the URLs right requires a lot of thought.
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
- There are a lot of opinionated blog posts on REST APIs. This is where I first got the idea for making the API as self-documenting as possible.
- Github’s API v3: I especially liked the way Github did short form and long form objects and rate limiting
- Twitter’s REST API: Being a social API, it had a lot of concepts we have in our API. I liked the way they managed to put all the docs on one page. We also borrowed some of the pagination paradigms they use.
Considerations for Self-Documenting APIs
There are several aspects to making an API self-documenting. Here are things we considered
- URLs and REST resources have to be intuitive
- The API should cross-link with URL between different resources. This is used very aggressively in the API
- I feel that JSON is better for self-documenting than XML and that the world is moving to JSON so the API is JSON only
- The objects returned have to be self-documenting in terms of how the keys are labeled
- One can’t hide too much functionally in the request or response Headers. This is at times a contentious issue in REST API designs and I explore it further below
- The different types of resources should use the same keys as much as possible. In our cases games and users had a reasonable amount of overlap
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
- Version: Through request ACCEPT headers
- Requesting Response Format: Through request ACCEPT headers
- Rate limiting: Through response headers
- Pagination: Through response headers
- Resources not found: Through response status codes
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.

