Skip to content

Ruby

A guide to using Soxom-generated Ruby SDKs. The generated gem targets Ruby 3.1+, ships idiomatic classes with YARD documentation, and verifies webhook signatures with constant-time comparison. The module name is the PascalCase of the gem name; the examples below assume a gem named acme_sdk (module AcmeSdk).

The gem name is whatever you set in targets.ruby.gem_name. Add it to your Gemfile:

gem "acme_sdk"
Terminal window
bundle install
# or
gem install acme_sdk
require "acme_sdk"
# Credentials are read from the matching env var when the option is omitted.
client = AcmeSdk::Client.new(api_key: ENV["ACME_SDK_API_KEY"])
user = client.users.get("user-123")
puts user.email

Configure globally and reuse a shared client, or construct per-client:

AcmeSdk.configure do |config|
config.api_key = ENV["ACME_SDK_API_KEY"]
config.base_url = "https://api.acme.com"
config.timeout = 60 # seconds; read/total timeout
config.open_timeout = 30 # seconds; connection timeout
config.max_retries = 3
config.logger = Logger.new($stdout)
end
client = AcmeSdk.client
# Per-client overrides take precedence over the global config:
client = AcmeSdk::Client.new(api_key: "...", timeout: 10)

The constructor option and the env-var name are derived from the SDK’s configured auth scheme. <PREFIX> below is the upcased gem name (e.g. acme_sdkACME_SDK).

# Sent as `Authorization: Bearer <token>`
client = AcmeSdk::Client.new(api_key: ENV["ACME_SDK_API_KEY"])
client = AcmeSdk::Client.new(api_key: ENV["ACME_SDK_API_KEY"])

The injection location is fixed at generation time from the OpenAPI security scheme.

client = AcmeSdk::Client.new(
auth: AcmeSdk::Auth::ClientCredentialsSource.new(
token_url: "https://auth.acme.com/oauth/token",
client_id: ENV["ACME_CLIENT_ID"],
client_secret: ENV["ACME_CLIENT_SECRET"],
scopes: %w[read write]
)
)

The token is fetched lazily on the first request, cached until shortly before expiry, and refreshed automatically; a 401 triggers a single forced refresh and retry.

Operations are mounted under resource and subresource namespaces as configured in soxom.yaml:

client.users.list
client.users.get("user-123")
client.users.settings.update("user-123", theme: "dark")

List endpoints return an auto-paginating enumerator — iterate it and the SDK fetches each page transparently:

client.users.list.each { |user| puts user.email } # walks every page
client.users.list.lazy.first(50) # bounded — stops early

Both cursor and offset schemes are supported, selected by pagination.default_type in soxom.yaml.

Operations marked streaming return an enumerator over Server-Sent Events, parsed lazily from the response body:

client.chat.completions.create(
model: "gpt-4",
messages: [{ role: "user", content: "Hello" }],
stream: true
).each do |event|
print event.choices.first.delta.content
end

Every error descends from AcmeSdk::Errors::Error, so a single rescue catches anything from the SDK. The hierarchy:

AcmeSdk::Errors::Error
├── ConfigurationError
├── APIError
│ ├── BadRequestError (400)
│ ├── AuthenticationError (401)
│ ├── PermissionError (403)
│ ├── NotFoundError (404)
│ ├── ConflictError (409)
│ ├── UnprocessableEntityError (422)
│ ├── RateLimitError (429)
│ └── ServerError (5xx)
├── ConnectionError
├── DeserializationError
└── SignatureVerificationError
begin
client.users.get("user-123")
rescue AcmeSdk::Errors::RateLimitError => e
sleep(e.retry_after || 30)
retry
rescue AcmeSdk::Errors::AuthenticationError
# refresh token / re-authenticate
rescue AcmeSdk::Errors::APIError => e
warn "API error #{e.http_status}: #{e.message} (request id: #{e.request_id})"
end

Every APIError carries #http_status, #code, #message, #request_id, #response_body, and #errors.

GET, HEAD, OPTIONS, and DELETE are retried automatically on connection errors and on HTTP 5xx, 408, and 429, with exponential backoff and jitter. POST, PATCH, and PUT retry only when an Idempotency-Key header is set — the SDK auto-injects one via SecureRandom.uuid for those methods. A parseable Retry-After header is always honored. Tune the budget with config.max_retries.

When your OpenAPI 3.1 spec declares top-level webhooks, the SDK emits a typed parser with signature verification:

event = AcmeSdk::Webhooks.parse(
payload: request.raw_post,
signature: request.headers["X-Signature"],
secret: ENV["WEBHOOK_SECRET"]
)
case event
when AcmeSdk::Webhooks::UserCreatedEvent
provision(event.data)
end

Webhooks.verify_signature uses OpenSSL.fixed_length_secure_compare for constant-time comparison, with a default tolerance of 300 seconds.

Pass any Logger-compatible object via config.logger; the SDK redacts known secret keys from emitted logs. When ActiveSupport::Notifications is available in the host app, the SDK publishes a request.<gem_name> event per HTTP exchange for instrumentation.

targets:
ruby:
gem_name: "acme_sdk" # RubyGems name; default is snake_case of sdk.name
generator_version: "0.1.0" # pin the generator version

See the soxom.yaml reference for the complete option list.

  • Ruby 3.1, 3.2, 3.3, 3.4
Terminal window
bundle install
gem build acme_sdk.gemspec
gem push acme_sdk-<version>.gem