> 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 16, 2026

## 2.92.3

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

## 2.92.2

**`(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 9, 2026

## 2.92.1

**`(fix):`** `reference.md` is now generated for every output mode, matching the Go and Python generators. It was
previously skipped unless the CLI passed a snippet path (`local-file-system` output without
`snippets: path:` in generators.yml). `README.md` is unchanged and still requires snippets.

## October 7, 2026

## 2.92.0

**`(feat):`** XML (TwiML) types can now carry XML comments. `Comment(text)` adds `<!--text-->` inside the element at its
position in the ordered content (interleaved with `AddText` segments and child elements), while
`CommentBefore(text)` / `CommentAfter(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 `FromXml` and round-trip through `ToXml`; `XmlElement.AddComment` and
the new `XmlComment` runtime type expose the same for untyped elements.

## 2.91.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

## 2.90.1

**`(fix):`** XML (TwiML) record types implement value equality: `Equals`/`GetHashCode` (and `==`) compare the
rendered XML, so `VoiceResponse.FromXml(xml).Equals(VoiceResponse.FromXml(parsed.ToXml()))` and
`new Say { Message = "x" }.Equals(new Say { Message = "x" })` are true. Previously the record
default compared the collection members by reference and was always false.

## 2.90.0

**`(feat):`** Add opt-in `user-agent-only` config. When enabled, generated clients send the `User-Agent`
header (including the structured form from `include-platform-headers`) 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. `omit-fern-headers` still takes precedence and drops
the `User-Agent` too.

**`(fix):`** Fix the generated `XmlElementTests` test project failing to compile because its `FakeNode` test
double did not implement `IXmlNode.ToXml(bool)`.

## 2.89.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.

## 2.88.0

**`(feat):`** XML (TwiML) types keep text segments, typed child elements and raw `XmlElement` children in a
single ordered `Content` list, so `ToXml()` emits them in the order they were added
(`Response.Say(a).AddChild(custom).Say(b)` yields `<Say>a</Say><Custom /><Say>b</Say>`) and
`FromXml()` preserves document order, including the position of unknown elements and repeated
wrapper elements. Adds `AddText(string)` to XML types and `XmlElement` for mixed content such as
`<Say>Hi <break strength="weak" /> world</Say>`. Typed child properties and `AdditionalChildren`
remain available; `AdditionalChildren` is now derived from `Content`.

## 2.87.0

**`(feat):`** Support SDK variables (`variables` in `api.yml` / `x-fern-sdk-variables` in OpenAPI). Path
parameters bound to a variable via `x-fern-sdk-variable` are removed from endpoint
signatures and request types and are configured once on the client through the generated
`ClientOptions.<Variable>` property. String variables that declare an `env` fall back to that
environment variable, and a variable that is still unset fails with a clear
`ArgumentException` before the request is sent.

## October 5, 2026

## 2.86.3

**`(fix):`** XML-encoded (TwiML) types now return their XML representation from
`ToString()` instead of JSON, and `ToXml()` prepends the
`<?xml version="1.0" encoding="utf-8"?>` declaration by default.
Pass `ToXml(xmlDeclaration: false)` to omit it.

## 2.86.2

**`(fix):`** Fix `RequestOptions.AdditionalBodyProperties` throwing `InvalidOperationException` on endpoints
without a request body (e.g. GET requests). When the request body is null, the additional
properties are now sent as the JSON request body.

**`(fix):`** `RequestOptions.AdditionalBodyProperties` is now applied to form-urlencoded requests. The additional
properties are merged into the form body (the additional property wins on a key collision) before
encoding. Multipart requests remain unsupported.

## September 29, 2026

## 2.86.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.

## September 25, 2026

## 2.86.0

**`(feat):`** Fluent child methods on XML-encoded (TwiML) types (e.g. `response.Say(...)`, `say.Break(...)`)
now carry `<param>` documentation for every argument, taken from the child type's property
descriptions, and include the child type's own description in the method summary.

## September 24, 2026

## 2.85.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.

## 2.84.3

**`(fix):`** `RawClient` now applies the configured `Timeout` to each retry attempt individually instead of
sharing a single budget across all attempts and backoff delays. Note that `Timeout` therefore no
longer bounds the total duration of a call that is retried; the worst case is roughly
`Timeout * (MaxRetries + 1)` plus backoff. If a retry attempt fails after an earlier attempt
already received an API response (e.g. a 429), that response is returned instead of a generic
cancellation error.

## September 22, 2026

## 2.84.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.

## 2.84.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

## 2.84.0

**`(feat):`** Object types with XML encoding metadata (e.g. TwiML) now implement `IXmlNode` with `ToXml()`/`ToXElement()`,
static `FromXml(string)`/`FromXElement(XElement)` parsers, and fluent child builder methods
(`response.Say("Hello", voice: "man").Dial(...)`). Unknown attributes and child elements are preserved in
`AdditionalAttributes`/`AdditionalChildren` and re-emitted; `AddChild(XmlElement)` adds arbitrary elements.
Parsing rejects malformed XML, mismatched root elements and DTDs. Types without XML metadata are unchanged.

## September 7, 2026

## 2.83.3

**`(fix):`** Fix the generated root client passing a `grantType` argument to `OAuthTokenProvider`
when the token endpoint declares a required, non-literal `grant_type` property.
`OAuthTokenProvider` hardcodes `grant_type: "client_credentials"` and never took that
parameter, so the generated SDK failed to compile with
`'OAuthTokenProvider' does not contain a constructor that takes 4 arguments`.

## August 24, 2026

## 2.83.2

**`(fix):`** Stop dropping the examples of endpoints whose request body is bytes or a file upload. The IR's
`ExampleRequestBody` cannot carry those bodies, so such examples read as bodyless and the
endpoint lost its snippets and generated tests.

## August 20, 2026

## 2.83.1

**`(fix):`** Honor the `x-fern-retries: { disabled: true }` extension (`retries: { disabled: true }` in a Fern
definition) at the endpoint level. Requests for those endpoints are now sent exactly once, even when
the client or the per-request options configure retries.

## 2.83.0

**`(feat):`** Added a `generate-documentation-file` config option (default `true`) so the
generated csproj emits XML documentation and the published NuGet package ships
`lib/<tfm>/<Namespace>.xml`, giving consumers IntelliSense. CS1591 is
suppressed so enabling documentation does not introduce warnings for
undocumented members.

**`(feat):`** Added a `package-metadata` config block for NuGet metadata written into the
generated csproj: `description`, `authors`, `tags`, `copyright`, `icon`,
`project-url`, `repository-url`, `repository-type`, `include-source-link`
(adds `Microsoft.SourceLink.GitHub`) and `include-symbols` (produces a
`.snupkg`).

**`(fix):`** Global header values set on `ClientOptions` are now respected in the default
(non-unified) client shape. Previously the generated root client read only the
constructor parameter for a promoted global header, so
`new Client(clientOptions: new ClientOptions { Version = "..." })` was silently
ignored and a stray environment variable could win instead. The resolution
order is now: explicit constructor argument, `ClientOptions` property,
environment variable, then the literal/client default.

**`(fix):`** `ClientOptions.Clone()` now copies literal global header properties (e.g.
`Version`), which were previously dropped when the options were cloned.

**`(fix):`** Escape markup characters in the attribute values of documentation comment tags. Descriptions
containing links such as `<a href="https://example.com/docs?a=1&b=2">` no longer emit invalid
XML (CS1570) in generated documentation comments, while attribute values that already contain
character or entity references are left untouched.

**`(fix):`** The generated README's `## Requirements` section is now populated with the
project's target frameworks (".NET 8 and above", ".NET Framework 4.6.2 and
above", ".NET Standard 2.0 and above"). Previously the C# generator passed an
empty requirements list, so the section rendered as a dangling
"This SDK requires:" with no entries.

**`(fix):`** README and dynamic snippets no longer pass an upper-cased placeholder for
global header constructor parameters (e.g. `new Client("TOKEN", "VERSION")`,
which sent the literal header value `VERSION` at runtime). Snippets now use
the header's literal or client default value when one is known.

**`(fix):`** Fixed invalid C# in the generated README: `RequestOptions` examples used
object-initializer syntax with `:` instead of `=` (`new RequestOptions { MaxRetries: 0 }`),
and the `WithRawResponse` section declared `var data` twice in one block.

**`(fix):`** Fix invalid `<see cref="..."/>` documentation comments generated for union types. Type names
are now converted to documentation comment identifiers (`List<string>` becomes
`List{String}`, `object?` becomes `object`), and types that cannot be expressed as an
identifier (arrays, nested generic arguments) are rendered as escaped inline code instead.
This removes CS1570/CS1584/CS1658 warnings from generated SDKs that contain unions when XML
documentation is enabled.

**`(fix):`** Escaped generic type syntax in the XML doc comments of the generated core
files (`Optional`, `OptionalAttribute`, `NullableAttribute`, `Extensions`),
and added the missing `param` tags on `Extensions.Assert`. Building generated
SDKs with XML documentation enabled no longer emits CS1570/CS1573 warnings.

## August 18, 2026

## 2.82.1

**`(fix):`** Global headers typed as a `literal<"...">` with an `env` fallback no longer throw when
neither the constructor parameter nor the environment variable is set. The literal is
known at compile time, so it is now used as the header's default value.

**`(fix):`** The generated `X-Fern-SDK-Version` header value is now emitted as a globally qualified
reference, so it can no longer be shadowed by a constructor parameter or local that
PascalCases to `Version` (previously `error CS1061: 'string' does not contain a
definition for 'Current'`).

## August 13, 2026

## 2.82.0

**`(fix):`** With `respect-optional-request-body` enabled, examples that omit an optional request body are no
longer dropped from the generated snippets and reference.

**`(feat):`** Add a `respect-optional-request-body` option. When enabled, an endpoint whose request body the API
does not require takes that body as `Body? request = null`, so the caller can leave the argument out
and the request is sent with no body and no body content type. Examples that supply no body render
as `client.BulkRefundAsync()`. The option defaults to false, so existing signatures and snippets are
unchanged.

**`(fix):`** Skip endpoint examples that omit a request body the endpoint declares. The generated request
type requires the body, so rendering such an example produced a call missing a required
argument in the reference, the snippets and the mock server tests.

## 2.81.1

**`(fix):`** Percent-encode `;` in query parameter names and values. RFC 3986 permits it, but it
remains a legacy parameter separator for many servers, which truncated values such as
`?mail2=a@example.com;%20b@example.com` at the first `;`.

## August 9, 2026

## 2.81.0

**`(feat):`** Add an opt-in `prefer-explicit-auth` 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 7, 2026

## 2.80.1

**`(fix):`** Fix generated mock server tests for examples that omit an optional request body. The
stub no longer matches on a `Content-Type` header that the bodyless request does not send.

## August 3, 2026

## 2.80.0

**`(feat):`** Add opt-in `allow-user-agent-app-info` config. When enabled, generated clients expose an
optional `AppInfo` client option (`Name`, `Version?`, `Comment?`) whose sanitized product
token is appended to whatever `User-Agent` the SDK would otherwise send
(`{sdk}/{version} ... {product}/{product-version} ({comment})`), following RFC 9110.
Disabled by default, so existing generated output is byte-identical, and it composes with
`include-platform-headers`, the configured `user-agent` template value, and the
`user-agent-name-from-package` fallback. Caller-supplied values are trimmed and encoded
(name/version percent-encoded to RFC 7230 `tchar`; comment delimiters `(`, `)`, `\` and
control characters incl. CR/LF escaped), so untrusted values cannot inject header content.
Still overridable by an explicit `User-Agent` header and suppressed by `omit-fern-headers`.
Works in both unified and non-unified client-options modes; generated code compiles for
`net462`, `net8.0`, and `netstandard2.0`.

## July 30, 2026

## 2.79.4

**`(fix):`** Respect the `user-agent` config template when `include-platform-headers` is enabled. The
structured User-Agent previously always used the NuGet package id, ignoring the
configured template.

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