Last week, I gave my first conference talk at the API Strategy and Practice conference in New York. Pretty exciting!
I thought it would be interesting to talk about Stripe's API, particularly lessons learned and what kind of things we did to try to make the API as easy to use 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.
Of course, we don't claim to have all of the answers. Every API is different, and a lot of what you see on Stripe today is the product of a lot of thought and discussion, as well as a lot of trial and error. We're constantly experimenting and improving.
Hopefully you find something here applicable towards your own API! (:
Make it easy to get started
It may sound like a no-brainer, but the best way to get people to try out (and hopefully eventually use) your API is to make it really easy to get started.
To that end, we do things like including pastable code snippets throughout our site and documentation. 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 fill in your test API key, otherwise, it's a sample account's API key), you can see the Stripe API in action.
All of our documentation code snippets are similarly possible to directly copy and paste—we try to embed as much information as possible (API keys, actual object IDs from the account, etc.) so our users don't have to.
Language-specific libraries and documentation
Since Stripe's API speaks HTTP and JSON, you could easily integrate it into your application with any standard HTTP client library. However, this still requires constructing requests and parsing responses on your own.
We maintain and support open-source libraries in some of today's most popular web languages. It turns out people are pretty attached to their favorite languages.
We had a lot of internal discussions about whether we actually wanted to support our own client bindings or allow the community to organically start and maintain the projects themselves. Is it worth owning the projects if it means that you might have to maintain libraries for languages or frameworks in which you don't have expertise?
Official libraries have the benefit of being consistent: they all have the same level of quality, support the same interface, and get updates at the same time. Having our own libraries also makes it easier for us to have language-specific documentation and help our users with any problems they might be having with a particular integration.
We decided that it was worth it, but this may not be the right answer for everyone.
Have a focused API, but allow flexibility
We've found that it's critically important to keep the API focused and simple.
It's often tempting to add new features that are not obviously necessary to the core API. For example, our users frequently want us to add better analytics, tax calculations, or to send customers receipts1. While these things are nice, every feature you add makes the API more complex and cluttered.
You can instead 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:
Stripe uses webhooks to let our users know when some interesting event has happened. This ranges from events triggered by an API call, like
charge.refunded, to asynchronous events like
Our aim was to make it easy to layer additional logic on top of Stripe events (like sending customer receipts or enabling push notifications). Giving our users the ability to build this kind of customized functionality allows them to control the entire experience for their users as well.
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. We've seen a variety of applications built on top of Stripe so far: marketplaces and checkout pages let users "plug in" their Stripe accounts to accept payments, and analytics dashboards fetch Stripe data in order to show interesting graphs or patterns.
Provide a testing environment
One of the most important things you need with an API is a great test/sandbox environment. This is particularly important for a payments API—our users shouldn't have to make live charges when they're trying to test their integration.
Doing this allows them 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.
Help your users debug
We're developers too. We know from experience that debugging is a disproportionately large portion of the development cycle. We also (unfortunately) know that sometimes you spend a lot of time debugging something that eventually turns out to be really obvious or silly.
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 =
". You can generate API keys from the Stripe web interface. See https://stripe.com/api for details, or email email@example.com if you have any questions.)
>> Stripe.api_key = TEST_KEY => ... >> Stripe::Charge.retrieve(LIVE_CHARGE_ID) 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.
On the other hand, some errors are harder to diagnose (especially from the API's end, since you have limited information about what your user is actually trying to accomplish).
Where possible, we absolutely think it's worthwhile to try to anticipate our users' errors and help as much as we can.
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 per-user version which reflects the state of the API the first time the user made an API request. Most of our new features are additions that aren't backwards incompatible, and they just work automatically for everyone.
Whenever we make a backwards-incompatible change2, however, it doesn't affect the API behavior for any of our current users. Users can then choose to explicitly upgrade their version in the dashboard (after reviewing the detailed changelogs) or can send a version override header in any API request to test the behavior of a specific version.
Thanks for reading!
- Stripe may very well end up implementing these particular features in the future, but it's not generally feasible to try to accommodate everyone's use case.
- We try to avoid this as often as possible.
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.