Generate a Ruby SDK from your OpenAPI spec
Octri reads your OpenAPI document and writes a Ruby gem with keyword arguments and typed models, publishes it to RubyGems under your own gem name, and rebuilds it every time the spec changes.
gem install acme_apiThe client they actually get
What your users write once the Ruby package is installed.
Two call-site shapes come out of the same spec. class-namespaced is the default: a client object whose operations are grouped by resource, which is the surface every widely used SDK ships and the one your own reference docs already assume. functions is the override: no client object, one configure call, and each operation called directly. It is a single setting, per language, and nothing else about the client changes with it.
Parameters are keyword arguments named after your spec’s properties, so a call reads the same as the request body it sends. Pagination is a plain Enumerator, and .lazy.select or .first(10) stop fetching as soon as they have enough.
Published with your RubyGems API key. The gem name also decides the module, so acme_api is required as "acme_api" and called as AcmeApi.
require "acme_api"
config = AcmeApi::ClientConfig.new
config.base_url = "https://api.acme.com/v1"
auth = AcmeApi::ClientAuthConfig.new
auth.bearer = ENV.fetch("ACME_TOKEN")
config.auth = auth
acme = AcmeApi::Acme.new(config)
# list_paginated returns an Enumerator, so each, lazy, first and take all work.
acme.users.list_paginated(limit: 100).each do |user|
puts user.id
end
invoice = acme.invoices.create(customer_id: "cus_123", amount_cents: 4900)require "acme_api"
AcmeApi::SdkConfig.configure(config)
AcmeApi::Methods::Users.list_users_paginated(limit: 100).each do |user|
puts user.id
endThe package at a glance
What a developer checks before adding the Ruby package to a project.
- Runs on
- Ruby 3.0+
- Transport
- Net::HTTP from the standard library
- Runtime dependencies
- None
- Models
- Plain Ruby classes with attribute readers
- Pagination
- Enumerator, lazy by construction
- Ships to
- RubyGems, installable with gem or Bundler
When a call fails
Each common status is its own subclass of SdkHttpError, so rescue clauses can be as narrow as the handler. A declared error model arrives decoded on e.data.
begin
acme.invoices.create(customer_id: "cus_123", amount_cents: 4900)
rescue AcmeApi::SdkRateLimitError
# 429 after the built-in retries. Re-enqueue the job.
retry_later
rescue AcmeApi::SdkHttpError => e
Rails.logger.error("acme failed status=#{e.status_code} request=#{e.request_id}")
endWhat you decide for Ruby
Ruby has no engine or data-model setting. Net::HTTP and plain classes keep the gem free of runtime dependencies.
Client style
class-namespaced (default), class, namespaced, functionsA client object with a reader per resource, the shape Ruby developers expect from AcmeApi::Acme.new. Functions are module methods under AcmeApi::Methods, sharing one thread-safe global config.
Argument style
positional (default), objectKeyword arguments named after the spec, with optional ones defaulting to nil. Object collects them into a request object.
Doc comments
full (default), minimalFull carries your spec’s prose into the comment above each method, so it reads well in an editor and in generated docs.
Method naming and namespacing work the same in every language: operations are grouped by your spec’s tags or by path, and the namespace word is dropped from the method name unless you ask to keep the operationId whole.
A yanked gem keeps its version number
RubyGems lets you yank a version, which stops new installs, but the number stays claimed and the name stays yours for good. A bad 1.0.0 means the fix ships as 1.0.1. Generate, read the package and run it against your staging API before the first push, since the first version is the one people pin.
acme_api- Not possible: Rename the gem
- Not possible: Reuse a yanked version number
- Still possible: Yank a version
The same in every generated client
The parts nobody wants to hand-write, and the parts a hand-written client usually skips.
- Retries with exponential backoff and jitter, on the statuses that mean "try again", with a per-attempt timeout.
- Idempotency keys on the methods that need them, so a retried write does not become two.
- Auth wired in from your spec’s security scheme, read from wherever your users keep secrets.
- Typed errors split by cause: a failed constraint, an HTTP status, a network failure and a timeout are four different things.
- Pagination that follows whichever contract your spec declares, cursor, offset, page number or next URL, without prefetching the collection.
- Server-sent events as a native stream, for the operations that stream.
- Error reporting to Octri’s monitoring, pre-wired and switched off until a consumer opts in.
Point it at your spec
Upload an OpenAPI or Swagger document, paste a URL, or connect the repository it lives in so a merge to main updates it.
Read the audit
The spec is scored out of 10 on what a generator can do with it, with every missing schema and colliding operationId named. 9 of the fourteen rules carry a button that writes the fix.
Shape the client
Client style, argument style, namespacing, auth, retries and pagination, set once for the project and overridden per language or per endpoint.
Generate and read it
The package is generated, compiled and handed to you as source you can read before anyone installs it.
Publish, then keep publishing
Release to the registry under your own account. Every later spec change regenerates the client, and the version number comes from your own document.
Or in another language
One spec, ten languages. The other nine are generated from the same document, with the same settings, at the same time.
Questions
Yes. It is a plain gem with no Rails coupling and no runtime dependencies, so it loads the same in a Rails app, a Sidekiq worker or a one-file script.