> This page is for SDK, version Legacy.
> For other versions, use one of these documentation indexes:
> - Current (default): https://buildwithfern.com/learn/sdks/current/llms.txt
> - Legacy: https://buildwithfern.com/learn/sdks/legacy/llms.txt

> For clean Markdown content of this page, append .md to this URL. For the complete documentation index, see https://buildwithfern.com/learn/llms.txt.

# Changelog

## October 16, 2026

## 1.39.2

**`(fix):`** `WebhooksHelper.verifyRequest` now parses `name=value` settings headers when
`webhook-signature.algorithm-header` sets `key-id-parameter`/`algorithm-parameter`
(e.g. Twilio's `X-Twilio-Webhooks-Signature-Settings: key=<id>,algorithm=HMAC_SHA256`),
so the key resolver receives the bare key ID instead of `key=<id>`. Requires IR 67.33.0.

## October 13, 2026

## 1.39.1

**`(fix):`** The generated `WebhooksHelper` now consumes `algorithm-header` from `api.settings.webhook-signature`:
a request-level `verifyRequest` helper reads the configured signature header first (missing or empty
fails early with "could not run: missing signature header"), then the algorithm-selection header
(e.g. `X-Twilio-Webhook-Signature-Settings: <key_id>;HMAC_SHA256`) to pick the HMAC algorithm from
the configured wire values and hand the key ID to an application-supplied secret resolver. When the
header is absent the configured default algorithm and the plain secret are used, so legacy
auth-token flows keep working. The lower-level `verifySignature` is unchanged and still returns
`false` without throwing.

## October 12, 2026

## 1.39.0

**`(fix):`** Pagination now works when the next cursor, the items, or the has-next-page flag is nested inside the
response (e.g. `$response.pagination.nextToken`). The pager used to read only the last field name
from the top-level response (`response.next_token`), which raised `NoMethodError`. It now follows
the full path (`response.pagination.next_token`).

**`(feat):`** Generated wire tests now cover every example of an endpoint instead of only the first, including
autogenerated error examples (one per error) for endpoints without a user-specified error example.
Error tests assert the typed error raised for the status code (`assert_raises`) along with its status
code and body. Each test's `X-Test-Id` header selects a dedicated WireMock mapping that serves that
example's response, success mappings use the endpoint's declared status code, and the test client
disables retries. Set `wireTestsFallbackToAutoGeneratedErrorExamples: false` to leave out the
autogenerated error examples.

## 1.38.2

**`(fix):`** Models no longer keep an unknown JSON key as an extra field when it matches a declared field
case-insensitively (e.g. `VALUE` next to `value`). Previously the extra key was re-emitted after the
declared field, so a case-insensitive, last-wins JSON decoder downstream (such as Go's `encoding/json`)
read the extra key's value instead of the declared field's. Non-colliding unknown keys are still
preserved and re-emitted. XML-encoded models are unchanged, since XML attribute names are case-sensitive.

## October 8, 2026

## 1.38.1

**`(fix):`** Fix paginated results silently skipping records. Every `each`/`map`/`to_a`/`first`/`any?`/`count`
over a pager (or its `pages`) now starts again from the first page, reading `pages` no longer
moves item iteration forward, items are no longer removed from fetched pages as they are read,
and `each` without a block returns an `Enumerator`. Manual `next_element`/`next_page` loops,
including retrying after a failed page, behave as before. Because each loop starts again,
reading the same pager more than once (or reading both `pages` and items) requests the pages
again, except a first page kept by `load_first_page` (or `fetchFirstPageOnCall`), which every
loop reuses.

## 1.38.0

**`(fix):`** Cursor and offset paginated methods now document what they return. The YARD `@return` tag and
the `reference.md` signature name the `CursorItemIterator` / `OffsetItemIterator` instead of the
response type, the method docs say which items it yields, that `pages` gives each page's full
response, and when the request is sent and API errors are raised. The README pagination example
uses a paginated endpoint even when the default endpoint is not paginated, and shows `pages`
instead of the nonexistent `results` method. Iterators gain `load_first_page`, which sends the
first request immediately so its API error is raised at that call.

**`(feat):`** Add opt-in `fetchFirstPageOnCall` config. When enabled, cursor and offset paginated methods send
the request for the first page before returning the iterator, so an API error for that page is
raised by the method call, as in non-paginated methods. Disabled by default because existing
callers may rescue errors around the loop, or build an iterator without iterating it.

## 1.37.3

**`(fix):`** Connection-level failures now raise the SDK's new `Errors::ConnectionError` (a subclass of
`Errors::ApiError`) instead of leaking raw `Errno::ECONNREFUSED`, `Errno::ECONNRESET`,
`EOFError`, `SocketError` (DNS) or `OpenSSL::SSL::SSLError`, so `rescue Errors::ApiError`
catches every failed call as the README documents. The original exception is kept as `cause`.
`Errno::ETIMEDOUT` now raises `Errors::TimeoutError` like the other socket timeouts.
Other `SystemCallError`s (e.g. file errors) are not wrapped. Code that rescued the wrapped classes
directly should rescue `Errors::ConnectionError` instead (or inspect `cause`). Transport failures are still not retried. The README errors example shows the new class.

## 1.37.2

**`(fix):`** Parsing an XML (TwiML) element without a text property keeps the text before its first child in the
ordered content, so `Gather.from_xml('<Gather>Press a key, then <Say>one</Say></Gather>')` round-trips
instead of dropping `Press a key, then `. Types with a text property (e.g. `Say`) are unchanged: that
text is still the property's value.

## October 7, 2026

## 1.37.1

**`(fix):`** Generate `float` and `double` fields and parameters as Ruby `Float` instead of `Integer`.
Decimal values (e.g. a balance of `100.57`) previously lost their fractional part when a
response was decoded or a request was sent. Integer types stay `Integer`.
Generated wire tests now also assert that numbers in request bodies reach the wire unchanged
and that numbers in mocked responses decode unchanged.
Integer and Float map keys nested in models now decode as numbers instead of staying symbols.

## 1.37.0

**`(feat):`** XML (TwiML) models can now carry XML comments. `comment(text)` adds `<!--text-->` inside the element at
its position in the ordered content (interleaved with `add_text` segments and child elements), while
`comment_before(text)` / `comment_after(text)` render a comment immediately before or after the element
itself (as a sibling in its parent, or around the root element after the XML declaration). Comments in
parsed documents are preserved by `from_xml` and round-trip through `to_xml`; `Element#add_comment` and
the new `Internal::Xml::Comment` node expose the same for untyped elements.

## 1.36.0

**`(feat):`** Generate a unit test file per XML-encoded (TwiML) type alongside the models, covering
fromXml/toXml round trips, escaping, unknown attributes/children, open enum passthrough,
child ordering, and rejection of malformed XML, wrong root elements and DOCTYPE declarations.

## October 6, 2026

## 1.35.2

**`(fix):`** List-valued XML (TwiML) attributes and text accept a space-separated String or a single
value (including a Symbol) in addition to an Array, matching the legacy call style:
`response.gather(input: "speech dtmf")` and `dial(status_callback_event: :answered)` no
longer raise `NoMethodError`.

## 1.35.1

**`(fix):`** Fluent XML (TwiML) child methods accept the text content as a keyword as well as positionally,
matching the legacy call style: `response.say(message: "x")`, `response.dial(number: "+1555")` and
`response.message(body: "hi")` now emit the text instead of silently dropping it. Passing the text
both positionally and as a keyword raises `ArgumentError`.

## 1.35.0

**`(feat):`** XML (TwiML) types support the legacy twilio-ruby building style: constructors and fluent child methods yield the
new element to a block (`VoiceResponse.new { |r| r.say("hi") }`, `r.gather { |g| g.say("one") }`), and
`add_child` also accepts an element name with text and attributes (`r.add_child("Custom", "v", a: "1")`).

## 1.34.2

**`(fix):`** Enum-typed attributes and text on XML (TwiML) types are open on the wire: `from_xml` keeps a value the enum does
not declare (e.g. a voice added after the SDK was generated) instead of raising, and it round-trips through
`to_xml`. This matches how the same enums already behave in JSON.

## 1.34.1

**`(fix):`** Endpoints whose request body is an array, set, map, primitive, enum, optional/nullable object, or an alias of
one of these now send the body argument itself (e.g. `[{"name":"Monstera"}]`) instead of wrapping it with the
other keyword arguments (e.g. `{"request": [...]}`). This also applies to such bodies on endpoints with headers
or query parameters, which no longer leak into the body. Objects inside these bodies (optional/nullable objects,
list and set items, and map values) are serialized through their model so fields use their wire names, sets are
sent as JSON arrays, and an omitted optional body is still sent without a body.

## 1.34.0

**`(feat):`** Add opt-in `userAgentOnly` config. When enabled, generated clients send the `User-Agent`
header (including the structured form from `includePlatformHeaders`) but omit the
`X-Fern-Language`, `X-Fern-SDK-Name`, and `X-Fern-SDK-Version` headers. Disabled by default,
so existing generated output is unchanged. `omitFernHeaders` still takes precedence and drops
the `User-Agent` too.

## 1.33.0

**`(feat):`** The generated `WebhooksHelper` HMAC verifier now accepts a per-call algorithm override via an optional `algorithm` parameter on `verifySignature`,
one of `sha1`, `sha256`, `sha384` or `sha512`. When omitted, the algorithm configured in
`webhook-signature` is used, so existing callers are unchanged. This lets one endpoint verify both
legacy HMAC-SHA1 (auth-token) and HMAC-SHA256 (shared-key) signed webhooks.
The verifier also logs a warning distinguishing an early failure (missing signature header) from a
failed verification (signature mismatch); it still returns `false` and never throws.

## 1.32.0

**`(feat):`** XML (TwiML) types keep text segments, typed child models and raw `Element` children in a single
ordered `content` list, so `to_xml` emits them in the order they were added
(`response.say("a"); response.add_child(Element.new("Custom")); response.say("b")` yields
`<Say>a</Say><Custom/><Say>b</Say>`) and `from_xml` preserves document order, including the
position of unknown elements. Adds `add_text(String)` to XML types and `Element` for mixed content
such as `<Say>Hi <break strength="weak"/> world</Say>`. Typed child properties and additional
children remain available and are reconciled into the content order on serialization.

## 1.31.1

**`(fix):`** XML-encoded (TwiML) types now include the `<?xml version="1.0" encoding="UTF-8"?>`
declaration by default when serialized to XML; pass the declaration flag as `false`
to omit it. Nested child elements and raw `XmlElement`s are still written without it.

## 1.31.0

**`(feat):`** Add support for SDK variables (`x-fern-sdk-variables` / `variables` in `api.yml`).
Each variable is exposed as an optional keyword on the root client (e.g.
`Client.new(target_account_sid: "AC...")`); path parameters bound to a variable are
removed from the endpoint methods and request classes, and the value is read from
the client instead. String variables declared with an `env` fall back to
`ENV.fetch("<ENV_VAR>", nil)` when the keyword is omitted, and endpoints raise an
`ArgumentError` with a clear message when a required variable is still unset.

## October 5, 2026

## 1.30.1

**`(fix):`** The `additional_body_parameters` request option is now applied to request bodies. Previously it was
documented on every endpoint method but silently ignored. Extra properties are merged into the serialized
JSON (or form-urlencoded) body using their API wire-format names, override SDK-set fields with the same name,
and create a body when the endpoint has none
(except GET and HEAD requests, which are still sent without a body). Multipart (file upload) requests are not affected.

## 1.30.0

**`(fix):`** Fix pagination for endpoints whose cursor or offset lives in the request body
(e.g. `$request.cursor` or `$request.options.offset`). The generated iterators
previously referenced an undeclared `query_params` and raised `NameError` on first
use; they now read and advance the body property, creating omitted parent objects.

**`(fix):`** Nil values are no longer serialized as `{}` (`nil.to_h`), and `optional<nullable<T>>`
fields keep their nullable metadata so explicit `nil` values are preserved, while
omitted optional fields are still left out of the request.

**`(fix):`** Add a `timeout:` option to the root client and honor `request_options[:timeout_in_seconds]`
per request (an explicit `0` is respected). The timeout also applies to OAuth and inferred-auth
token requests. Net::HTTP open/read/write timeouts are raised as the SDK's `TimeoutError`.
README timeout snippets now show the real API.

**`(fix):`** Endpoints returning text, bytes, or a file download now return the raw response body
instead of `nil`.

**`(fix):`** Generated `.rubocop.yml` disables `Style/CommentAnnotation` (API docstrings such as
"Note:" tripped it), and the generated retry tests follow the `retryStatusCodes` setting.

**`(feat):`** Error responses now expose the body parsed into the endpoint's declared error type
as `error.body` (exact status codes take precedence over `4XX`/`5XX` wildcards).
`error.message` and `error.code` are unchanged; `body` is `nil` when no error type
matches or the body is not valid JSON.

**`(feat):`** Add the opt-in `wrapInvalidJsonResponses` config. When enabled, a 2xx response whose
body is not valid JSON raises `Errors::ResponseError` (with the raw body and status
code) instead of leaking `JSON::ParserError`.

**`(feat):`** Add the opt-in `requireAuthCredentials` config. When enabled, the client
constructor raises `ArgumentError` if a bearer or header credential is neither
passed nor set in its environment variable, instead of sending an empty auth header.

## 1.29.0

**`(feat):`** Add the opt-in `allowCustomHttpClient` config. When enabled, the generated client
accepts an `http_client:` keyword — any object responding to
`request(url, http_request)` and returning a `Net::HTTPResponse` — that replaces the
SDK's built-in `Net::HTTP` transport. This lets callers configure proxies, custom TLS,
connection reuse, or request/response interceptors (logging, signing, header
injection). Retries still wrap the custom client, and it is also used for OAuth /
inferred-auth token requests. The custom client owns its own connection settings,
including timeouts.

## October 2, 2026

## 1.28.3

**`(fix):`** The README's Environments example for a multi-URL SDK passes the environment constant through
the client's `environment:` keyword instead of `base_url:`. With more than one URL per
environment the constant is a hash of URLs, and `base_url:` expects a string, so the example
built a client that could not make a request. Single-URL SDKs keep `base_url:`.

## September 29, 2026

## 1.28.2

**`(chore):`** Bump bundled @fern-api/generator-cli to 0.10.3. When the hosted auto-version analysis of the
full SDK diff fails, generator-cli now retries chunk-by-chunk instead of silently applying a
PATCH bump; any remaining unavailable or incomplete analysis adds a "Version bump not verified"
notice to the SDK PR body and disables automerge.

## 1.28.1

**`(fix):`** Enum values (and other screaming-snake constants) named `begin` or `end` are now emitted
as `BEGIN_` / `END_`, since `BEGIN` and `END` are Ruby reserved words and `END = "end"`
is a syntax error.

## September 25, 2026

## 1.28.0

**`(feat):`** Inline documentation on XML-encoded (TwiML) types: each `field` carries its property
description as a comment, and fluent child methods document the child type, the text
parameter's description, and every attribute via `@option attributes` entries.

## 1.27.1

**`(fix):`** Cap generated request-wrapper filenames (`lib/<gem>/<service>/types/*_request.rb`) at
100 characters, matching the existing cap on type filenames, so `gem build` no longer
fails with `Gem::Package::TooLongFileName` for endpoints with long path-derived names
(e.g. operations without an `operationId`). Over-long names are truncated with a short
deterministic hash appended; the Ruby class name is unchanged.

## September 24, 2026

## 1.27.0

**`(feat):`** Generate the shared `WebhooksHelper` from the API-wide `api.settings.webhook-signature`
setting in `generators.yml` (IR `sdkConfig.webhookSignatureVerification`). The helper is
emitted even when the API definition models no webhooks; webhook-specific signature
overrides still produce their own named helpers.

## September 22, 2026

## 1.26.2

**`(chore):`** Bump bundled @fern-api/generator-cli to 0.10.1. Auto-versioned MAJOR/MINOR bumps no longer
produce a version-only `changelog.md` block when the AI analysis returns an empty changelog
entry; the PR description, version bump reason, or commit message body is used instead.

## 1.26.1

**`(fix):`** Honor the org-level whitelabel setting (`readmeConfig.whiteLabel`) and the `apiName`/`disabledSections` (IR `disabledFeatures`) readme
config options when generating README.md. Previously the "Built with Fern" badge was always emitted.

## September 21, 2026

## 1.26.0

**`(feat):`** Object types with an `xml` encoding (e.g. TwiML) now generate XML support: `to_xml`/`to_xml_element`,
`from_xml`/`from_xml_element`, fluent child builders (`response.say("Hello", voice: "alice")`, with an
`add_` prefix when a child tag collides with a field), and preservation of unknown attributes and child
elements (`additional_attributes`, `additional_children`, `add_child`). A REXML-based
`Internal::Xml` runtime (with DOCTYPE rejection) is emitted only when XML-encoded types exist.

## September 15, 2026

## 1.25.0

**`(fix):`** A 503 now raises a typed error instead of `ArgumentError`.
`ServiceUnavailableError` inherited from `ApiError`, which accepts 0..1
positional arguments, while it is raised with the `(msg, code:)` signature
its siblings use — so constructing it threw from inside the SDK and the
caller lost the response during exactly the outage they needed to see. Every
other 5xx mapped correctly; this was a one-word divergence from
`ResponseError`.

**`(feat):`** Add `respectNullableUnionFields` (opt-in). The undiscriminated-union matcher
tested a member's required fields against `optional` alone, so a field that
is required *and* `nullable` and legitimately arrives as `nil` read as a
missing required field. The arm was rejected, no member matched, and the
union silently degraded to an untyped Hash while the SDK documented the
model — callers then hit `NoMethodError` on the documented accessors.
`Model#to_h` already pairs the two flags this way. Opt-in because a caller
of an already published gem may be reading that Hash by key; enabling it
changes the returned type.

**`(feat):`** Add `coerceAliasResponses` (opt-in). A type alias to a named type generated a
`load` that did a bare `JSON.parse`, so an endpoint whose response schema is
a `$ref` to another schema returned a string-keyed Hash while the SDK
documented the aliased model — and it parsed without `symbolize_names`,
unlike `Model.load`, so even hash access used different keys. With the flag,
`load` delegates to the aliased type's own coercion. Aliases to primitives,
containers and `unknown` are unaffected: there is no named type to delegate
to. Opt-in because a caller of an already published gem may be reading that
Hash by key.

## September 11, 2026

## 1.24.1

**`(fix):`** Fix file upload endpoints crashing with `NoMethodError`. Generated multipart code now
calls `FormData#add_file` instead of the non-existent `to_form_data_part`, file arrays
add one part per file, and declared MIME types are forwarded as `content_type:`.
`FormData#add_file` also accepts a file path `String` (matching the generated method
signatures), reading the file contents and deriving the filename from the path.
Non-file multipart properties are now added unless the value is `nil`, so `false` and
`0` are no longer silently dropped while an explicitly-passed `nil` is still omitted
rather than sent as an empty part.

**`(fix):`** Deserialize non-object JSON response bodies. Endpoints whose response is a top-level
list, map, set, primitive, or unknown JSON value previously returned `nil`; they now
parse the body and coerce it to the declared type. Bodies are parsed with
`symbolize_names: true` to match `Model.load`, so an `unknown` value is symbol-keyed
wherever it appears; maps whose keys coerce to `Integer` keep string keys, since JSON
object keys are always strings and a `Symbol` cannot coerce to `Integer`. A named type
behind an `optional`/`nullable` wrapper now deserializes through `.load` like a bare
named type, so a union member is resolved instead of the body reaching the caller as
raw parsed JSON. Every successful response with an empty body (including named types)
returns `nil` instead of raising `JSON::ParserError`.

**`(fix):`** Preserve `false` values in model fields. Previously `Model#initialize` used `||` to
coalesce values, so any boolean field set to `false` was dropped and never serialized
(e.g. `users.update(blocked: false)` sent an empty body).

## September 1, 2026

## 1.24.0

**`(feat):`** Added the `respectAuthSchemeNames` configuration option. With it enabled, credential
keyword arguments on the client follow the names configured on the auth schemes
(`token: { name: apiKey }` produces `api_key:` instead of `token:`), matching the
TypeScript and Python generators. Names that would shadow a built-in client keyword
(`base_url`, `environment`, `max_retries`, `app_info`, `client`, `request_options`) are
suffixed with `_auth`, and global headers that collide with a credential or built-in
keyword are prefixed with `header_`. The option defaults to `false`, since renaming a
keyword argument breaks callers of an already published gem; the default is expected to
flip in the next major version.

## August 20, 2026

## 1.23.2

**`(fix):`** Respect endpoint-level disabled retries. Endpoints configured with
`retries: { disabled: true }` (`x-fern-retries` in OpenAPI) now pin the
generated request's retry count to zero, so they are never retried
regardless of the client-level or per-request retry settings.

## 1.23.1

**`(fix):`** Fixed generated WireMock stubs for OAuth client credentials APIs whose token endpoint is
itself marked as authenticated: the token (and refresh) endpoint stub no longer requires
an `Authorization` header, since the token request is made to obtain the token and cannot
carry one yet.

## August 13, 2026

## 1.23.0

**`(fix):`** Endpoints whose request body is a referenced type no longer serialize path parameters into
the JSON body. With `respectOptionalRequestBody`, an omitted optional body on such an endpoint
now sends no body and no `Content-Type` instead of a body containing the path parameters.

**`(feat):`** Add the `respectOptionalRequestBody` configuration option. When enabled, an endpoint whose
request body the API does not require lets callers omit the body: the request then carries
neither a body nor a `Content-Type` header, instead of sending `{}` as `application/json`.
Disabled by default.

## August 9, 2026

## 1.22.0

**`(feat):`** Add an opt-in `preferExplicitAuth` configuration option. When enabled (and the API composes
OAuth client-credentials with basic auth via `auth: any`), auth credentials passed
explicitly to the client constructor take precedence over environment-variable
defaults when selecting the auth scheme — e.g. explicitly provided basic auth
credentials win over OAuth client ID/secret environment variables. Disabled by
default, so existing generated output and runtime behavior are unchanged.

## August 8, 2026

## 1.21.3

**`(fix):`** Wire tests no longer assert an exact `Basic` Authorization header when an OAuth
scheme is also configured: the test client is constructed with client credentials,
so the OAuth provider's dynamic Bearer token takes precedence over the static
Basic header at request time.

## 1.21.2

**`(fix):`** Fixed WireMock stubs for wire tests of APIs with `auth: any` combining Basic and
OAuth/Bearer schemes: stubs now accept either Authorization form instead of
requiring one exact value the client may not send.

_Showing the 20 most recent of 134 entries. Append `/llms.txt` to the changelog URL for the complete index._