Need help with api-versions?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

130 Stars 25 Forks MIT License 169 Commits 6 Opened issues


A simple gem to manage your Rails API routing endpoints.

Services available


Need anything else?

Contributors list


Build Status Gem Version Coverage Status Code Climate


  • Rails 5.0+
  • Ruby 2.2+

api-versions is a Gem to help you manage your Rails API endpoints.

api-versions is very lightweight. It adds a generator and only one method to the Rails route mapper.

It helps you in three ways:
  • Provides a DSL for versioning your API in your routes file, favoring client headers vs changing the resource URLs.
  • Provides methods to cache and retrieve resources in your routes file to keep it from getting cluttered
  • Provides a generator to bump your API controllers to the next version, while inheriting the previous version.

See below for more details on each of these topics

Assumptions api-versions makes:

  • You want the client to use headers to specify the API version instead of changing the URL. (
    header of
    for example)
  • You specify your API version in whole integers. v1, v2, v3, etc. If you need semantic versioning for an API you're likely making too many backwards incompatible changes. API versions should not change all that often.
  • Your API controllers will live under the
    directory. For example


In your Gemfile:

gem "api-versions", "~> 1.0"

Versions are specified by header, not by URL

A lot of APIs are versioned by changing the URL.
for example. But is someresource different from version 1 to version 2? It is likely the same resource, it is simply the interface that is changing. api-versions prefers the URLs stay the same. `
need not ever change (so long as the resource exists). The client specifies how it wants to interface with
this resource with the
header. So if the client wants version 2 of the API, the
header might look like this:
application/vnd.myvendor+json;version=2`. A complete example is below.


api-versions provides a (very) lightweight DSL for your routes file. Everything having to do with your routes API lives in the api block. This DSL helps you version your API as well as providing a caching mechanism to prevent the need of copy/pasting the same resources into new versions of the API.

For example:

In your routes.rb file:

  # You can leave default_version out, but if you do the first version used will become the default
  api vendor_string: "myvendor", default_version: 1 do
    version 1 do
      cache as: 'v1' do
        resources :authorizations

version 2 do
  inherit from: 'v1'


rake routes
api_authorizations      GET    /api/authorizations(.:format)          api/v1/authorizations#index
                        POST   /api/authorizations(.:format)          api/v1/authorizations#create
new_api_authorization   GET    /api/authorizations/new(.:format)      api/v1/authorizations#new
edit_api_authorization  GET    /api/authorizations/:id/edit(.:format) api/v1/authorizations#edit
 api_authorization      GET    /api/authorizations/:id(.:format)      api/v1/authorizations#show
                        PUT    /api/authorizations/:id(.:format)      api/v1/authorizations#update
                        DELETE /api/authorizations/:id(.:format)      api/v1/authorizations#destroy
                        GET    /api/authorizations(.:format)          api/v2/authorizations#index
                        POST   /api/authorizations(.:format)          api/v2/authorizations#create
                        GET    /api/authorizations/new(.:format)      api/v2/authorizations#new
                        GET    /api/authorizations/:id/edit(.:format) api/v2/authorizations#edit
                        GET    /api/authorizations/:id(.:format)      api/v2/authorizations#show
                        PUT    /api/authorizations/:id(.:format)      api/v2/authorizations#update
                        DELETE /api/authorizations/:id(.:format)      api/v2/authorizations#destroy

Then the client simply sets the Accept header

. If no version is specified, the default version you set will be assumed. You'll of course still need to copy all of your controllers over (or bump them automatically, see below), even if they haven't changed from version to version. At least you'll remove a bit of the mess in your routes file.

A more complicated example:

  api vendor_string: "myvendor", default_version: 1 do
    version 1 do
      cache as: 'v1' do
        resources :authorizations, only: :create
        resources :foo
        resources :bar

version 2 do
  cache as: 'v2' do
    inherit from: 'v1'
    resources :my_new_resource

# V3 has everything in V2, and everything in V1 as well by virtue of V1 being cached in V2.
version 3 do
  inherit from: 'v2'


And finally

rake routes
api_authorizations        POST   /api/authorizations(.:format)           api/v1/authorizations#create
            api_foo_index GET    /api/foo(.:format)                      api/v1/foo#index
                          POST   /api/foo(.:format)                      api/v1/foo#create
              new_api_foo GET    /api/foo/new(.:format)                  api/v1/foo#new
             edit_api_foo GET    /api/foo/:id/edit(.:format)             api/v1/foo#edit
                  api_foo GET    /api/foo/:id(.:format)                  api/v1/foo#show
                          PUT    /api/foo/:id(.:format)                  api/v1/foo#update
                          DELETE /api/foo/:id(.:format)                  api/v1/foo#destroy
            api_bar_index GET    /api/bar(.:format)                      api/v1/bar#index
                          POST   /api/bar(.:format)                      api/v1/bar#create
              new_api_bar GET    /api/bar/new(.:format)                  api/v1/bar#new
             edit_api_bar GET    /api/bar/:id/edit(.:format)             api/v1/bar#edit
                  api_bar GET    /api/bar/:id(.:format)                  api/v1/bar#show
                          PUT    /api/bar/:id(.:format)                  api/v1/bar#update
                          DELETE /api/bar/:id(.:format)                  api/v1/bar#destroy
                          POST   /api/authorizations(.:format)           api/v2/authorizations#create
                          GET    /api/foo(.:format)                      api/v2/foo#index
                          POST   /api/foo(.:format)                      api/v2/foo#create
                          GET    /api/foo/new(.:format)                  api/v2/foo#new
                          GET    /api/foo/:id/edit(.:format)             api/v2/foo#edit
                          GET    /api/foo/:id(.:format)                  api/v2/foo#show
                          PUT    /api/foo/:id(.:format)                  api/v2/foo#update
                          DELETE /api/foo/:id(.:format)                  api/v2/foo#destroy
                          GET    /api/bar(.:format)                      api/v2/bar#index
                          POST   /api/bar(.:format)                      api/v2/bar#create
                          GET    /api/bar/new(.:format)                  api/v2/bar#new
                          GET    /api/bar/:id/edit(.:format)             api/v2/bar#edit
                          GET    /api/bar/:id(.:format)                  api/v2/bar#show
                          PUT    /api/bar/:id(.:format)                  api/v2/bar#update
                          DELETE /api/bar/:id(.:format)                  api/v2/bar#destroy
