5 Traits That Popular APIs Have in Common

5 Traits That Popular APIs Have in Common

Posted in

It’s always helpful to learn by example, especially when those examples are the best of the best. That’s one of the main advantages of APIs becoming more mainstream in recent years. We’re well out of the experimentation phase of the API economy — APIs have become a vital component of the digital economy in recent years, and there are many excellent APIs to study and learn from.

Examining some of the most valuable APIs helps us discover what essential features make up an excellent API developer experience. It can help pinpoint potential pitfalls and errors, and inform new API developments. With that in mind, here are five traits we’ve found that some of the most popular and useful APIs tend to have in common.

1. Simplicity

As programmers and developers, it’s our job to solve complex problems. We shouldn’t make them more complex with our solutions. It’s often said that the true benchmark of mastery is being able to explain something to a kindergartner. It’s often infinitely harder to design and implement a simple API than a complex one.

There’s a tendency to want our APIs to do everything for everybody. That’s the wrong approach. Instead, focus on doing particular things and doing them very well. If you encounter a function outside your current API’s scope or domain, you might want to design and build another API instead.

Consider SendGrid, currently listed as the most popular API on RapidAPI. To send an email using the SendGrid API, you just have to use the POST send command. To retrieve all of your email alerts, you can use GET retrieve all alerts. There’s little danger of confusing what either of those commands do. You could probably use the SendGrid API in its basic form without even looking at its documentation which no doubt plays a part in its popularity.

TikTok’s web API is another masterclass in simplicity and ease. To retrieve a user’s profile and display it in a web app, you simply must call the /v2/user/info/ endpoint.

2. Provides Useful Abstraction

Good APIs implement a level of abstraction in their designs. A lack of abstraction in your API design can result in sensitive user data being exposed. It can also expose vulnerable endpoints, resulting in incidents like the recent Optus API breach.

Take Imgur, a popular image-sharing API. Imgur’s API extensively uses abstraction to protect sensitive user data. For instance, developers call the https://api.imgur.com/3/account/{{username}} endpoint to retrieve account information. The username is passed from the access token, which is governed by 0Auth. This is a simple example of how abstraction can help ensure API security. Users can also use the account_id parameter to retrieve user data. This is a good illustration of using API abstraction to make your API less breakable and easier to use.

Adding an abstraction layer is generally good practice when designing an API, anyway. It makes a service more flexible and scalable. When your API isn’t tightly coupled, you’re far less likely to experience breakage or downstream service outages. Plus, API abstraction makes your API more discoverable and easier to navigate.

abstraction

credit: @BrianH

3. Discoverable

Speaking of which, your API can only be popular and useful if people can find it. API discoverability is equally important for your own team, as you have to keep track of what APIs you have and how they power your development projects.

credit: @RapidAPI

API discovery is an important aspect in making an API self-contained, which helps an API to be hypertext-driven as dictated by Roy Fielding. This can be augmented with good API documentation and sensible names for your endpoints, making your API even easier to use and navigate.

For an example, let’s take a look at GitHub API, one of the most popular code-sharing APIs for developers. Opening that endpoint returns the following:

{
  "current_user_url": "https://api.github.com/user",
  "current_user_authorizations_html_url": "https://github.com/settings/connections/applications{/client_id}",
  "authorizations_url": "https://api.github.com/authorizations",
  "code_search_url": "https://api.github.com/search/code?q={query}{&page,per_page,sort,order}",
  "commit_search_url": "https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}",
  "emails_url": "https://api.github.com/user/emails",
  "emojis_url": "https://api.github.com/emojis",
  "events_url": "https://api.github.com/events",
  "feeds_url": "https://api.github.com/feeds",
  "followers_url": "https://api.github.com/user/followers",
  "following_url": "https://api.github.com/user/following{/target}",
  "gists_url": "https://api.github.com/gists{/gist_id}",
  "hub_url": "https://api.github.com/hub",
  "issue_search_url": "https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}",
  "issues_url": "https://api.github.com/issues",
  "keys_url": "https://api.github.com/user/keys",
  "label_search_url": "https://api.github.com/search/labels?q={query}&repository_id={repository_id}{&page,per_page}",
  "notifications_url": "https://api.github.com/notifications",
  "organization_url": "https://api.github.com/orgs/{org}",
  "organization_repositories_url": "https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}",
  "organization_teams_url": "https://api.github.com/orgs/{org}/teams",
  "public_gists_url": "https://api.github.com/gists/public",
  "rate_limit_url": "https://api.github.com/rate_limit",
  "repository_url": "https://api.github.com/repos/{owner}/{repo}",
  "repository_search_url": "https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}",
  "current_user_repositories_url": "https://api.github.com/user/repos{?type,page,per_page,sort}",
  "starred_url": "https://api.github.com/user/starred{/owner}{/repo}",
  "starred_gists_url": "https://api.github.com/gists/starred",
  "topic_search_url": "https://api.github.com/search/topics?q={query}{&page,per_page}",
  "user_url": "https://api.github.com/users/{user}",
  "user_organizations_url": "https://api.github.com/user/orgs",
  "user_repositories_url": "https://api.github.com/users/{user}/repos{?type,page,per_page,sort}",
  "user_search_url": "https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"
}

