Building the Brex API
Oct 21, 2021, 6 min read
Oct 21, 2021
6 min read
When we spoke to some of our power users, it became clear that our dashboard and mobile app wouldn’t be sufficient for the scale and complexity of how they wanted to use Brex. Ultimately we want to give our customers all the tools they need to manage their finances and grow their business. For many customers, API access is key to scaling not only their finance team, but their business as well.
We’re excited to launch the Brex API this week! Now, customers can programmatically read their data, issue cards, invite users, send payments and much more. Customers who can’t dedicate time to building directly against our API can automate their processes and workflows using low-code and no-code options like Zapier, Pipedream, and Dashblock.
Brex wasn’t a developer-focused company when we started on this journey and we needed to shift our mindset to serve the developers. Getting this ‘cultural architecture’ right is just as important as getting the technical architecture right, and we were fortunate to have folks on our team that had prior experience in serving the developer audience.
As we oriented the team to take on this work, we took a few critical steps that set us up for success.
- We advocated for good internal APIs so that people thought more deeply about maintaining them and getting them right at the first attempt.
- We built a shared understanding as to why APIs are good for our business and important for our customers.
- We studied great APIs from other companies to help us define API-first principles.
- We set up our Slack developer community so we could get faster feedback and support our customers in real time. We like to say that weeks of coding can save you hours of design. Joking aside, when building an API it’s important to measure twice (or more) and cut once.
The principles below on how we built it are equally important for public and internal APIs.
Design APIs for people
Many think of APIs as being consumed by machines and not people. But APIs should provide a great user experience by being intuitive to understand, easy to read, and by providing great error responses. They should also use familiar patterns, such as status codes and authentication methods, so that the engineers integrating against them can work efficiently.
- Bad: It’s not obvious to the recipient why they received this error message.
"message": "Resource not found"
- Good: This is better than the previous one, the error message is actionable but still requires some guesswork for the recipient.
"message": "Card not found"
- Great: This is much better. It determines the exact error code so recipients don’t need to match against a specific pattern and the error message is human-readable and gives the recipient all the information they need to debug.
"message": “Card with ID 'card_ckuxnye4w000008mkhubd8rgo' not found",
Anyone building a public-facing API should obsess about backwards compatibility. It’s difficult and in some cases impossible to change your callers. It can be expensive for a customer or partner to upgrade API versions, and it may not be a high priority for them. With APIs and backwards compatibility, it’s much easier to extend an API than to deprecate an older one. When it comes to backwards compatibility, addition is much easier than subtraction or modification.
When building the payments API, we discussed how to represent recurring payments in our API. RFC-5545 seemed like a great, generalizable way to represent recurring events, but would require us to update the underlying service to be generic. In the end, we decided to not externalize it. While it’d be trivial for one to build scheduling on top of the API, it’d be hard for us to remove scheduling through our API. Our customers and partners would rely on it and they have their own roadmaps and priorities. We totally get that upgrading APIs isn’t top of mind for them. There were countless other examples where we not only obsessed over backwards compatibility but had to gaze into our future product roadmap and prefigure different paths our product might end up on.
Even the most intuitive APIs benefit from well-written documentation. Our API documentation, contracts, and schema are all in the same place: our code base. This prevents us from having obsolete documentation and makes it easier for API consumers to gain context. Reference links to alternative sources so readers can use it to gain additional context. Most importantly, like with any documentation, communicate with empathy. Think through what might be non-obvious and confusing to developers, and how context can be shared effectively.
In our early set of customers, we took every request for clarification we got as an opportunity to improve our documentation. Each clarification request is a gift that lets you see your APIs with a pair of fresh eyes. As an example, each sentence in our create card endpoint came from answering a query from an API developer.
API == Product
Designing an API isn’t any different from building a product. APIs should be thought of as a product as much as they’re thought of as technology. You should design APIs like you’d design a user experience because that’s what it is. APIs are designed for computers and people. Here are key insights from our product and technical choices:
- Foundational pieces: While the breadth of our product offering is pretty wide, we wanted to ensure that the ways you interact with our APIs are consistent. This is visible externally in pagination, filtering, idempotency, error responses, and many other interfaces of our API. The need for consistency runs a lot deeper and percolates into how we build and think of our products.
- Democratizing API access: Our APIs were built for partners to build integrations against Brex, but we wanted to go a step further and give every customer API access to their own account. As a Brex customer, you can generate API tokens in your Brex dashboard to make API calls for your own account in the way that works best for you. Our APIs use OAuth for authentication and authorization. OAuth can be a complicated spec to implement, but we wanted to provide a simple but secure way of accessing APIs. We’re strong believers in that when you lower the barrier to entry it enables folks to innovate more.
- OpenAPI: Our APIs are described using OpenAPI — a standard, language-agnostic interface for REST APIs. OpenAPI gives us many powers like generating API docs, SDK generators and powers our public Postman collections. Not only did it save us a lot of time, but also improved the developer experience. Our developer portal is hosted by Redocly — a happy Brex customer — to build and deploy our OpenAPI-generated documentation. One of the best decisions we made was to auto generate the OpenAPI specs from our code. This meant that any new parameter that was added was automatically documented and easily communicated to customers and removed any human intervention.
- Tooling and Infrastructure. As much as we wish software was magic, in reality it’s a system with complex interactions powered by great tooling and infrastructure. Like most public APIs, we use rate limiting to prevent a buggy client or denial-of-service attacks from affecting our API availability for others. To support an API program at scale, internal tooling is important. It’s critical to be able to onboard new developers. For example, we built an internal API logging infrastructure to log all API requests with metadata that excludes the sensitive information. This powers our analytics and helps us debug unexpected errors.
While we’ve made a lot of progress on our API, there’s a ton of exciting work ahead of us to scale it. We’re looking forward to releasing new features, including webhooks — an API that calls you — and tons of invisible changes that improve the API experience behind the scenes. We’re excited to see what you build on top of APIs and the types of problems it helps you solve.