Versioning APIs Internally

Versioning APIs Internally

June 02, 2015

There's a fair amount that's been written on how to version APIs, but what nobody talks about is what goes on behind the scenes--how versioning is implemented at a code level.

One reason behind this is that the implementation is often a mess, a nestled spaghetti of conditional statements littering your codebase that usually looks something like this:

if params[:version] == 1
  show_something
else
  show_something_else
end

At Clearbit we've been considering how we're going to approach this problem and plan for the future when presumably we'll have a lot of different versions. I think the approach we've come up with strikes a good balance between being simple and being DRY. While this post is Ruby & Sinatra specific, the general conventions should apply to any stack.

Date based versioning

Whenever you first make an API call to one of our APIs we save today's date to your account and use that as a version reference from then on. You can override the version on a per request basis (with a custom header) but the client's default version is whatever is in the database. When a client is ready to upgrade, say to gain access to a new data attribute, they can do so in their account's settings.

Version based routes

If we're changing something at a route based level, say altering the default behavior or data returned, we create a new route and mount them in reverse chronological order.

We then filter routes by the version they represent using Sinatra conditions which means they'll only get executed if the version matches [1].

# people_2015_05_11.rb
get '/v1/combined/email/:email', version: '2015-05-11' do
  # ...
end

# people_2015_04_30.rb
get '/v1/combined/email/:email', version: '2015-04-30' do
  # ...
end

This approach has the downside that you're repeating code (not very DRY), but the significant upside that it's very straightforward to test each API version, and that removing a deprecated version is as simple as deleting a file.

Version based serializer

We use the idea of a 'serializer' to represent an object (usually wrapping a model) that can be serialized to JSON and returned via our API. Whenever we want to make changes to the data our API returns (say remove an attribute) it's the serializer that needs changing.

For major changes we version using the same route based technique shown above, having two different resources with dates in the filename representing how the serializer is presented for different API versions.

However, copying a file to make smaller changes is clearly a bit excessive. That's when we fallback to conditional logic:

def attribute
  account.version_has?(:fancy_feature) ? attribute_v2 : attribute_v1
end

We have a mapping of version dates to more readable version names so dates aren't littered throughout the codebase. We also have a convention of using different methods for each versioned attribute rather than an inline if/else statement.

Early days

While it's early days for us and we're still making improvements, the above approaches combined with rigorous testing for each API version seems to be a good approach. If you've got any additional thoughts about best practices we'd love to hear them.


[1] - If you're interested in the Sinatra extension we wrote to do condition based routing, here it is.


Company Logo API

Engineeringby Alex MacCaw on January 01, 2021

Clearbit's free Logo API is still available here in 2021 — and still completely free. We never found anything that catered well to company logos. And yet there's a lot of clear use-cases ranging from setting an organization's default image on signup to pulling in logos next to job listings. Clearbit Logo API The API is incredibly simple, taking a company's domain and returning an image. GET https://logo.clearbit.com/:domain Behind the scenes we're using Clearbit's Company API [https://clear

Introducing ultimate parent to Clearbit Enrichment API

Engineeringby Emily Brown on April 16, 2019

We've added a new data attribute to Clearbit's Company Enrichment API: ultimate parent. Bring full context of your accounts' hierarchy into view — so your team can stay up to speed on new acquisitions and know when they're in conversation with the same parent company.

The Standard in B2B Data

Now reinvented with Artificial Intelligence—Clearbit is the first AI Native Data Provider. Enrich your records, reveal buying intent, and connect with your ideal customers.

image-hero