> This page is for SDKs, 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 13, 2026

## 0.53.3

**`(fix):`** Fix a compile error in multipart upload requests whose property names don't round-trip
through the old snake_case conversion (for example `file_0` or `URLPath`). `to_multipart()`
now uses the same field name as the request struct instead of deriving its own.

## October 12, 2026

## 0.53.2

**`(fix):`** Fix generated snippets and wire tests referencing type names that don't exist in the SDK.
Snippets now use the same casing as the generated types (e.g. `MarketplaceIdsItem` rather than
`MarketplaceIDsItem`) and the same collision-resolved names (e.g. `Address4` when several
types are named `Address`), including inline, file upload and query request types. Casing
overrides are kept.

## October 8, 2026

## 0.53.1

**`(fix):`** SSE unions discriminated by the `event:` line (`x-fern-discriminator-context: protocol`) now read
the `event:` line instead of deserializing every event as `__Unknown`. Both variant styles work:
envelope variants (`{event, data: Payload}`) are built from the `data:` JSON, and payload variants (the `data:` JSON itself) get the `event:` value injected as the discriminant.
The event name no longer has to be repeated inside the `data:` JSON.

## October 5, 2026

## 0.53.0

**`(feat):`** Add `RequestOptions::additional_body_param` (backed by `additional_body_params`) for sending
extra request body properties, such as undocumented or beta fields, without a new SDK release.
Properties are merged into the serialized body under their wire-format keys and override
generated fields with the same key. When an endpoint has no body, a JSON object containing only
the additional properties is sent. JSON and form-urlencoded bodies are supported; multipart and
raw bytes bodies are sent unchanged.

## October 2, 2026

## 0.52.0

**`(feat):`** The generated prelude now re-exports `futures::StreamExt`, so `use <crate>::prelude::*;` is enough to
iterate SSE streams and paginators without adding `futures` as a direct dependency.

## September 30, 2026

## 0.51.0

**`(feat):`** Add `preserveCrateNameHyphens` config option (default `false`). When enabled, hyphens in `crateName`
are kept in the generated `Cargo.toml` package name (e.g. `my-sdk` instead of `my_sdk`), as well as in the
docs.rs link, README install snippet, and default crates.io publish package name. Rust `use` paths keep
the underscored name (`use my_sdk::prelude::*`), matching how Cargo maps hyphenated package names.

## 0.50.0

**`(feat):`** Add the `userAgentFromPlatformHeaders` config option. When it is set to `true`, the default
`ClientConfig.user_agent` uses the IR's platform `User-Agent` value (`<package-name>/<version>`
by default, or the `user-agent` template from generators.yml), matching the other SDK generators.
The option defaults to `false`, which keeps the existing `<ApiName> Rust SDK` value. The Rust SDK
also falls back to `<ApiName> Rust SDK` when the IR has no platform User-Agent.

## September 29, 2026

## 0.49.1

