---
id: changes
title: Changes and versioning
summary: What counts as a breaking change, how much notice you get, and what can appear without notice.
faces: ["public", "agent"]
section: shared
group: Changes
slug: changes
order: 80
---
# Changes and versioning

## Ninety days' notice before a breaking change

We give ninety days' notice before a breaking change to the documented API's wire shapes or to the browser
runtime.

These count as breaking, and each of them gets the notice:

- removing or renaming a field, a route, an error code or an event name;
- changing a field's type;
- adding a required request field, or making an optional one required;
- narrowing what is accepted, so that a value which worked stops working;
- making a field that was always present nullable or omittable;
- removing a value from a closed set.

The fifth one is worth saying out loud because it is the one most often argued about. A field that has always
carried a value becoming sometimes-null breaks every client that never checked, and "it was always
technically nullable" is an argument nobody wants to be making at the moment somebody's checkout is failing.

<!-- CHANNEL PLACEHOLDER: how the notice is delivered is an operator decision and is not yet published.
     This page does not claim a channel until it is answered. -->

## What can appear without notice

The promise means something only if this list is equally plain. These ship whenever they are ready:

- new optional request fields;
- new response fields;
- new routes;
- new event types;
- new values in an open set;
- anything behind a setting you have not turned on.

**Your client has to tolerate values it does not recognise** in an open set. That is not a disclaimer, it is
the other half of the promise: we would otherwise be unable to add a decline reason without breaking
everybody, and you would get no new reasons at all.

## Which sets are closed

A closed set is one you can safely write an exhaustive switch against, because we will give notice before
removing anything from it.

**Closed:** the API error codes, thirty-eight of them, and the SDK error codes, fourteen. Both are held
closed by tests rather than by intent — a code added on our side fails a check until it is declared, and the
count is asserted rather than described.

**Open:** decline codes and decline categories. A decline arrives with a code and a category from the card
networks and the acquirer, and new ones appear without us choosing them. Handle an unrecognised category as a
decline you should not retry — which is what the SDK itself does with one it does not know.

## The browser runtime is not pinned, and that is why this matters

You do not pin the loader. It is one script tag served from our CDN, with no version in the URL and no
upgrade step, so a change to the runtime reaches every merchant at once. That makes the runtime the place
where a breaking change would be most damaging and where this promise does the most work.

The npm package is different: you choose when to upgrade it, on your own clock.

## Where changes are announced

<!-- CHANNEL PLACEHOLDER: the same operator decision. Until it is answered this page says where changes are
     RECORDED, which is not the same as notice and must not be presented as if it were. -->

Every change to the API document is visible in the OpenAPI at [/openapi.json](/openapi.json), whose hash is
published in `/.well-known/ripper-docs.json`, so an automated check can tell that something changed without
reading anything.
