Skip to content

OpenAPI Guide

Roman Samoilov edited this page Aug 25, 2025 · 9 revisions

Playground

Use OpenAPI Playground to see what documentation Rage::OpenAPI generates without having to set up a project. Experiment with various tags and serializer combinations to understand how Rage::OpenAPI works.

Usage

Rage::OpenAPI is a lightweight solution that allows you to create and customize an OpenAPI specification for your application. To enable OpenAPI specification for an application, update your config.ru file to mount the Rage::OpenAPI component at the URL of your choice. For example:

map "/publicapi" do
  run Rage::OpenAPI.application
end

Alternatively, Rage::OpenAPI can also be mounted in config/routes.rb:

Rage.routes.draw do
  mount Rage::OpenAPI.application, at: "/publicapi"
end

Consider we have the following route definitions:

Rage.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :users, only: %i[index show create]
      resources :photos, only: :index
    end

    namespace :v2 do
      resources :users, only: :show
    end
  end
end

Launch the server using the rage s command and visit localhost:3000/publicapi to see the specification:

191420

Customization

Currently, our specification is not very useful. All we do is display a list of endpoints the application exposes. Sometimes, this is what we are looking for, but most of the time, what we also want is to add some information to our specification and turn a faceless list of API endpoints into actual documentation.

One of the main concepts of Rage::OpenAPI is limited scope. We believe that complex DSLs that attempt to implement the whole OpenAPI specification make no sense - at some point it becomes easier to manually create an OpenAPI document instead of learning a DSL, which will then create the OpenAPI document for you.

Instead, Rage::OpenAPI aims to enhance the developer experience by focusing on specific tasks that API developers face. With Rage::OpenAPI, customizations to your API specification can be made using YARD-style tags placed directly on action definitions inside controllers. Not only is this a very simple and natural way to document APIs, but it also allows you to simultaneously document your code, too.

There are two types of tags. Some tags, like @description, can only be attached to an action within a controller. Others, like @deprecated, can also be attached to a class. This way, all actions in the class together with all actions in child classes will become deprecated.

Tags

Summary

The simplest customization you can make is to add a summary - comment on your action to add a one-line summary to the specification.

class Api::V1::UsersController < ApplicationController
  # Returns the list of all active non-admin users.
  def index
  end
end

Reload the page to see the updated specification:

121852

@description

@description allows you to add a longer description for documentation purposes. The tag supports markdown, and, similar to other text tags, can be multi-line.

class Api::V1::UsersController < ApplicationController
  # Returns the list of all active non-admin users.
  # @description The API endpoint provides access to a list of
  #   registered users within the system. Only non-admin
  #   users are returned by this endpoint.
  def show
  end
end

@version/@title

The @version and @title tags can only be used once per API specification and allow setting the title and version properties on the info object.

@internal

The @internal tag allows you to leave comments for other developers. Rage::OpenAPI ignores this tag and doesn't put its contents into the specification:

class Api::V1::UsersController < ApplicationController
  # @internal All changes to this action must first be approved by a principal engineer.
  def create
  end
end

@deprecated

With @deprecated you can mark an action as deprecated:

class Api::V1::UsersController < ApplicationController
  def index
  end

  # @deprecated
  def create
  end
end

Additionally, you can mark the whole class as deprecated. In such a case, make sure to place the tag inside the class without attaching it to an action:

class Api::V1::UsersController < ApplicationController
  # @deprecated

  def index
  end

  def create
  end
end

Authentication

Usually, Ruby developers place authentication logic in the before_action callbacks. With Rage::OpenAPI, you can specify the name of the before_action used for authentication using the @auth tag. For instance:

class ApplicationController
  before_action :authenticate_by_token
  # @auth authenticate_by_token

  private

  def authenticate_by_token
    ...
  end
end

In this example, Rage::OpenAPI will follow all applications of the authenticate_by_token callback, take into account the skip_before_action calls, and apply the corresponding security scheme to all necessary controller actions. By default, the scheme that corresponds with the authenticate_with_http_token method is used:

type: http
scheme: bearer

You can customize the security scheme definition by inlining a corresponding YAML entry:

class ApplicationController
  before_action :authenticate_by_token

  # @auth authenticate_by_token
  #   type: apiKey
  #   in: header
  #   name: X-API-Key

  private

  def authenticate_by_token
    ...
  end
end

It is also possible to have multiple security schemes:

class ApplicationController
  before_action :authenticate_by_user_token
  before_action :authenticate_by_service_token

  # @auth authenticate_by_user_token
  # @auth authenticate_by_service_token
end

The optional second argument can be used to change the name of the security scheme:

