Something that all API’s need to consider (no matter how simple they are!) is how they will handle adding and upgrading functionality once someone gets a hold of it.
Sure, you could just wing it and rely on your test suite to give you some confidence in just rolling it out…but what about those weird and wonderful ways others may be leveraging your API? How will they cope with your update?
One approach that is often taken is to include the version number within your
endpoint routes - an example would be https://api.example.com/v1/...
. While
this definitely works, it does create some issues with cache negotiation and
clients holding onto outdated links.
Cache negotiation pitfalls
A large portion of caching identifiers are based on URI unless configured
otherwise which means that https://api.example.com/v1/users
will have a
different cache key to https://api.example.com/v2/users
. While this is
desired behaviour you are potentially paying for the same cache data to be
served even though it could be identical and when you pay according to the
traffic offset by this service, it can quickly grow out of control. Should you
be lucky enough to have a self hosted service you have the additional overhead
of the same cached data being stored X times where X is the number of API
iterations are accessible.
Outdated links
People bookmarking and holding onto outdated links is a problem that is as old
as the internet itself and it probably won’t be going away any time soon. So
help yourself out by not creating more issues when
https://api.example.com/v1/...
no longer works because version 1 of your API
has been deprecated and the newly released version is now located at
https://api.example.com/v2/...
instead.
Now that you know the issues, what is the solution? You guessed it, the HTTP ‘Accept’ header.
The Breakdown™
Using the HTTP Accept header is relatively straight forward and looks a little something like this:
Accept: application/vnd.{app_name}.{version}+{response_type}
- vnd (as per RFC 4288) is the method of registering as a “vendor” and that you plan to interchange using this context.
- app_name is a field that identifies your application or product.
- version will contain the version number you wish to use to distinguish between iterations or releases. Common values are ‘v1’, ‘verison2’ or even ‘beta’.
- response_type covers what structure you plan to send the response as. E.g. ‘json’, ‘xml’, etc.
Using the HTTP Accept header
Now that you have a fair idea on why it benefits you to use the HTTP Accept header and what it looks like in the wild, it’s time to write some code to use it.
In this example an endpoint to return a user’s record has been setup. Unknown to the general public, a new field has been added to the API however while we are still within the testing phase we only want users who are a part of the beta program to see it.
get '/user/:id' do
# .. Some magic to get the user based on ID. I will just assume this is done
# and you have a User object under a local variable called 'user'.
response_data = {
name: user.name,
email: user.email
}
if request.headers["Accept"].include? "beta"
response_data[:plan] = user.subscription_type
end
response_data.to_json
end
To see the results all we need to do now is send a request to the endpoint specifying the correct HTTP ‘Accept’ header.
$ curl https://api.example.com/user/1
{
"name":"Jacob",
"email":"[email protected]"
}
Now, let’s specify the ‘beta’ HTTP Accept header so that we can take advantage of that new field.
$ curl -H "Accept: application/vnd.example_app.beta+json" \
https://api.example.com/user/1
{
"name":"Jacob",
"email":"[email protected]",
"plan":"medium"
}