February 23, 2013

Building Stripe's API

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 most web 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

  1. 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.
  2. 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.