API Versioning At Gusto

Introduction
Just over two years ago, we introduced date-based versioning to the Gusto API. Since then, we’ve released over ten(!) versions of our public API. And over the past half year, our engineering and technical solutions teams have collaborated closely with our partners to deprecate our ten oldest versions, migrating the majority of our integrations to leverage our latest stable API versions — the most performant, secure, and developer-friendly evolution of our API.
In the changing landscape of payroll and compliance, especially at scale, we future-proof our API by versioning it.
In this post, we’ll share more about our API versioning approach, how we continue to operate this change management process, and most importantly, the lessons we’ve learned along the way.
What is API versioning?
If you’re already familiar with API versioning, feel free to skip below to “Lessons learned: A debug log.”
An API represents a contractual exchange of data between a provider and client. Given a specific interface, our partners expect to be able to create, update, and retrieve data in a mutually agreed-upon interface.
Contracts shouldn’t change unexpectedly. When developers integrate with our API, they write code with expectations built into it. Changes that break these assumptions can also break developer integrations; for example, if we suddenly remove a response field or make a request parameter required, our partners’ applications may unexpectedly error. As a critical piece of infrastructure for our partners, this is our worst-case scenario.
Sometimes, implementing breaking changes is necessary for the security, performance, and usability of an API. When we decide to roll out such changes, we encapsulate them into a new API version — much like a new product release. When a new version is released, it has no impact on an existing integration until a partner choose to upgrade their application to use the latest version.
Not all changes are breaking changes. Non-breaking changes can be backported to older API versions and do not impact an existing integration in a disruptive manner. Currently, we release these types of changes to production immediately (with typically no version upgrade required). This offers our existing partners an opportunity to improve their embedded payroll product or integration at their pace.
Both types of changes are always published to our API changelog upon release. You can read more about our latest API versioning policy in our API version guide.
Life of (an A)PI
Incremental system upgrades are common practice; if you’re a developer, you might be familiar with routine major and minor version releases from your language of choice, which may bundle together critical bug fixes, syntax updates, and removed features. A similar mental model: if you’re an iPhone user, you might also receive push notifications when a new iOS version is available and ready to be installed that night.
We’re cognizant that upgrading an API version doesn’t auto-magically happen overnight (and certainly not while you’re asleep — though one can dream!). A new version is a disruptive event in the lifecycle of an API. For an API consumer, it means that they must, at some point, allocate engineering resources to upgrade their integrations. Therefore, each version release results in the accrual of tech debt.
Our utmost priority is to provide a frictionless developer experience for our Gusto partner developers, whether they are building payroll software integrations or an embedded payroll experience. Figuring out how to gracefully introduce, manage, and deprecate an API version is perhaps one of the most significant challenges to this mission statement (credit is due to one simple, sneaky truth: change is hard). To help manage change internally, we’ve explicitly defined the lifecycle of an API into the following phases:
- Beta
- Stable
- Deprecated
- Sunsetted
- Retired

We may offer early or beta access to an API version, particularly if the included changes are at the top of the wishlist for partners. Once the version has passed our internal QA — during which we also dogfood the new version with our own API consumers, like Gusto Embedded Flows — it can then be stabilized, and ready for general public consumption.
Deprecated and sunsetted may sound like interchangeable statuses, but represent different stages of an API version’s end-of-service life.
When a Gusto API version is marked deprecated, we are actively suggesting that you stop using it; this may be due to existing security vulnerabilities, incompatibility with newer API versions, and/or new compliance requirements.

