• Content

V3 Upgrade Guide

The purpose of this document is to provide you with the guidance necessary to upgrade from version 2 to version 3 of the API. It assumes that you have a comfortable understanding of V2 and your current integration.

Table of Contents

HTTP API Differences


V3 Clients are still authenticated by API key using HTTP basic auth. You no longer need a subdomain as the site is now to be specified per-request [see Scoping by Site].


One major change you will notice is the transport layer is now JSON. We no longer support XML and we no longer use “links”.


The API is now versioned by date rather than the previous major.minor format. The date will roughly correspond to the GA date of the version and will follow the format vYYYY-MM-DD. Ex: v2018-08-09.

Breaking Changes

Due to certain constraints with the previous ecosystem, versioning in V2 was very conservative. Instead of bumping the version on every change, V3 will only change versions when the API requires breaking changes. We will also provide a policy explaining our definition of breaking changes. This will allow us greater flexibility in changing the API meaning much fewer forced upgrades for our customers.


Since we no longer utilize “links” in our resources, it’s up to you to know which identifiers to use when calling the servers. There are two types of identifiers in V3:

  1. Druuids
  2. Friendly Ids

The Druuid is a base36 encoded integer and can be thought of as the internal identifier for a resource. The Friendly Id is a unique property on the resource which can also be used to identify the resource. It can be considered an external id for a resource. It’s often provided by you, and is often human-readable.

An example, consider a Site resource:

  "id": "3w5e11264sgsg",
  "subdomain": "mysite",
  // ...

You can use one of the two formats anywhere a site can be referenced. Consider the endpoint to fetch a Site:

GET /sites/{site_id}

You can use the Druuid (id):

GET /sites/3w5e11264sgsg

or a friendly id (use format {property_name}-{property_value})

GET /sites/subdomain-mysite

All ids are interchangeable, but we recommend you use a friendly id wherever possible as it is often the identifier you will store on your system. The Druuid can be useful for debugging or transient usage.

Scoping by Site

The API no longer assumes that one client will only be handling one site. We try to provide an abstraction in the client libraries to allow you continue this way of thinking if that’s how your system is setup. On the API level, the effect is that all urls are now scoped by the site you wish to operate on. Example:

GET /sites/subdomain-mysite/accounts/code-myaccountcode

This can be powerful if you have multiple sites as it allows you to reuse one TCP connection to perform operations across multiple sites.

Client Library Differences

Explicitly Managed Clients

Based on the assumption that one client would operate on one site, clients in V2 were statically and globally initialized for convenience. The underlying connection and request / response cycle was also abstracted from the programmer. Example with the V2 ruby client:

# client initialized globally for the whole process
Recurly.subdomain = "mysite"
Recurly.api_key   = "fad7b04dc787ab7809e8d90e9df73cf7"

# client, request, and response are hidden from the programmer
account = Recurly::Account.find('myaccountcode')
account.first_name = "Benjamin"

This had a few unintended consequences:

  1. Network calls are hidden from view. This results in accidental performance problems and makes testing difficult.
  2. Network calls are spread out amongst different objects and modules.
  3. Changing the site is not thread-safe.

With V3, the programmer has to explicitly initialize and manage the client, but it eliminates the problems with V2.

# you must explicitly create and manage this connection now
client = Recurly::Client.new(api_key: "fad7b04dc787ab7809e8d90e9df73cf7")

id = "code-myaccountcode"
account = client.get_account(account_id: id)
# overwrite the account with new account
account = client.update_account(account_id: id, body: { first_name: "Benjamin" })

Inert Resources

Another related problem with V2 was a mix of data and behavior. This had a couple negative effects:

  1. Network calls are opaque (you have no idea what will be in them).
  2. Requires internal state tracking to build up a request which has subtle bugs.

With V3, the resources you get back from the server have no behavior. They only have properties. Instead of mutating the resources and “syncing” their state to the server, you must now send an explicit request to the server and get back a new resource. Consider the account example again:

id = "code-myaccountcode"
account = client.get_account(account_id: id)
# If i want to change the first name, ask the server to
# change it and get back a new copy of what is on the server
account = client.update_account(account_id: id, body: { first_name: "Benjamin" })

This makes it very clear what the underlying JSON request will look like, and removes any state tracking bugs that you may have experienced with the previous approach.