#12 - API Design and Stripe

API Design is hard, but Stripe nailed theirs. What can we learn from them?

Hey all,

Last week, I discussed upgrading software systems, but also upgrading the “wetware” that controls our awareness of race. We’ve seen countless number of individuals and companies add their voices and throw their support behind Black Lives Matter. Some cut straight to the chase:

But the work is just starting.

For me, in my little corner of Software Mentor, I’ll continue to write articles that propel the careers of software engineers. But my own experience as a software engineer has been shaped by race - each company has its own distinct racial “vibe”, which affected how confident I felt at each job, and my output.

Race is pervasive in America and tech is no exception. It’s not the focus of this newsletter, but it’s such a significant part of my experience and thus, expect the theme of race to consistently emerge in future posts.


This week’s topic is about lessons we can pull from the Stripe API. Stripe is one of the most beloved developer focused companies on the planet, and for good reason. Prior to Stripe, you had to jump through all sorts of hoops, use all sorts of torturous technologies (i.e. Authorize.net), just to accept credit card payments.

Stripe turned this multi-week bureaucratic process into a pleasant API that took minutes to integrate with and was even fun to use!

What can we learn from Stripe when we design our own APIs? Here are a few things that stand out to me:

  • Data Modeling and Naming

  • Predictability

  • Reducing Friction

Data Modeling and Naming

At the heart of Stripe’s success is beautiful data modeling. Without this, everything else wouldn’t matter. Coming up with beautiful data models requires creativity, artistry, and a ruthless drive for simplicity.

Let’s see how Stripe nailed this.

Stripe’s problem domain is large, but at their core they accept credit card payments online.

So, what did Stripe do? They designed their entire API around one verb:

  • Charge

Let’s let that sink in for a moment. Developers come to the Stripe API to accept credit card payments on the internet. And lo and behold, there is an obvious way to do that - you “create a charge”.

That seems so… obvious. How did Stripe’s competitors not do this? Let’s look at Braintree.

Braintree solves the same problem, but it opted for a more complex/cumbersome data model centered around a “Transaction”. In order to charge a credit card, you create a Transaction, and upon which you perform a “sale”. The word “charge” doesn’t even exist. Thus with Braintree, you have:

  • Transaction

    • Sale

This is identical in terms of functionality, but now you have an extra layer. Tiny difference, but consider the experience of a developer trying to take credit card payments fast. Are you going to bother to understand Braintree’s mental model of the world where everything is a transaction? Or are you going to go with Stripe, where you just make a charge?

Developers clearly preferred Stripe. There was nothing to learn - their data models were easy to understand for both payments newbies and veterans.

The new Braintree GraphQL API is trying to fix this. They have a number of mutations of the form chargeX (for example, chargePaymentMethod). But it doesn’t fix the problem, because these methods return TransactionPayload objects. You perform a charge, but get a transaction. In Stripe, you create a charge, and get a charge.

One is obvious, and the other is not.

So the lesson here: search tirelessly for simple and delightful data models.

As Joshua Bloch, former Chief Architect of Java at Google once said, “the names are the API talking back to you, so listen them”. As you search for data models, good names guide you to good abstractions.

Predictability

Stripe excels at reducing the amount you have to learn in order to use the API. We’ve already seen that Stripe chose data abstractions that mapped well to a beginner’s mental model of credit card payments.

But they go much further than that. Stripe makes sure their API is predictable, such that by learning one part of it, you gain familiarity with a lot of it. Predictable patterns everywhere. Let’s touch upon three:

  • REST-like API for resources

  • List Object

  • Type Identifiers and IDs

REST-like API for resources

Stripe has done a masterful job of mapping the credit card domain to a RESTful style. Here are the API endpoints for charge:

Apart from /v1/charges/:id/capture - you can probably guess what each endpoint does! That’s predictability.

List Object

Whenever a Stripe endpoint returns a collection of resources, it returns a list object. For example, look at the “list all charges” endpoint:

But it’s not just list endpoints. Look at the refunds field on the charge object:

It’s a list object, just as you might have predicted. Learn once, use everywhere.

Type Identifiers and IDs

The structure of ids in Stripe is pretty unique - each id starts with a 2-3 character type identifier. A charge is ch_asdfasdf and a refund is re_asdfasdf.

When you’re looking at a bunch of ids - being able to distinguish between types is extremely useful.

And for the most part, the type identifiers are extremely predictable:

  • ch: charge

  • re: refund

  • dp: dispute

  • evt: event

  • txn: balance transaction

Reducing Friction

Stripe worked tirelessly to remove friction from their onboarding process. Some of this was at a product level. Instead of requiring customers to get their own Merchant Account, Stripe allowed you to take a payment without getting your own. This was hugely innovative at the time and eliminated a massive amount of friction.

But in terms of their API, Stripe finds ways to reduce as much friction as possible. My favorite instance of this is how they deal with identity.

If you look at Stripe’s API reference for making a charge, you will see the following cURL:

With this single statement, you can make a test charge for $20 USD. (Well almost, you need to generate a card token first)

What I want to call out here is the -u sk_test_asdfasdfasdf: flag. The -u flag means you’re telling cURL to add a Basic Auth header. The colon is important, it separates the username from the password. But notice… how there’s nothing after the colon!

For Stripe, there is no password - the username is both!

The vast majority of APIs require you to generate a client id and a client secret, which you then pass via Basic Auth as the username and password, respectively. Stripe cuts the complexity in half without trading off any fundamental security. It’s just one fewer thing to think about.

Instead, Stripe gives you a private key, which you access from the developer dashboard. And you can roll this key (meaning to invalidate the current key, and get a new one) any time you want.

Stripe deviated from the status quo because they saw something that would reduce friction for developers. Their mission is to “increase the GDP of the internet” and reducing friction helps them do exactly that

Conclusion

In a post on Quora, Robert Oswald a former Braintree dev, had this to say about Stripe:

Braintree (a company I worked for for many years) was the closest competitor on the scene. We already had a developer friendly API and great support. But Stripe's API was next level and Braintree couldn't match the onboarding.

In this post, we’ve broken down aspects of that “next level” API: data modeling, predictability, low friction to use.

A good API is more than just a good business. It transforms a previously inaccessible technology, and put it in the hands of millions of developers. It bulldozes the old status quo, and empowers builders who have little domain knowledge, no connections, and no previous ties to the industry. A good API, paired with good documentation, is liberating.

So when you go about designing your next API, make sure to keep these principles in mind.

Until next time,

Phil