During the deprecation period of an API version, we tag this version as “Deprecated” in our public API docs. To ensure that our partners are notified and can plan ahead for version upgrades in their roadmap, we also dispatch extensive deprecation notices via a variety of channels: changelog posts, direct email outreach, dashboard alerts in your Developer Portal account, and API response headers.
A final sunsetted date is served with these deprecation notices. After the sunset date, requests made to that version will begin to fail with a 406 Not Acceptable
error. Note that when a version has passed its sunset date, we will remove public guidance and API documentation for that version.
Finally, when there are no longer any active consumers of an API version, we can officially retire the version and begin versioned code removal from our end. The benefits here are two-fold: our API consumers can reap the benefits of a more performant, secure, and usable API. On our end, we’re able to reduce our version maintenance overhead and clean up tech debt from our systems.
Lessons learned: A debug log
As we’ve rolled with the (breaking) changes in the past two years, we’ve gathered some insights on effective API versioning along the way.
Just as the best things in life are free, the best changes to an API are ones that aren’t breaking
In some cases, implementing a breaking change can be avoided in lieu of implementing a non-breaking one instead. We carefully evaluate whether a breaking change necessitates a whole new API version, and whether it’s possible to achieve the desired outcome without breaking the existing contract.*
*It’s worth caveating that this isn’t a universally applicable principle; making additive, non-breaking changes can run the risk of increasing the complexity of an API, which can in turn worsen developer ergonomics. Moreover, some API changes are widespread by nature; for example, in a previous API version 2022–11–01, we removed support for IDs (in favor of UUIDs) in request parameters and response attributes.
Each API version should represent a meaningful unit of change
When versioning for our API was introduced, we were (admittedly) overzealous about using it. While some of our now-sunsetted API versions encapsulated large system-wide updates, others contained small changes impacting a singular API. This resulted in the quick production of 10+ versions, ranging from major updates to minor tweaks.
For our partners using our oldest version, we empathize that upgrading a double-digit number of API versions can sound daunting. Going forward, we intend to group breaking changes into more meaningful units of change, so that each new API version represents a significant upgrade from the previous one.
Our first goal is to avoid making a new API version in the first place, but when an API version is absolutely necessary, our next goal is to make it well-scoped.
Opt into documentation-driven API design
We do our best to get our API design right the first time around. At Gusto Embedded, we practice documentation-driven design to help drive the construction of intuitive APIs.
The actual practice of documentation-driven design (DDD) varies from company to company; we tend to shave it down to: “If a feature is not documented, then it doesn’t exist, and if a feature is documented incorrectly, then it’s broken” (source). Before we build an API, we are intentional about how it looks and feels to a developer who leverages it.
The world of payroll, taxes, and compliance is complex. Our job here is to simplify and abstract it intuitively. Writing a documentation-driven spec before implementation helps us achieve this in our API design. When we contextualize an API design in its problem space, we learn about unknown unknowns and can quickly validate our assumptions with non-technical domain experts.
Occasionally, we share these documents externally with partners for preview and early feedback. This further tightens our feedback loop and helps us avoid fixing costly errors or inconsistencies down the line.
Make version management as cost-effective as possible
Versioning improves the developer experience by offering stable and long-term support for an API. At the same time, we’ve learned that maintaining many API versions can result in compounding internal maintenance costs over time:
- Increased code bloat
- Increased cognitive load (context switching depending on the API version)
- Increased code duplication, especially across an automated test suite
To manage our own maintenance burden, we refined our change management process such that when an API version moves to a new stage of its lifecycle (e.g. stable to deprecated), we incur only a consistent, fixed overhead.
We accomplished this in a few ways. For one, we created a prescriptive, self-validating Domain Service Language (DSL) in Ruby. Because each version’s logic is neatly encapsulated inside its respective block, this makes it easy to contribute, contextualize and eventually cut out the version (on retirement) without impacting other parts of the code.
# GET /employees/:employee_id
def show
api_versions do |version|
version.v2025_01_01 do
render json: { name: name } # no longer includes the `foo` field
end
version.vbase do
render json: { name: name, foo: :bar }
end
end
end
Additionally, we maintain a robust automated pipeline that helps us propagate breaking and non-breaking changes to our API reference, updating all versioned documentation (as well as our public Postman collections) from a single source. Write once, update many.
Communicate, communicate, communicate
Communication is crucial when an API version enters the deprecation process. To ensure partners are well-informed of changes, we send out multi-channel comms as a version enters its deprecation period. We offer and maintain a version upgrade guide to help partners allocate time for upgrades in their roadmaps. We believe that clear self-serve documentation helps encourage proactive (rather than reactive) adoption of new versions.
If possible, our team may automatically upgrade partners’ demo applications to next stable API version. If we cannot automatically upgrade an application, our partners can expect to receive communication from our Gusto Developer Relations team (developer@gusto.com) on next steps.
We recognize that each of our partners has unique business needs, distinct API requirements, and changing product roadmaps. As a critical piece of infra for our partners, our north star is to make our API updates as seamless and straightforward as possible, so that our partners can focus their time on scaling their own businesses.
If you’d like to build with our API, head over to our Developer Portal to get started.
Interested in working with us on building a best-in-class developer platform? We’re hiring!