This is an excellent example of API discovery in action. The user can retrieve just about everything they need to use the API from a browser or command line without ever having to open the documentation.

4. Symmetrical and Consistent

Consistency is key to any tool’s success. It lets consumers know what to expect and increases the trustworthiness and reliability of a product or brand. Consistency is even more important for developers, as it saves them from unnecessary headaches, errors, and having to endlessly check the documentation.

It’s always a good idea to practice symmetry in programming and development. This means using arguments in consistent ways. For example, if you have a function that lets you retrieve user info using Open(user_id) you should also include a Close(user_id) command. This is a simple, straightforward way to help users understand how to use your API as well as what it’s doing.

Symmetry doesn’t just pertain to naming, either. Using consistent indentation throughout your code snippets is another quality that makes developer resources symmetrical. For example:

public class Person {
    private String name;
    private String about;
    private int birthYear;

    public Person(String name, String about, int birthYear) {
        this.name = name;
        this.about = about;
        this.birthYear = birthYear;
    }

    public String getName() {
        return name;
    }

    public String getAbout() {
        return about;
    }

    public int getBirthYear() {
        return birthYear;
    }
}

Consistent formatting makes code far easier to understand. To achieve consistency and symmetry, it helps to think like a publisher. Reviewing and linting your code before publishing will help you catch any inconsistencies and errors.

A lack of symmetry isn’t just cosmetic — it can cause actual troubles. For example, consider this issue with the Microsoft Rush Stack API. A developer discovered that when they switched commands from "commandKind": "global" to "commandKind": "bulk" in command-line.json the name field changed without explanation in the global section. The user expressed a wish that bulk commands had their own shellCommand function to make it easier to switch between commandKinds.

This raised an interesting discussion on why this might not be the best approach for Rush Stack API, in particular, as the bulk is largely used for running scripts. Other developers felt that bulk commands should have their own name field for aliasing purposes. This entire conversation wouldn’t have been necessary had the API developers stuck to a symmetrical API design.

5. The Principle of Least Astonishment

This trait of a successful API seems contradictory to many of the other main tenets of software design and business in general. When it comes to designing a useful and popular API, you don’t want to surprise users. Instead, you should strive to give your users exactly what they’re expecting.

The Principle of Least Astonishment (also known as the Principle of Least Surprise) is a guideline for software and UI design. It states that a component should behave exactly the way the user expects it to. To illustrate, think of the arrow on its side, an icon common to most music players. Instead of play, imagine making that mean rewind — or even worse, record. That’s not going to be a very popular music player.

Likewise, an endpoint like UserAddress should be instantly apparent for an API user. You don’t want to deliver any shocks or rude surprises to your developers. Even minor annoyances can cause your users to turn to one of your many competitors.

For a cautionary tale about what happens when the Principle of Least Astonishment is broken in API design, consider this story about migrating a Ruby on Rails monolith to Ruby 4.

This app tracks assets across the internet and generates reports on them. Assets are recorded as Records, and the information is stored as Metadata. The developers encountered some unexpected issues when transitioning from Rails 3 to Rails 4, though.

A simplified version of their code looks like this:

class Record < ActiveRecord::Base
  has_many :metadata
end

class Metadata < ActiveRecord::Base
  belongs_to :record
end

class RecordsExporter
  def export_data
    records = Record.exportable.includes(:metadata)

    filtered_records = filter_exportable_records(records)

    serialized_records = RecordSerializer.new(filtered_records).serialize

    upload(serialized_records)
  end
end

In Rails 3, record.metadata.class accepts a simple array. In Rails 4, however, it’s typed as ActiveRecord::Associations::CollectionProxy. It’s a simple enough fix, yet, developers weren’t able to catch the error before launch, though, resulting in some headaches. This wouldn’t have happened had the Rails API engineers picked one solution and stuck with it.

You don’t have to be an expert programmer to create an excellent API. Even if you are an excellent programmer, though, there are always new things to learn. We’re always learning, growing, and evolving, which is one of the reasons APIs are such an exciting and inspiring field in the first place!

Many of the traits of the best APIs are common to programming in general, anyways. For starters, we should always strive to deliver consistent products and symmetrical code. Abstraction layers are useful for cloud-based apps and services and microservices. Simplicity is always a good idea, regardless of what you’re making, as it lets users begin using your API immediately upon discovery.