class ApplicationController
  before_action :authenticate_by_token

  # @auth authenticate_by_token UserAuth
end

Responses

With Rage::OpenAPI, there are three ways to specify the response schema.

For simple use cases, you can inline a YAML definition of the response into the tag:

class Api::V1::UsersController < ApplicationController
  # @response { id: Integer, full_name: String }
  def show
  end
end

For the maximum flexibility, you can use shared references. Finally, Rage::OpenAPI can automatically build the response schema based on ActiveRecord models or Alba resources.

For example, let's say we have the following Alba resource:

class UserResource
  include Alba::Resource

  root_key :user
  attributes :id, :name, :email
end

Pass the resource to the @response tag to have Rage::OpenAPI build the response schema:

class Api::V1::UsersController < ApplicationController
  # @response UserResource
  def show
  end
end

The response schema Rage::OpenAPI builds will look like this:

schema:
  type: object
  properties:
    user:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        email:
          type: string

Most Alba features, including associations, key transformations, inheritance, and typed attributes, are supported. Use the [] or Array<> to specify that the endpoint returns a collection:

class Api::V1::UsersController < ApplicationController
  # @response Array<UserResource>
  def index
  end
end

Rage::OpenAPI also takes namespaces into account, so in case you have the Api::V1::UserResource and Api::V2::UserResource classes, referencing UserResource from Api::V2::UsersController will pick Api::V2::UserResource.

Additionally, in case the endpoint can have different responses, you can optionally pass the response code in the first argument:

class Api::V1::UsersController < ApplicationController
  # @response 200 UserResource
  # @response 404 { error_code: String }
  def show
  end
end

Response tags can also be global, in which case they will be applied to all actions of all child controllers:

class Api::V1::BaseController < ApplicationController
  # @response 404 { status: "NOT_FOUND" }
  # @response 500 { status: "ERROR", message: String }
end

Requests

The @request tag is pretty similar to the @response tag - it can accept an inline YAML, a shared reference, or an ActiveRecord model. In the latter case, Rage::OpenAPI will strip all internal attributes from the request, e.g. id, created_at, or type.

class Api::V1::UsersController < ApplicationController
  # @request User
  def create
  end
end

Parameters

The @param tag can be used to describe query parameters:

class Api::V1::UsersController < ApplicationController
  # @param account_id The account the records are attached to
  def index
  end
end

By default, parameters are required. To mark a parameter as optional, use a ? after the parameter name:

class Api::V1::UsersController < ApplicationController
  # @param created_at? Filter records by creation date
  def index
  end
end

You can also specify the type of the parameter by enclosing it in {} and providing it as a second argument:

class Api::V1::UsersController < ApplicationController
  # @param is_active {Boolean} Filter records by active status
  def index
  end
end

Shared References

Rage::OpenAPI allows you to have a file with shared OpenAPI definitions that you can reference from the tags like @response, @request, or @param. The file can either be in the YAML or JSON format and should have the root components key.

For example, create the config/openapi_components.yml file with the following content:

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string

Then, reference the component inside the controller:

class Api::V1::UsersController < ApplicationController
  # @response #/components/schemas/User
  def show
  end
end

Limiting Visibility

There are two options you can use to limit the visibility of specific endpoints. The first is the @private tag. You can use @private to either hide single actions:

class Api::V1::UsersController < ApplicationController
  def show
  end

  # @private
  def create
  end
end

or the entire class:

class Api::V1::UsersController < ApplicationController
  # @private

  def show
  end

  def create
  end
end

Additionally, you can limit Rage::OpenAPI to a specific namespace by passing it into the Rage::OpenAPI.application call:

map "/publicapi" do
  run Rage::OpenAPI.application(namespace: "Api::V2")
end

With this option, you can even have different specifications for different use cases:

map "/publicapi" do
  run Rage::OpenAPI.application(namespace: "Api::Public")
end

map "/internalapi" do
  use Rack::Auth::Basic
  run Rage::OpenAPI.application(namespace: "Api::Internal")
end

Tag Resolver

Rage::OpenAPI groups all endpoints by tags that it derives from controller names. For example, all actions from the Api::V1::UsersController controller will be grouped under the v1/Users tag.

You can customize this behavior with a custom tag resolver:

Rage.configure do
  config.openapi.tag_resolver = proc do |controller, action, default_tag|
    # ...
  end
end

A custom tag resolver is a proc that is passed to the config.openapi.tag_resolver configuration. It is invoked for every endpoint Rage::OpenAPI processes and accepts three arguments:

  • the controller class;
  • the action symbol;
  • the original tag generated by Rage::OpenAPI;

The resolver should return a tag (or an array of tags) that will be assigned to the endpoint.

Clone this wiki locally