Rails has no built-in OpenAPI output — unlike FastAPI or Spring Boot, nothing ships a spec for free. The standard fix is rswag: you describe and test your API with an RSpec DSL, and a rake task turns those request specs into a real OpenAPI 3.0 document. From there, a Rails OpenAPI SDK is the same two-step problem as any other framework: get a clean spec out, then run it through a generator or a hosted pipeline like Sourced.
Getting an OpenAPI spec out of Rails with rswag
rswag doesn't read your controllers directly — it generates the spec from RSpec request specs that both document and test each operation. That's the trade: you write (or already have) integration tests, and the spec comes out synchronized with what actually passes.
| Step | Command / file | What it does |
|---|---|---|
| 1. Add the gems | gem 'rswag-api', gem 'rswag-ui', plus gem 'rspec-rails' and gem 'rswag-specs' in :development, :test (or just gem 'rswag' for all three) |
Installs spec generation, JSON/YAML serving, and the Swagger UI |
| 2. Install | rails g rswag:install |
Scaffolds spec/openapi_helper.rb and mounts the rswag-api/rswag-ui engines |
| 3. Write specs | spec/requests/*_spec.rb, using path, get/post/etc., parameter, response, run_test! |
Describes and tests each operation in one file |
| 4. Generate | rake rswag:specs:swaggerize (aliased rake rswag) |
Runs the specs and writes the OpenAPI file(s) declared in config.openapi_specs |
| 5. Serve | mount Rswag::Api::Engine => 'api-docs' and mount Rswag::Ui::Engine => 'api-docs' in config/routes.rb |
Spec JSON and an embedded Swagger UI both live at /api-docs |
A minimal spec looks like this:
# spec/requests/blogs_spec.rb
require 'openapi_helper'
describe 'Blogs API' do
path '/blogs' do
post 'Creates a blog' do
tags 'Blogs'
operationId 'createBlog'
consumes 'application/json'
parameter name: 'blog', in: :body, schema: {
type: :object,
properties: { title: { type: :string }, content: { type: :string } },
required: %w[title content]
}
response '201', 'blog created' do
let(:request_params) { { 'blog' => { title: 'foo', content: 'bar' } } }
run_test!
end
end
end
end
The output location is configurable in spec/openapi_helper.rb:
RSpec.configure do |config|
config.openapi_root = Rails.root.to_s + '/openapi'
config.openapi_specs = {
'v1/openapi.json' => { openapi: '3.0.1', info: { title: 'API V1', version: 'v1' } }
}
end
If you're on Grape instead of Rails controllers
Grape APIs don't use rswag's request-spec DSL. The equivalent there is grape-swagger: add gem 'grape-swagger', call add_swagger_documentation in your root API class, and it introspects your Grape routes directly — no separate spec files needed:
module API
class Root < Grape::API
format :json
mount API::Cats
mount API::Dogs
add_swagger_documentation # default mount_path: /swagger_doc
end
end
The gotcha: as of this writing, grape-swagger generates Swagger / OpenAPI Spec 2.0, not OpenAPI 3.x. Most modern codegen and hosted-docs tooling — Sourced included — expects OpenAPI 3.0 or 3.1 input, so a Grape spec needs an upgrade pass first. See OpenAPI 3.1 vs Swagger 2.0: upgrade first before feeding a grape-swagger output into any 3.x-only pipeline.
The gotcha: operationId is opt-in, not automatic
rswag's DSL supports an explicit operationId 'createBlog' call inside an operation block, but nothing forces you to set one. Skip it and the field is simply absent from the generated spec — which is spec-legal but bad for codegen: generators fall back to building a method name from the HTTP verb and path, so api.createBlog(...) becomes something like api.blogsPost(...). See how to fix missing operationId errors for the naming pattern that survives regeneration, and add operationId to every operation block before you generate a public SDK from it.
From a Rails spec to hosted docs and typed SDKs
Once rake rswag produces a clean file — real operationIds, referenced (not inline) schemas — turning it into an installable SDK is a separate step. With Sourced:
- Point Sourced at your repo or the generated file. Connect from GitHub, or upload the
openapi.jsonthatrake rswagwrote directly. - Preview the TypeScript and Python SDKs. Sourced renders both before anything publishes, so a verb-and-path fallback method name shows up in the preview, not a customer's autocomplete.
- Get hosted docs and an llms.txt from the same spec. No separate docs deploy — the hosted docs preview and llms.txt come from the same pass as the SDKs.
- Publish on your schedule. Free unlimited previews and up to 2 hosted noindex docs review sites and one hosted MCP server, no credit card, cover steps 1-3.
Comparison: rswag vs. a full SDK pipeline
| Need | rswag gives you | What it doesn't give you |
|---|---|---|
| A generated OpenAPI spec | Yes, from RSpec request specs | Nothing — this part is real and free |
| Tests that stay synced with docs | Yes — the DSL runs your specs to produce the spec | Nothing to add here |
| Interactive API browsing | Embedded Swagger UI at /api-docs |
A hosted docs site for customers |
| A typed client SDK | Nothing built in | Generated TypeScript or Python SDK |
| A diff before a breaking release | Nothing built in | A compatibility report |
| Agent-readable docs | Nothing built in | llms.txt derived from the spec |
OSS generators for other languages
Sourced's generated SDKs focus on TypeScript and Python today — the two ecosystems where teams publish first, not Ruby. For the SDK itself, a clean rswag or grape-swagger (upgraded to 3.x) spec works with the standard per-language OpenAPI Generator ecosystem — Ruby, Go, Java, PHP, C#, Kotlin, Rust, and Swift all have their own walkthroughs. The five-minute generate-an-SDK guide covers running a local generator against any spec, rswag's included. Sourced still has a role on that same spec: hosted docs, llms.txt, and validation, independent of which language generates the client.
Other backend frameworks with their own spec-extraction paths: FastAPI, NestJS, Express, Django, Spring Boot, Laravel, and .NET.
Honest scope: when rswag alone is enough
If your Rails API only has internal consumers who already read Ruby, the embedded Swagger UI at /api-docs plus a thin Faraday wrapper is a reasonable stopping point. rswag is also only as good as your spec coverage: an endpoint with no request spec produces no documentation, since generation is driven by the tests you've written, not by scanning controllers. Reach for full SDK generation once you have external customers, a second language to support, or a docs site that needs to outlive your test suite's Swagger UI.
FAQ
Does Rails generate OpenAPI automatically?
No. Rails has no built-in OpenAPI output. rswag is the standard way to get one: it generates the spec from RSpec request specs via a rake task, rather than introspecting your routes or controllers directly.
What's the rake task to generate the OpenAPI file?
rake rswag:specs:swaggerize, aliased as rake rswag. It runs your RSpec request specs and writes the file(s) configured in config.openapi_specs inside spec/openapi_helper.rb.
Does rswag support OpenAPI 3.1?
rswag documents itself as "OpenAPI 3.0 compatible." If your downstream generator or docs tool needs 3.1 specifically, validate the emitted document rather than assuming — see OpenAPI 3.1 vs 3.0 for what actually changes between versions.
I'm on Grape, not Rails controllers — does rswag still work?
No, rswag's DSL is built for rspec-rails request specs. Grape APIs use grape-swagger instead, which introspects your Grape routes directly. Its output is Swagger 2.0, so plan an upgrade step before feeding it to OpenAPI 3.x-only tooling.
Why does my generated SDK have method names like blogsPost instead of createBlog?
That's the generator's fallback when operationId is missing from the spec. rswag supports an explicit operationId 'createBlog' call inside each operation block — it's just not automatic. Add it to every operation before generating a public SDK.
Can rswag document an endpoint without a request spec?
No. Generation is driven entirely by the RSpec request specs you write, with no separate controller-scanning step. An endpoint without a spec simply doesn't appear in the generated file.
Ship it
Once rake rswag produces a spec with real operationIds and referenced schemas, you have a real Rails OpenAPI SDK source to work with. Start free on Sourced to preview a TypeScript and Python SDK plus hosted docs from that spec in one pass, or run it through the OpenAPI validator first to catch gaps before you generate anything.