api_my_new_resource_index GET    /api/my_new_resource(.:format)          api/v2/my_new_resource#index
                          POST   /api/my_new_resource(.:format)          api/v2/my_new_resource#create
  new_api_my_new_resource GET    /api/my_new_resource/new(.:format)      api/v2/my_new_resource#new
 edit_api_my_new_resource GET    /api/my_new_resource/:id/edit(.:format) api/v2/my_new_resource#edit
      api_my_new_resource GET    /api/my_new_resource/:id(.:format)      api/v2/my_new_resource#show
                          PUT    /api/my_new_resource/:id(.:format)      api/v2/my_new_resource#update
                          DELETE /api/my_new_resource/:id(.:format)      api/v2/my_new_resource#destroy
                          POST   /api/authorizations(.:format)           api/v3/authorizations#create
                          GET    /api/foo(.:format)                      api/v3/foo#index
                          POST   /api/foo(.:format)                      api/v3/foo#create
                          GET    /api/foo/new(.:format)                  api/v3/foo#new
                          GET    /api/foo/:id/edit(.:format)             api/v3/foo#edit
                          GET    /api/foo/:id(.:format)                  api/v3/foo#show
                          PUT    /api/foo/:id(.:format)                  api/v3/foo#update
                          DELETE /api/foo/:id(.:format)                  api/v3/foo#destroy
                          GET    /api/bar(.:format)                      api/v3/bar#index
                          POST   /api/bar(.:format)                      api/v3/bar#create
                          GET    /api/bar/new(.:format)                  api/v3/bar#new
                          GET    /api/bar/:id/edit(.:format)             api/v3/bar#edit
                          GET    /api/bar/:id(.:format)                  api/v3/bar#show
                          PUT    /api/bar/:id(.:format)                  api/v3/bar#update
                          DELETE /api/bar/:id(.:format)                  api/v3/bar#destroy
                          GET    /api/my_new_resource(.:format)          api/v3/my_new_resource#index
                          POST   /api/my_new_resource(.:format)          api/v3/my_new_resource#create
                          GET    /api/my_new_resource/new(.:format)      api/v3/my_new_resource#new
                          GET    /api/my_new_resource/:id/edit(.:format) api/v3/my_new_resource#edit
                          GET    /api/my_new_resource/:id(.:format)      api/v3/my_new_resource#show
                          PUT    /api/my_new_resource/:id(.:format)      api/v3/my_new_resource#update
                          DELETE /api/my_new_resource/:id(.:format)      api/v3/my_new_resource#destroy


The api-versions gem provides a Rails generator called

. This generator will go through all of your API controllers and find the highest version number and bump all controllers with it up to the next in sequence.

If for example you have a controller

it will create
and inside:
class Api::V2::AuthorizationsController < Api::V1::AuthorizationsController

So instead of copying your prior version controllers over to the new ones and duplicating all the code in them, you can redefine specific methods, or start from scratch by removing the inheritance.

Passing Rails Routing options

The api-versions routing DSL will pass any options that are regularly accepted by Rails' own routing DSL. For example, if you are using an api subdomain and don't need your paths prefixed with

, you can override it as you normally would:
api vendor_string: 'myvendor', default_version: 1, path: '' do
  version 1 do
    cache as: 'v1' do
      resources :foo

Then a

rake routes
would show your desires fulfilled:
                              GET    /foo(.:format)                      api/v1/foo#index
                              POST   /foo(.:format)                      api/v1/foo#create
                              GET    /foo/new(.:format)                  api/v1/foo#new
                              GET    /foo/:id/edit(.:format)             api/v1/foo#edit
                              GET    /foo/:id(.:format)                  api/v1/foo#show
                              PUT    /foo/:id(.:format)                  api/v2/foo#update
                              DELETE /foo/:id(.:format)                  api/v2/foo#destroy

It's also possible to configure route's namespace with

option (if you want to remove namespacing at all just pass a blank string):
api vendor_string: 'myvendor', default_version: 1, namespace: 'auth_api' do
  version 1 do
    cache as: 'v1' do
      resources :foo, only: :index

Then a

rake routes
would show your desires fulfilled:
                              GET    /auth_api/foo(.:format)             auth_api/v1/foo#index


Because controller tests will not go through the routing constraints, you will get routing errors when testing API controllers.

To avoid this problem you can use request/integration tests which will hit the routing constraints.

To do this in RSpec, you should only need to move your spec files from

# spec/requests/api/v1/widgets_controller_spec.rb
require 'spec_helper'

describe Api::V1::WidgetsController do describe "GET 'index'" do it "should be successful" do get '/api/widgets', {}, 'HTTP_ACCEPT' => 'application/vnd.myvendor+json; version=1' response.should be_success end end end

For Test::Unit, inherit from ActionDispatch::IntegrationTest:

# test/integration/api/v1/widgets_controller_test.rb
require 'test_helper'

class Api::V1::WidgetsControllerTest < ActionDispatch::IntegrationTest test "GET 'index'" do get '/api/widgets', {}, 'HTTP_ACCEPT' => 'application/vnd.myvendor+json; version=1' assert_response 200 end end

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.