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.

Install from RubyGems
gem install acme_api

The 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.

Client object (default)
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)
Free functions
require "acme_api"

AcmeApi::SdkConfig.configure(config)

AcmeApi::Methods::Users.list_users_paginated(limit: 100).each do |user|
  puts user.id
end

The 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.

Typed errors
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}")
end

What 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, functions

    A 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), object

    Keyword arguments named after the spec, with optional ones defaulting to nil. Object collects them into a request object.

  • Doc comments

    full (default), minimal

    Full 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.

RubyGems gem name
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.
  1. 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.

  2. 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.

  3. Shape the client

    Client style, argument style, namespacing, auth, retries and pagination, set once for the project and overridden per language or per endpoint.

  4. Generate and read it

    The package is generated, compiled and handed to you as source you can read before anyone installs it.

  5. 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.

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.