We’re proud of being good at using our tools, but we’re also willing to do things that don’t scale if it means we can move quickly.
Let’s acknowledge two general truths right up front. One, developers like making things. Two, we’re pretty lazy. Sure, we like to solve problems, but we tend to avoid learning about domains not directly related to the problem we’re solving. That’s why APIs, which abstract away the details of those domains, exist! And yes, we’re proud of being good at using our tools, but we’re also willing to do things that don’t scale if it means we can move quickly. We’ll not only discover and depend on private or undocumented API features that haven’t been flagged away, but we’ll also merely skim all those best practices and warnings from the documentation. Since we often depend on bugs in the API’s implementation, our code might break when those bugs are fixed.
An API is, simply put, an interface for developers. As a developer, you might think designing software for an audience that includes yourself is easy—but developers are, in fact, difficult users to build an exceptional product for. With that in mind, I propose two user personas for API design: the eager developer and the discerning developer. The eager developer chooses convenient APIs that are easy to understand and fast to integrate. The discerning developer chooses reliable, expressive, customizable APIs that will serve their needs as they iterate. While most developers have a bit of both personas in them, it’s useful to consider them independently to ensure your API strikes a balance between convenience and power.
Designing for the eager developer
Eager developers love to see taglines like, “Build a real-time chat app in 10 lines of code,” “Process payments in seven lines of code,” and “Generate a chair in five minutes.” They don’t want to read a lot of documentation or take a quiz before they can get started. They want to run some code and see what happens, even if that code isn’t what ultimately makes it into their application. They’re probably using your API to save time, so their impatience is justified.
Say there’s a new demand for stylish, ergonomic chairs among your customers, so you’re building an API for custom chair design and fulfillment. The eager developer is so excited to get started they won’t want to stop to learn about wood grains and proper lumbar support before they see their first chair come to life. Show them runnable code snippets that will display a completed chair order (even if it’s not customized exactly as they want) as early as possible, before they even sign up. Seeing code run successfully gives developers the same feeling of delight as finishing World 1-1 in Super Mario Bros. They might not have saved the princess yet, but they’ve started to build a mental model of the concepts in your API—learning the controls and recognizing patterns they can apply later.
Designing for the eager developer doesn’t mean you have to artificially condense your code into as few lines as possible or overabstract your domain by pretending chairs are always boxy things with four legs and a back. It does mean you only reveal the minimum API surface area necessary to get started. Begin by writing a basic, end-to-end integration guide before you write a single line of code. This is like starting with wireframes for a UI product: It helps you anticipate the number and shapes of API requests you’re asking users to make, whether they come from the client or server, and whether they fit users’ application code.
With this in mind, package your API so it reveals only the complexity necessary for the most straightforward use cases. Ask yourself: What’s the first piece of code the eager developer will interact with? Does it get developers to a completed chair order quickly, or teach them something they need to know to order a basic chair? Does the guide force developers to make decisions about the chair they’re building that they don’t have enough information to make yet? (Don’t introduce wheels or recliners or odd numbers of legs too early, even if you think they’re cool.) Solicit feedback on your integration guide. Do developers immediately understand what your API does? Most importantly, does your packaging immediately show developers how simple you’ve made chairs, and thus demonstrate the value of your API?
Create pits of success so that even when developers hit an integration error or write a bug, they’ll still fall into the correct integration.
All developers will make mistakes, especially when they’re in a hurry. After building an API prototype, create pits of success so that even when developers hit an integration error or write a bug, they’ll still fall into the correct integration.
Users should be able to intuit what other parts of your API might do based on their first integration experience. For example, if you have a parameter called
dimensions in your API for both chair and stool creation, it should behave the same way and have the same structure across both objects. If you have many types of chairs that come in preset sizes, their
size property and its values should be consistently named so developers can intuit their meaning.
Naming is perhaps the most important detail to consider. Avoid domain-specific jargon (do you really need to call it
casters if you can just say
)and decide up front what regional spelling you want to use across your entire API (does the chair have a customizable
colour?). Spending a few (very energizing) hours every now and then debating the name of a particular parameter is often worth it.
Error messages should include instructions on how to fix the problem. For instance: “Unexpected parameter:
casters. Did you mean
wheels?” In fact, developers should be able to build a pretty good integration using just error messages. If you find many developers are running into the same error, consider making that usage of the API work the way they expect it to.
Designing for the discerning developer
If your objects and functions can’t be concisely defined, they also can’t be quickly understood.
If you’re only building for the eager developer, it’s easy to abstract over too much of your domain. Discerning developers look for APIs that continue to satisfy their needs as they grow and require increasing customization. To do this, they build a mental map of the concepts exposed and abstracted away by your API. Often, they’ll jump past your integration guide and go straight to your API reference to get their bearings and see the full range of capabilities. A developer with no context about your domain should get a good sense of what you support by scanning your reference. If your objects and functions can’t be concisely defined, they also can’t be quickly understood.
To design an API for this user persona, define enough concepts to cover the complexity of your domain, even if you don’t reveal them all to the user at once. Depending on the type of API you’re building, concepts can manifest in different ways: as objects (and operations on those objects), commands, or functions. For example, a
chair object and a
purchase function may be initial core concepts in your chair API. It’s easy to fall into path dependence—to add just one more parameter (
purchase to support free sample orders, for example, or a couple more properties and enum values (
allowed_quantities=1,2,4,6`, `quantity=4) to
chair to support ordering a set of chairs. Instead, you should probably separate the payment and fulfillment concepts (so a sample order can be fulfilled without purchasing it and without redefining what “purchase” means) and introduce a grouping concept to represent sets of chairs.
Once an API is released, it becomes harder to redefine core concepts because this forces users to migrate to a new API. For fast-changing domains, this is hard to avoid. Even so, coming up with future-proof concepts helps avoid unnecessary migrations. Be wary of overloaded concepts and concepts that represent similar things—both are difficult for users to understand and to know when to use. A good exercise is to write down a list of your concepts and describe each in a sentence. If you can’t, or if you find yourself writing too many caveats (“but stools that have backs are ordered via the Chairs API and not the Stools API,” or, “and chairs with
quantity>1 cannot be reclined”), the concept is likely duplicative or overloaded.
You’ll know you’ve succeeded when other developers start using your design patterns in their own products.
Another exercise: Write a second integration guide that describes how to fully customize the integration. What additional skills does World 2-1 of your API require? Maybe the developer now needs to learn how to configure chairs that have variable height, or to purchase multiple chairs at once. Remember, customizing a basic integration shouldn’t require a developer to significantly change their integration or unlearn patterns they’ve already internalized and coded against.
Also vital are the finishing touches to your API’s documentation, website, dashboard, or command line interface—a well-placed animation, a snappy loading screen. To a discerning developer, they’re often as important as the API’s functionality because of their impact on user experience. Because these surface areas are easier to change than the API itself, you can take more risks and pursue more opportunities to delight developers. Apply traditional product design techniques here: Do user testing on your docs, run an A/B test in your information architecture, or run a beta program for a supplemental tool, such as a command line interface for your API. You’ll know you’ve succeeded when other developers start using your design patterns in their own products.
The challenge of building future-proof APIs
Good design requires both practice and intuition.
How do we make sure an API is mostly right before releasing it? In traditional product development, there’s a foolproof strategy: launch something, talk to users, and iterate using user research, beta tests, and A/B tests. These strategies don’t work out of the box for APIs. User interviews tend to be lower signal: Watching their reactions to documentation doesn’t show you how users would actually write code in the context of their own applications. Beta tests are more difficult to run: The barrier to entry is much higher, and testers integrate on their own timelines, within the context of their specific needs. Once they integrate, you’ll need to wait for them to update their integration when you make changes based on feedback. Finally, A/B tests are off the table: You can’t show your users one version of your API on Monday and a different one on Friday and expect them to trust your API’s stability.
Good design requires both practice and intuition. The following methods—alternatives and extensions to existing product development practices—should not be treated as one-size-fits-all solutions for API design, but they are a place to begin.
|Traditional method||API-friendly alternative|
|User interviews||Friction logs: Ask a user to integrate against your API and keep a log of the questions, points of friction, and moments of delight they encounter. This usually takes several weeks.|
|Beta testing||Pilot program: Iterate intensively with a few representative users who are enthusiastic about your product, and give them real-time access to support (ideally through a direct line to your team). Because any friction they encounter could cause them to pause their integration, try to address feedback and fix bugs in the moment to maintain momentum. Build dedicated dashboards to monitor which errors they run into.|
If you don’t have ready access to an ideal user, you can still get good feedback if you set expectations appropriately. Make it clear that as part of the beta program the user will be asked to make changes to their integration if your API evolves. Additionally, ask them to write a friction log documenting the experience so you get higher-signal feedback.
|A/B testing||Dogfooding: Instead of A/B testing, internally integrate against and create friction logs for various versions of your API as soon as you have a prototype based on your design. (It doesn’t matter if the API is ready for production scale yet.) Internal friction logs must strongly inhabit your developer persona of choice in order to be actionable. Take as many screenshots as you can while going through the flow—anything that will remind you of the issues you ran into. Once you’re finished integrating, go through the screenshots again to flesh out your friction log.|
Of the strategies described above, dogfooding is the single most effective way to measure your API’s convenience and power. While dogfooding an API is harder than dogfooding a UI (the dogfooder has to make up a use case and write code), it’s both possible and worthwhile. Break the API, find confusing errors and edge cases, pretend to be at a hackathon, jump into an unknown domain. Dogfooding in real time is especially useful because you can share the pain of the developers in the (virtual) room as they run into errors or points of confusion in the documentation.
At scale, rely instead on a friction log, especially if you’re not the only person working on the API. Friction logs should include relevant context and personas, a stream of consciousness–like log of the integration journey (including screenshots, links, and instructions on how to reproduce errors), as well as overarching considerations. What kind of chair did you want to design, and for what purpose? How much did you know about chairs already? How much time did you have to build your chair application? What keywords did you search for? What other tools did you use? Where did you feel lost, and how did you get back on track? What errors did you run into? How confident were you that you integrated correctly? At what points during the integration did you feel good? Once you’ve seen all the issues developers run into, you’ll start to develop an intuitive sense for the sorts of things that will introduce friction into the integration.
Designing for all developers
If eager developers want convenient APIs, and discerning developers want powerful APIs, real-life developers want both. Though they’re particularly challenging users to design products for, they’re also particularly rewarding users to design for. When you delight a developer, you know you’ve created something surprisingly, meaningfully great.