**`(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.

## 0.49.0

**`(feat):`** Every multi-URL environment now embeds its URLs: `impl Default for <Env>Urls` per
environment and a constructor per environment (`Environment::staging()`), so selecting a
non-default environment no longer means retyping URLs from `generators.yml`. The README
environments snippet of a multi-URL SDK sets `environment` through that constructor instead
of `Environment::Production.url()`, which does not compile against a tuple variant.

**`(fix):`** In a multi-URL environment an explicit `base_url` now wins over the default environment.
`ClientConfig::default()` set both `base_url` and `environment`, and every endpoint let the
environment win, so `ClientConfig { base_url: "https://sandbox…", ..Default::default() }`
silently sent every request to the default (production) environment. Requests now go to
`base_url` whenever it is anything other than one of the configured environment's URLs (or
the default environment's URL that `Default` fills in); otherwise each request goes to the
environment's URL for its service, as before. `ClientConfig::service_url` carries the rule.

## September 28, 2026

## 0.48.0

**`(feat):`** Generate webhook signature verification helpers (`src/webhooks.rs` plus a
`core::webhook_signature` runtime) when the API defines HMAC-signed webhooks.
Helpers verify the configured signature header (HMAC-SHA1/256/384/512, base64 or
hex, optional prefix) with constant-time comparison, support sorted/deduplicated
form parameters, `bodySHA256`-style body-hash binding for JSON requests,
notification URL port/legacy-query normalization, and timestamp tolerance checks.

## September 23, 2026

## 0.47.11

**`(fix):`** Client `mod.rs` files no longer emit `use crate::api::*;` when the glob is not
needed. Sub-client structs are already re-exported by the containing module, and
root-level endpoints only import the glob when they reference generated types.
Previously every nested resource module (and the root resources module) produced
an `unused_imports` warning on `cargo build`. Streaming (SSE and JSON-chunk)
response payloads now count as generated-type usage, so services whose only
generated type is a streamed payload keep the import they need.

## September 22, 2026

## 0.47.10

**`(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.

## September 18, 2026

## 0.47.9

**`(fix):`** An undiscriminated union with a single member no longer emits an unreachable
`_ => None` arm in its `as_*` / `into_*` accessors. That arm produced an
`unreachable_patterns` warning in the generated crate, which blocks users who
publish with `-D warnings`.

## September 9, 2026

## 0.47.8

**`(fix):`** A request body declared `application/x-www-form-urlencoded` is now form-encoded
instead of being sent as a JSON document under an `application/json` header. Those
endpoints went through `execute_request`, whose `.json()` both serializes to JSON and
stamps the header, so neither the bytes nor the content type matched the declaration.

## 0.47.7

**`(fix):`** A JSON request body declared under a media type other than `application/json` - a
vendor type, or `application/merge-patch+json` - is now sent under that type instead
of `application/json`. `execute_request` calls reqwest's `.json()`, which stamps
`application/json` over whatever the endpoint declared, so those endpoints now take a
variant that sets the header and serializes the body identically. Emitted only when an
endpoint declares such a type, so an SDK that never does is unchanged.

## 0.47.6

**`(fix):`** An array query parameter declared `style: form` with `explode: false` is now sent as
one comma-joined value instead of a repeated key. The IR has always carried `explode`
and the Rust generator read it nowhere, so every array went out exploded regardless of
what the endpoint declared. An empty array still produces no parameter.

## 0.47.5

**`(fix):`** Basic auth credentials are now sent. An API whose only security scheme is basic auth
generated a client that applied NO credential at all: the client-wide auth path
applied an API key and a bearer token but had no basic branch, `username` and
`password` sat unread on the config, and the one `Authorization` header in the
generated client was gated on `config.token`, which such an API never sets. Only
per-endpoint auth routing handled basic auth.

## 0.47.4

**`(fix):`** A string field sent alongside a file in a multipart body is now written verbatim
instead of being JSON-serialized first. `serde_json::to_string` wrapped it in quotes
and those quotes reached the server as part of the value, so a 13-character caption
arrived as 15. Structured parts (objects, arrays, maps) still go as JSON; numbers and
booleans were never affected.

## 0.47.3

**`(fix):`** A raw-bytes request body is now sent under the content type the endpoint declares
instead of always `application/octet-stream`. The IR has always carried it on the
bytes body, and the C#, Python and TypeScript generators already stamped it; Rust
hard-coded the header in `execute_bytes_request`. An endpoint that declares no
content type still sends `application/octet-stream`.

## September 8, 2026

## 0.47.2

**`(fix):`** An offset-paginated endpoint's auto-paging method now stops on a SHORT page rather than
only on an empty one. A page holding fewer items than the caller's page size is the last
page, so treating "not empty" as "there is more" cost one wasted request against a
well-behaved server and never terminated at all against one that keeps answering.

## 0.47.1

**`(fix):`** An offset-paginated endpoint now sends its page-size parameter on every page request
instead of only the first. The `step` parameter of `x-fern-pagination` was being
stripped from the query along with the offset, but only the offset changes between
pages - the page size is the same on all of them and has to be sent each time.

**`(fix):`** Advance an offset by the number of items the page returned rather than by a step read
out of the response body, which was never present and so left every offset advancing
by one. Add `offsetSemantics`, matching the TypeScript and Python generators: the
default `"item-index"` advances by the page's item count, and `"page-index"` advances
by one.

## 0.47.0

**`(feat):`** Paginated endpoints once again generate an auto-paging method. It is now emitted
ALONGSIDE the single-page method as `<endpoint>_paginated` rather than replacing it,
so the endpoint keeps its typed response and callers who want one page do not have
to go through a paginator.

**`(fix):`** Fix the borrow that made the paginated method fail to compile. The page loader
outlives the method call, so path and body parameters are now captured as owned
values, and the rewrite that points the loader at those captures no longer fails to
match every name.

## September 3, 2026

## 0.46.7

**`(chore):`** Solve `Default` support for every type in one pass over the IR instead of walking
the type graph again for each reference. Derives are unchanged, and the answer no
longer depends on which type is analyzed first.

**`(fix):`** Index type declarations once instead of scanning every IR type on each lookup.
Generating models for APIs with thousands of types is now dramatically faster.

## September 2, 2026

## 0.46.6

**`(fix):`** List- and set-typed path parameters are now joined instead of being interpolated
directly, so a spec with an array path parameter produces an SDK that compiles.

Previously the parameter name was passed straight to `format!`, which does not
compile because `Vec<T>` has no `Display`:

```rust
pub async fn path_array(&self, ids: &Vec<String>, ..) -> Result<Echo, ApiError> {
    &format!("params/path-array/{}", ids),
```

That is an E0277 in the generated crate, so the failure is not limited to the
endpoint declaring the parameter — the whole SDK fails to build and every other
endpoint in it becomes unusable.

The elements are now joined with a comma, which is OpenAPI's default `simple`
path style, using `to_string()` per element so that any `Display` element type
works and not only `String`. Optional, nullable and map containers keep their
previous behavior: an optional path parameter cannot be omitted from a path, and
a map has no defined path serialization.

The non-default `label` (`.a.b`) and `matrix` (`;ids=a,b`) path styles remain
unimplemented.

## 0.46.5

**`(fix):`** Aliases of `unknown` (e.g. `AnyType` wrapping `serde_json::Value`) now derive
`Default`, so structs and aliases that contain them and derive `Default`
compile without manual patching. The struct generator's private copy of the
default analysis has been folded into the shared `hasDefaultImpl`, which is
now the single source of truth and no longer recurses forever on types that
reference each other. `#[serde(default)]` emission is unchanged: a required
`unknown` field still fails to deserialize when the key is missing.

## August 25, 2026

## 0.46.4

**`(fix):`** Fix generated code failing to compile when a property is named `self`, `Self`, `crate` or `super`.
Rust rejects these as raw identifiers, so they are now suffixed with an underscore (`self_`)
and given an explicit `#[serde(rename = "self")]` to preserve the wire name.

## 0.46.3

**`(fix):`** Custom headers are applied when the SDK runs through an injected `RequestExecutor` (the path
used by generated CLI custom commands). `ClientConfig::custom_headers` — including an
`X-Source`-style override and the `X-Fern-*` platform headers — and
`RequestOptions::additional_headers` were previously dropped before delegating to the executor,
so they reached the wire on generated-path requests but not on custom-command ones. Auth and
retries remain the executor's responsibility.

## August 20, 2026

## 0.46.2

**`(fix):`** Endpoints that declare `retries: { disabled: true }` (`x-fern-retries` in OpenAPI) no longer
retry. The generated method pins `max_retries` to zero on the request options, overriding both
the client-level default and any caller-supplied value.

## 0.46.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 17, 2026

## 0.46.0

**`(feat):`** Add `respectOptionalRequestBody`. When enabled, an endpoint whose request body the API does not
require takes the body as an `Option`, so a caller who passes `None` sends no body and no
`Content-Type`. Examples that omit the body render as `None` in snippets, reference docs, and
wire tests. The flag defaults to false, so existing SDKs keep their current method signatures.

## August 14, 2026

## 0.45.0

**`(feat):`** Generated clients accept a custom `reqwest::Client`. `ClientConfig` gained a
`reqwest_client: Option<reqwest::Client>` field, set through the new
`ApiClientBuilder::reqwest_client(...)`, and `HttpClient` uses it as-is when present
instead of building its own. This is the transport escape hatch other Fern SDKs already
expose (`httpx_client` in Python, `OkHttpClient` in Java, `urlSession` in Swift), and it
lets callers configure root certificates, client certificates, proxies and connection
pooling themselves. Authentication, custom headers and retries are still applied by the
SDK on top of the supplied client; when the field is `None` the client is built exactly
as before from `timeout` and `user_agent`.

## 0.44.5

**`(fix):`** Write the LICENSE file into the generated crate when a custom license is configured
(`github.license.custom` or `metadata.license.custom`), matching the other SDK generators.
When that file is written and neither `packageLicense` nor `packageLicenseFile` is set,
`Cargo.toml` now points `license-file` at it instead of defaulting to MIT.

## August 12, 2026

## 0.44.4

**`(fix):`** Non-2xx responses with a body are now returned as errors instead of successes.
`parse_response` and `parse_response_raw` only consulted the status code inside
their empty-body branch, so any error response that carried a payload was
deserialized into the success type and returned `Ok`. When the success type can
absorb an arbitrary object — all-optional fields, a bare `Value`, a defaulted
collection — the call looked like an empty-but-successful result: a 401 surfaced
as "no results found" and the process exited 0, which a calling script reads as
"nothing to do". The status is now checked before deserialization in both
methods, and the response body is carried through as the `ApiError::Http`
message so the server's own error detail reaches the caller.

This also closes the gap left by the executor path: `send_request`
short-circuits to `executor.execute(...)` when a custom `RequestExecutor` is
installed, bypassing `execute_with_retries` — previously the only place the
status was inspected. Checking in the parsers covers both paths.

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