APIs are tricky things to maintain. Because they’re designed to be open by nature, it’s hard to test every possible way someone could use it. That makes incredibly easy to break if you don’t think through any changes you’re making carefully, with an emphasis on what a developer is expecting. Luckily, there are a few things you can do to limit the amount of times you inadvertently break a bunch of applications with a well-meaning API update.
First and foremost, version your ****ing API. I can’t emphasize this one enough – it gives you an out for anything else on the list. Revving up the current version of your API is a signal to the developers using it that anything can change. Having the option to give the “anything can change” signal gives you the ability to change, well, anything should the need arise. This gives you the opening to make major, fundamental changes to your API without breaking other people’s code. Not breaking other people’s code is the # 1 priority of maintaining an API, and you’re going to have to make fundamental, backwards-incompatible changes at some point. If you refuse to version your API, you’re either handcuffed in terms of what you can and can’t do to maintain your API, or you’re going to end up breaking your API’s backwards compatibility, which from the perspective of a developer using your API is a broken API, which is a sign of a service that can’t be relied on, that’s the kind of thing that encourages people to investigate using some other product.
In addition to versioning your API, you also need a known deprecation schedule, and to stick to it. You don’t want to support all versions of your code forever, but developers consuming your API need to know when things are going to be cut off. Having a pre-defined policy (e.g. API versions are supported for X months after we release a new version) makes things predictable. The important thing is that you stand by your deprecation schedule – on the date you say version X is no longer going to be available, then you need to shut those servers off. Not standing by your stated policies trains developers not to trust you, and that they don’t have to update their code to keep up-to-date with your current API.
How often you deprecate old versions of your API and how long you give people to switch really depends on how often you upgrade the API – the more often you do it the longer you should give people to switch to avoid overwhelming them – but I would recommend any time period you use be measured in at least months (plural – 1 doesn’t count). Remember, any update to your API is a big deal to the developers using it, and they’re going to have to test their code extensively to make sure everything still works properly – all on top of their regular development workload. That’s why you need to be extremely generous in making sure they have plenty of time to migrate forward.
As much crap as I’ve talked about it in the past, Facebook’s API is a really good example of these practices. They rev their API version a couple of times a year, each time making at least 1 major change per release. In other words, they’re not wasting their version bumping, but they’re still pushing major releases regularly. You have a minimum of 90 days (a.k.a. 3 months) to migrate up to a newer version of the API. All in all, Facebook tends to support each released version of their API for at least 1 year. As a result, Facebook’s API is regularly evolving, without developers feeling any sense of stress about having to rush to upgrade. They could probably stand to give people more time to migrate, but that’s really the only complaint I can think of (their release notes used to be incomplete, but the last 2 or 3 API bumps haven’t included any undocumented changes that broke my code, so I’m thinking they’ve improved on that front).
Now that we’ve gotten versioning out of the way, next up are some things to remember when making changes that don’t involve ramping the API version (and not every change does). Keep in mind, your API is a contract – once you’ve established a certain type of behavior, you’re committed to that behavior until you decide to ramp the API. That means if an API call returns a certain type of ID, it should always return that type of ID for as long as you support that API version. And if those IDs can be used to retrieve information from your system, they better be pulling that data back for as long as those API servers are running. Don’t laugh at my examples – I’ve seen these things change within an API version, and it was a miserable experience. Our code broke for days while we tried to figure out workarounds because someone changed the type of ID being returned by an API call – as a result our code wasn’t able to find data that we expected to be there since the “ID” we had wound up being useless. That sort of behavior makes people avoid your APIs on the grounds that they’re buggy and will only introduce problems into their applications, not to mention the fact that it makes you look like a rank amateur.
Speaking of not changing things on people without ramping your API version, if a call returns a particular type of object, you don’t get to change that return type on people. That means if an API call typically returns an HTML string, you can’t start returning a JSON string. If you think changing the type of ID returned was bad, try just flat out changing the base object that comes back. That should be just basic API principles, but I had to write code to determine what type of data I got back from an API call, and handle each appropriately. It’s almost as if when the API was being updated, the person writing the code didn’t even bother to ask what the API was up to already. The end result to this is an API that seems like it has multiple personality disorder.
Another important thing if you’re going to be a responsible steward of APIs – document them! Not only list each endpoint and what it does, but also a full list of possible parameters and their bounds. Also talk about all the possible fields that could be returned in any document that can be returned by the API. If you can, I recommend setting up something like I/O Docs to supplement your documentation (or even as your primary documentation if you’re willing to put in good descriptions of the endpoints). It gives developers a chance to see how your API works and to see results from live API calls. Good documentation can make or break the adoption of your API (so can not breaking it every time you push a code change, but I’m assuming that if you’ve made it this far into the blog post you’ve stopped doing stupid stuff like that). Interactive documentation lets developers experiment with your API, which is perfect for people who tend to learn by doing like me.
Personally, I think the best APIs out there are the ones powering the apps developers are trying to integrate with. You know the API is useful and stable because it’s powering the very application you’re hoping to integrate with. It also means that the people responsible for the API are sensitive to breaking changes because they’re eating their own dogfood. As a result, breaking changes will break their app long before it has a chance to break 3rd-party apps. The API is constantly being tested with real-world usage scenarios. I’d say probably the biggest issue I’ve had with APIs could have been solved by everyone within the organization committing to using the public APIs as their means of interacting with platform data.
The trickiest thing about APIs is remembering that any major change is going to break things for users. As a result, you need to be able to indicate to users when breaking changes are being made, and support both your new code and old code long enough for them to update their code to support your API. You also need to be able to test your API updates regularly in a way that confirms it’s behaving exactly how you told the people building it into their code that it would. Without that regular testing, it’s all too easy to start treating your API like an application, and forget that there’s a lot of running code out there that depends on your API running the exact same way before you updated it as it does after.