More than five years ago, we launched the Slack Platform, giving developers an easy way to build apps in Slack and publish them in our App Directory. Today, millions of users bring their work into Slack, and those apps built by over 885,000 active developers on the platform are key to further improving collaboration in Slack.
Over the years, one thing we keep top of mind is designing for a great developer experience. While we can change the implementation of our features under the hood, removing or changing the behavioral contract for an existing API is very hard. That’s why it’s important to carefully think about your API design from the very beginning.
If APIs are designed well, developers will love them, and can become the most creative innovators using your APIs. They will invest heavily, and sometimes even become evangelists for your APIs. We also value a developer’s time and the resource they risk by building on our platform. Bad API design leads to a bare minimum adoption, and even frustration. Bad APIs become a liability for a company.
This is not to say that Slack has always designed APIs well. We made mistakes, and the platform certainly could provide a friendlier developer experience. But by recognizing those mistakes and identifying how to improve them — even sometimes doubling down on being consistent with a past choice we would not agree with in the present — we can improve the developer experience holistically.
Developing your own API style guide can’t completely save you from making crummy decisions or preventing today’s enthusiastic choices becoming tomorrow’s regrets, but they will help you make decisions openly, honestly, and with clarity. In this post, we’ll describe our API design principles, as well as our process for how new APIs are specced, reviewed, and tested. By the end, you should have some ideas that you can take back to your own API process.
Our design principles
Over the years, we settled on some principles that guide our API design.
1. Do one thing and do it well
When you start designing an API, it’s tempting to try and solve too many problems at once. As you try to do many things, it becomes complex and hard to understand. By picking a specific use case, you focus on a single design and keep your API simple. Simple APIs are not just easy to understand but are also easier to scale, more performant, and safer. Moreover, it’s easy to add new features to an API, but hard to remove them.
At Slack, most of our APIs have followed this philosophy. However, in some cases, we had to break existing, complex APIs into simpler, more-focused APIs. One of our most popular API methods,
rtm.start, became quite expensive over time. This method would return a URL to connect to our websocket API along with a wide variety of data about the team, its channels, and its members. As team sizes grew, this payload became unwieldy and large, which was expensive for developers to handle.
Even though a handful of developers used the data returned from this method, most developers wanted only to connect to the WebSocket. As a result, Slack introduced a new API method,
rtm.connect, that only did one thing: return a WebSocket API session URL without returning any other data in the payload. This new method has helped application developers and Slack to overcome some of the scaling problems of
One takeaway from this specific story is: when in doubt, enforce a finite number of objects in any collection or paginate them. It’s not a premature optimization to define sane, rational upper bounds. When you let organic growth show you where those boundaries are, it’s that much harder to contain them to something more reasonable.
2. Make it fast and easy to get started
It’s important for developers to be able to understand your API and get started quickly. You want to ensure developers who are unfamiliar with your API can complete a “Hello world” exercise in a reasonable amount of time. One metric you can use to evaluate this is “Time to First Hello World.” At Slack, we’d love for entry-level developers to be able to learn about the platform, create an app, and send their first API call within about 15 minutes. Your target “Time to First Hello World” will vary depending on the platform you run and your developer audience’s experience level.
Getting started guides, tutorials, sample code, and interactive documentation can go a long way towards helping developers get started quickly. At Slack, apart from getting started guides and tutorials, our documentation includes an interactive API tester where developers can try out any API endpoint in a browser. We have even begun weaving in code snippets in multiple languages that can be easily plugged into code using our growing collection of SDKs.
Being able to get started quickly is great, but are your developers going beyond “Hello world”? If you want to measure your developer experience, you will also want to pay attention to other goals you want your developers to achieve. For us at Slack, we think it’s important for a developer to get their app in front of at least one other user, or to reach some point of interactivity where the app can do more than broadcast a message on command.
As you design your initial API documentation, the question you should always be asking is: What’s important for the platform you’re building?
3. Strive for intuitive consistency
You want your APIs to be intuitively consistent. That should be reflected in your endpoint names, input parameters, and output responses. Developers should be able to guess parts of your API even without reading the documentation. It should work similar to what they already know. There are 3 levels of consistency:
- Consistency with industry standards: It should adhere as closely as possible to accepted best practices in the industry.
- Consistency with your product: You should choose field names based on what you call those concepts in your product. avoid abbreviations, acronyms, and jargon. Be verbose.
- Consistency with your other API methods: Names that you use in different API methods should be consistent with each other.
One of the best ways to achieve consistency is by solidifying and writing down your opinions about API design, especially when there’s no single right or wrong way to do it. Pick a side and stick with it. At Slack, we wrote down comprehensive API design guidelines defining the consistent practices and patterns that we follow for our API.
4. Return meaningful errors
Another principle for designing APIs is to return meaningful errors, making troubleshooting easier for developers. Too often during API design, engineers pay little attention to the errors returned by their APIs. Incorrect or unclear errors are frustrating, and can negatively affect the adoption of your APIs. Developers can get stuck and just give up. Good errors are easy to understand, unambiguous, and actionable. They will help developers understand the problem and how to address it. Implementation details should not leak in your API, particularly errors.
In addition to returning error codes, it’s often useful to add longer-form errors, either in the documentation or somewhere else in the API response. These can include human-readable descriptions of errors, including how to address the issue or link to more information.
If you’re also building SDKs and libraries for developers, it’s important not to “swallow” error messages and warnings. Ensure the developer has access to anything that could be useful in a harrowing debugging session, like HTTP headers and raw request bodies. It’s great if an SDK can interpret errors and make them even more useful, but a developer should also be able to read raw API documentation about an error and still pinpoint it at the SDK level. We are sometimes guilty of this ourselves, robbing developers of our platform’s helpful error messages.
5. Design for scale and performance
Bad design can limit performance. So, while designing an API you should ensure these few things:
- Paginate big collections: Quite often, APIs need to handle large datasets. An API call might end up returning thousands of items. This can overload the web application backend and even slow down clients that can’t handle large datasets. For that reason, it’s important to paginate any large result sets.
- Do not nest big collections inside other big collections: Pagination, in that case, is too complicated.
- Rate limit your API: You do not want a single misbehaving developer or runaway code loop to bring your application down. To protect the infrastructure while increasing reliability and availability of the application, you should add reasonable rate limits.
One of Slack’s APIs,
channels.list (now retired), used to return a list of channels and all members in each channel — essentially nesting a collection into another. When Slack was born, we had an assumption that each team would be limited to a few hundred users. However, as the product evolved and grew, that assumption was no longer true. We started to have teams with tens of thousands of users. At that point, this structure that would require returning all channels and members for each channel in the same API call became too hard to support. We then broke this into two different, simpler APIs: conversations.list and conversations.members. This not only helped improve our API performance but also helped improve the developer experience.
We mention this topic of pagination again, not only because pagination is important but because the pattern of the mistake is the same. “Good enough” for you probably isn’t good enough for the rest of the world. Part of the joy of an API is never knowing how another developer is going to use it. And yet it’s still up to you to anticipate many possibilities that you may be blinded to, especially if you yourself are also a consumer of the same API.
6. Avoid breaking changes
The last thing you want to happen when releasing a new update of your product is to break the code of your clients because of an API change. So, what’s a breaking change? Any change that can stop an existing app from functioning as it was before your change.
At Slack, the philosophy we follow is what worked yesterday should work tomorrow. That said, change is inevitable. In rare and exceptional cases, we decide to introduce breaking changes. How much notice to give and to what lengths we’ll go to make the experience for developers a smooth one varies depending on many factors like the number of users and workspaces impacted and the degree of difficulty for developers to handle. You don’t want to be asking the same developers to handle breaking changes every few months.
Things don’t always break when you want them to. Have an (apologetic) communications plan for how to respond to a breaking change you didn’t anticipate and cannot roll back.
Beyond well-defined API design guidelines, we also have a rigorous API design process that enables any team at Slack to build public APIs. Here are the steps that we follow:
1. Write an API spec
Once a team nails down the problem they want to solve and have defined use cases for APIs, we start by writing an API spec. This spec describes various aspects of APIs that developers will be using. This may include information like method names, purpose, example request, response, and possible errors. The spec gives us an opportunity to think through API design thoroughly. It also acts as the central draft that keeps everyone aligned on what our objectives are, and how our resources are exposed.
If you’re on a team that’s so inclined, writing JSON schema and/or specifications in a public format like OpenAPI or AsyncAPI can even help you prototype your API before implementation. Publishing your schema for developers can help them jumpstart their own tooling, but the commitment to keeping specs up to date is daunting. It’s another area we could improve ourselves.
2. Internal API review
It’s much more efficient to spot issues with your design before writing any code. At Slack, after writing an API spec, engineers share them in an internal Slack channel called #api-decisions. This is an opportunity for engineers building the API to get a diverse perspective on their proposal from a cross-functional group that comprises people from developer relations, engineering, product management, developer support, partner engineering, and security. If a change needs a deeper discussion, we review it in our regularly scheduled API office hours. During these meetings, the group discusses topics like, “Are the changes introduced consistent with our other existing APIs?”, and “Are they aligned with our API design guidelines?”. The group also dives deep into naming, usability, security, and performance considerations of the change.
3. Early partner feedback
Along with internal stakeholders, we also provide a draft of our API spec to the partners who are our ideal developers to implement the API. Their feedback is critical in validating that the API solves the problem it is supposed to solve, as well as help us understand the aspects of our API that need improvement. By seeking this feedback before we write any code, we can quickly iterate the API design and build a much better API.
4. Beta testing
Before we make our new APIs widely available, we provide early access to selected partners. These partners integrate our APIs into their products and provide us detailed feedback on the API. That gives us an additional opportunity to further improve the API design and fix any identified issues before the public release.
If you’ve gone to the trouble to set your own API design guidelines and to build a process around reviewing prospective APIs against them, then it’s also likely your team has now internalized your guidelines to some extent.
It won’t be long before you encounter cases that don’t match the scenarios you’ve predicted. You will make compromises that go against your own good advice — sometimes in service to your own engineering org, or in pursuit of a still disappointing consistency. But sometimes an entirely novel kind of API comes your way, and taking inspiration from the spirit of your guidelines can allow you to unblock your cross-functional partners. You can always write the letter of the law later.
Designing intuitive, consistent, and easy to use APIs is hard. In this post, we covered API design principles and the design process that we follow at Slack. Some key takeaways are when you’re building an API: spend time thinking about your API design up front, be intentional with your design choices, and collect feedback from multiple stakeholders.
We’re just getting started here, and we’re excited to see what we’ll be able to do next! If you like working on platforms and building APIs that developers love, come join us!