As a sequel to my talk last year on building Stripe's API, I thought it'd
be useful to go over how we scaled some of our internal abstractions to
continue building and iterating quickly. I gave this talk
at APIStrat Chicago a couple of weeks ago and several events at
HeavyBit last week, who generously recorded and transcribed the whole thing
for anyone who'd like to watch a video. (Thanks, HeavyBit!)

I'm the kind of person who will impatiently watch videos of talks or lectures on
2x speed and greatly prefer reading a blog post that can be skimmed easily, so I decided
to write an accompanying post to go with the slides.

Enjoy! (:

Let's build an API!

Stripe has a lot of APIs, and as a result we had to quickly figure out how
to scale our abstractions and code. Since code is worth a thousand words
(that's a saying, right?), I'll run through an example of building an API.

Here's a super simple example of an API endpoint in Sinatra that
creates credit card charges:

It's starting to get a little crowded in here, but it's more or less a working
API. That was easy!™

What next?

As you start adding new endpoints, functionality, and changes you run into more
problems.

How do you (scalably) do things like authentication,
validation, actual API logic, error handling, authentication, and at the
same time support every combination of behaviors that has ever existed in the
past? We can't ever break integrations—particularly as a
payments processor, broken
integrations means our users are literally losing money every minute. We need
to be able to build and change things rapidly without compromising any API stability or
backwards compatibility.

Once your API has reached a certain size, something that also starts to creep up
on you is dependencies. Documentation is a good example of this. Say you build
an API and write up docs in the form of static
HTML or markdown. It launches and everyone is happy.

A week later you decide to add something to the API. You diligently add it
to your API code and maybe even remember to make the change in the docs as
well.

With more additions or updates though, sooner or later this is going to
happen:

Crap, I forgot to update the docs!
— Everyone, ever

Sound familiar? (This has definitely happened to me more than once.) How did we
start making these problems less painful?

Separate the layers of responsibility

In the example above, many things are going on at once: authentication, validation,
endpoint-specific logic, error handling, and constructing the response.

For authentication and error handling, we use Rack middleware and it works
pretty well. You shouldn't have to worry about authenticating users in the
middle of your API logic (most frameworks have a concept of before filters
for this as well). You
also shouldn't have to hardcode error response formats. Wouldn't it be nice
if you could just throw an error and know that someone else will catch it to
format it into the proper response later?

We represent endpoint validations, logic, and response generation internally in
our code as APIMethods and APIResources. If you're familiar
with MVC, they're very similar to controllers and views for your API.

Make it really hard to mess up

A good UX design principle is that you should make it really hard for your
users to mess up or do the wrong thing. Why not apply this toward building the
API as well?

One thing that we did that I thought was really cool was a system for
documenting our API. To try to address "I forgot to update the docs!"
syndrome, we made it really hard to forget by putting the
documentation right under the code that adds a new property.

Our documentation then auto-generates itself from these specs—for
changing most things, there's no need to go dig up static HTML files.

Similarly for our API libraries (or at least those that can support it),
we don't hardcode properties for each object but instead dynamically generate
them based on the properties present in the response that is received.
This way we don't have to worry about adding new fields to object
definitions in each library.

Hide your backwards compatibility

We're often asked how we implement our API backwards compatibility. This probably
merits an entire talk and post by itself, but I'll go over it at a high level.

When a user starts implementing Stripe for the first time they don't need to
worry about API versions. Instead it's invisible—they'll innocently make
their first API request, we'll record what
internal version they're on, and from then on our code takes care of making sure
we never break their integration.

If a user wants to worry about versions they can: we allow overrides to
be sent in via request headers and users can upgrade their version via the
dashboard. However most people won't care, so they shouldn't have to know about
it. All most Stripe users see is that their integration, even if they first
wrote it years ago, never breaks.

All of our versions live in the same code base and are deployed to the same
service. We don't do separate services or deploys for different versions. The
downside of doing is that it's easy for things to get hairy after a
while.

First pass

Imagine sometime down the road Stripe decides to deprecate the amount
parameter and all charges are now $1. (Disclaimer: I'm pretty sure we
will never actually do this.)

What's the naive way to implement that change in behavior? Maybe something like this:

def execute
if !user.old_version? && params[:amount]
raise UserError.new("Invalid param.")
end
...
if !user.old_version?
response.delete(:amount)
end
end

There are a couple of problems with this. First, who knows what old_version? is
supposed to mean? You can infer it from the code, but it's really not intuitive
and is just waiting for accidental regressions. Second, the regular API logic and
legacy logic are being mixed together. If someone wanted to add or update the something in
the current API (somewhere in that ...), they would have to wade through
those extra conditionals.

Gates

We've modeled our versioning system around a series of gates. A "gate" is a
database flag (similar to feature flags) that our code can use to determine
what functionality to allow. For example, if a user is on an old version and is
therefore allowed to send the amount parameter, they're on the allows_amount
gate.

We declare all of the versions along with the corresponding functionality gates
in a single YAML file:

and are able to decouple versions from the actual behavior they represent in
our code:

def execute
if !user.gating(:allows_amount) && params[:amount]
raise UserError.new("Invalid param.")
end
...
if !user.gating(:allows_amount)
response.delete(:amount)
end
end

To go one step further and actually get that logic out of the endpoint execution
code, separate compatibility layers were added to our code flow. Because who
doesn't need more layers of indirection?

Now when a request comes in it first filters through the request
compatibility. That layer may or may not reject it based on the parameters (like
if someone passes in amount who isn't allowed to) or munge the parameters to
something that the later logic expects.

After the response is created, it passes through another layer of response
compatibility which transforms the response into whatever the user's version
dictates. The great thing about this is that the API logic and response
construction steps can represent the current version of the API without any
legacy edge case clutter.

It's worth noting that these layers aren't free: there's a certain amount of
complexity added when fiddling with requests and responses that are passed through,
but we made the tradeoff to be able to keep our general API code (what engineers
spend 80-90% of their time working on) clean and easier to reason about.

In the real world

What does this look like in practice? Stripe has (at the time of writing) 106
endpoints, 65 versions, and 6 API client libraries. You can do the combination math
yourself.

We would be far behind where we are today if we couldn't find, read, and change
code quickly without being afraid of breaking our users.

Conclusion

So design for yourself. Spend some effort not just thinking about how you can
optimize your users' experiences, but how you can optimize your own as well.

Stripe's far from perfect; we're learning more and more every day and
(like any other startup) there are still very
many things in our code that annoy and embarass us. I
hope this gives others a sense of things we've learned over the years
and helps other developer companies who are tackling the same problems.

If you're ever interested in chatting about any of these topics, I'd love to
chat—drop me an email or poke me on Twitter.

I ended up writing far more than I had intended so if you made it down this
far, congratulations and thanks for reading!

Credit to Sheena Pakanati, Saikat Chakrabarti, Ross Boucher, Greg Brockman, and many others at Stripe for contemplating, building, and iterating on everything covered in this post.

I spend a lot of time nowadays doing code reviews and helping spin people up on
Stripe (or more generally, distributing knowledge about our code base). One
thing I found myself needing over and over again was the ability to show someone
a file or some code over IM or Slack. Since we use Github to host our
repositories, this is as easy as linking to the online file.

However, it's pretty annoying to have to use Github's UI to navigate through
folders, and I almost always mess up trying to type the full path in the URL
(blob? tree?). I also use Command-T with Vim, which means I never remember
file paths or exact file names. (If you don't use Command-T or something
similar, you should—it's the single biggest improvement to my code
reading/writing productivity over the last couple of years.)

"Wouldn't it be awesome if I could just open a file in Github directly from Vim
with some keyboard shortcut?", I thought to myself.

I did a bunch of research to try to figure out how hard it would be to build
myself (I've never written a Vim plugin before), and stumbled upon
git web--browse. From the man page:

NAME
git-web--browse - git helper script to launch a web browser
SYNOPSIS
git web--browse [OPTIONS] URL/FILE ...
DESCRIPTION
This script tries, as much as possible, to display the URLs and FILEs
that are passed as arguments, as HTML pages in new tabs on an already
opened web browser.

Sounds like exactly what I want!

Amazingly, fugitive, a Git wrapper for Vim, actually has this functionality
built in. If you have fugitive installed already, try it right now: go to a file
that's hosted on Github, then type in the command :Gbrowse. It'll
automagically open the corresponding link (keeping in mind what branch you're
on, and everything) in your browser. You can even highlight a couple lines in
visual-mode to have those be highlighted on Github.

I ended up remapping :Gbrowse in my .vimrc to something easier to type,
you should too:

noremap <leader>w :Gbrowse<cr>

I use this at least a few times every single day. Hope you find it useful too!

A couple of friends and I decided around a month ago that we wanted to get better at
drawing, so we decided to embark on one of those "draw one
thing every day for 365 days" projects. Well, if we're being realistic, for
approximately 365 days—we even named ourselves "Approximately 1095"
(1095 = 365 x 3).

It's been really fun so far:

(I'll write up a separate post later about how/where I'm posting these!)

One day I woke up to discover that my friend Jack had, that
morning (on mostly a whim), gone out and purchased a set of painting supplies
and was happily painting away on an easel in front of the bay windows in
his room.

I thought that was pretty amazing and was inspired to do the same. As much as
I like drawing and sketching, the lack of color is sometimes a bit depressing.
I've always wanted to experiment with painting (having done digital/Photoshop
painting but never physical painting), and decided to try it out1!

Color mixing

The day my acrylic paints arrived (2-day Amazon Prime, ftw), I sat down and practiced
mixing a bunch of colors to get a feel for the ratios and color combinations.
And it's a good thing I did, because I instantly made the newbie mistake of squeezing
out entirely too much dark paint (a dark pigment like red will overwhelm a
lighter one like yellow; 90% yellow + 10% red is basically already dark orange).

I ended up with a bunch of index cards with color gradients, much like a Pantone
catalog. Pretty fun. I actually like using physical paints a lot more than
using a digital color picker, since the colors blend together pretty
interestingly when painted, and aren't just one flat color.

First painting!

The prompt of the day for our drawing project was "mermaid" and I knew I
wanted to paint a landscape (so hard to mess up!), so I decided to paint the
underwater castle from the Little Mermaid.

The hardest part was probably being too scared to paint over things that
I had already painted in the background (no undo or layers?!). It was really
fun though.

Highly, highly recommend picking up painting as a hobby. It's relatively cheap
(I got everything for around $50-60 on Amazon), and it's super fun and
relaxing/therapeutic to be swirling paint around for a couple of hours. Not
to mention you get an awesome physical piece of art that you can then decorate
with, gift, or hide in your closet.

I decided to start off with acrylic because watercolor is really hard to
control or recover from mistakes with, and oil has a messier cleanup, but it's
really up to your own preference. Enjoy!

I hope to be posting more about the rest of my painting adventures soon! (:

1: I'm still continuing the drawing project, I'll just substitute paintings in
every once in a while.

I just got back from New York, where I gave my first conference talk ever at the API Strategy and Practice conference. Pretty exciting!

I thought it would be interesting to talk about Stripe's API, particularly lessons learned and what kind things we did to try to make using the API as easy as possible. I've included the slides below, but most of the content isn't on the slides so I'll try to cover some of the highlights below.

As a disclaimer, we definitely don't know everything. A lot of what you see on Stripe today is the product of thought and discussion, as well as a lot of trial and error. I hope you find something in here applicable toward your own API! (:

Highlights

Make it easy to get started

This sounds like a no-brainer, but the best way to get people to try out your API is to make it really easy to get started.

We do things like including passable code snippets throughout our site and documentation. For example, one of the first things you'll see on our front page is a curl snippet you can paste into a terminal to simulate charging a credit card. Regardless of whether you have a Stripe account or not (if logged in, we embed your test API key, else, it's a sample account's API key), you can see the Stripe API in action.

All of our documentation code snippets are similarly easy to copy and paste—we try to embed as much information as possible (API keys, actual object IDs from your account) so you don't have to.

Language-specific libraries and documentation

Since Stripe is an HTTP API, you could easily integrate it into your application with any HTTP client library. However, this still requires constructing requests and parsing responses on your own.

To make this easier, we support official open-source libraries in the mostweb today (turns out people are pretty attached to their favorite languages). There was a lot of internal discussion about whether we actually wanted to support our own API or allow the community to organically start and maintain the projects themselves.

Ultimately, I think there's a certain degree of trust that users put in official libraries, which makes it easy for them to get started (as opposed to trying to audit different third-party libraries). It also makes it really easy for us to have language-specific documentation this way.

Have a focused API, but allow flexibility

One thing that we found critically important was to keep the API focused. It's tempting to add new features that are nice, but not necessary.

For example, our users frequently want us to add better analytics, tax calculations, or to send user receipts. While these things are nice, they're not our core competency and may very well clutter our API with too many options1.

Instead, you should give your users the tools to be able to write their own extensions. We allow our users (and third party applications) to hook into Stripe in a couple of ways.

Webhooks

Webhooks are a way of Stripe letting you (our user) know when some interesting event has happened on the Stripe server. Examples include charge.succeeded, charge.refunded, invoice.paid, and so on.

With webhooks, it's easy to build something on top of Stripe events, like sending a customer receipts. This has the added benefit of allowing our users to control the entire user experience.

Stripe Connect

Stripe Connect, an API we released just last year, is another way of building on top of the Stripe platform. Connect is an OAuth2 API that allows a Stripe user to authorize access to their Stripe account to a third-party application. This application might be a marketplace, whose users want to accept payments, or an analytics dashboard, who wants to be able to have full access to Stripe data.

Provide a testing environment

One of the most important things you need with an API is a test environment. This is particularly important for a payments API— obviously your users won't want to make live charges when they're trying to test their application.

In our testing environment, we allow you to send test web hooks of any type and provide handy test card numbers that trigger certain errors (like declines). Doing this allows our users to easily test the behavior of their own application in the face of different scenarios instead of having to manually trigger things that are nondeterministic, like declines, or time-dependent, like expiring subscriptions.

If there's certain behavior that your user's application potentially depends on, make sure they can test it easily.

Help your users debug

We're developers too. We know that a large percentage of our users' time is probably spent debugging. We also (unfortunately) know that sometimes you spend a lot of time debugging something that eventually turns out to be really obvious or stupid.

For common or easy errors, you (the API) likely know exactly what's wrong. So why not try to help?

>> Stripe::Customer.create
Stripe::AuthenticationError: No API key provided. (HINT: set your API key
using "Stripe.api_key = <API-KEY>". You can generate API keys from the
Stripe web interface. See https://stripe.com/api for details, or email
support@stripe.com if you have any questions.)

Or,:

>> Stripe.api_key = TEST_KEY
=> ...
>> Stripe::Charge.retrieve("ch_17SOe5QQ2exd2S")
Stripe::InvalidRequestError: (Status 404) No such charge: ch_17SOe5QQ2exd2S;
a similar object exists in live mode, but a test mode key was used to make
this request.

If you help your users debug, they'll love you.

Dealing with Change

Lastly, dealing with change is never fun. As much as you hope you'll never have to change the API, sometimes you need to make changes, and sometimes those changes are backwards-incompatible.

There's no easy answer to versioning APIs. We keep a version per-user, which reflects the state of the API the first time they made an API request. This version isn't taken into account when we add a new feature or non-breaking change (i.e. you'll always be able to use new features, regardless of what version you're on).

Whenever we make a backwards-incompatible change2, however, we bump the current version of the API (so that any new users will see the "new" changes) and take the legacy user's version into account in the relevant API code paths.

Users can choose to upgrade their versions in the dashboard (after reviewing the details changelogs, of course), or can send a version override header in any API request to test the behavior of a specific version.

Questions?

If you have any questions, feel free to email or tweet at me. Thanks for reading!

Footnotes

I'm not saying that Stripe is not going to do these particular things in the future, but it's not feasible in general to try to accommodate everyone's use case.

We are usually hesitant to do this.

Credit for various parts of the presentation content go to Greg Brockman, Sidd Chandrasekaran, Evan Broder, and Ross Boucher. And of course, credit to everyone at Stripe for actually doing the things I outlined in the talk.

I spent an embarassingly long time trying to figure out what was wrong ("Do I just not understand how private methods work?!"), and confused one of my coworkers as well in the process of doing so.

However, it turned out to be oldnews. One post puts the issue pretty succinctly:

private methods can never be called with an explicit receiver, even if the receiver is self

So, the problem with self.private_hello is that the private method is being called on an explicit receiver, even though the receiver is technically the same object—you'd need to call private_hello by itself instead.

Having learned access control modifiers in Java first, I thought this was really bizarre. I guess I need to learn Ruby a little better! (: