Changelog
2.25.0
(feat): Add support for SDK variables (x-fern-sdk-variables / variables in api.yml). Each variable
becomes an optional, named constructor parameter on the root client (also settable through the
options array and per request). Path parameters bound to a variable are removed from endpoint
method signatures and request classes and resolved from the client instead. String variables
that declare an environment variable fall back to getenv() when no value is passed, and a
clear InvalidArgumentException is thrown when a bound variable is still unset at call time.
2.24.2
(fix): Fix JsonSerializableType::jsonDeserialize throwing Unexpected non-string type for date.
when a #[Date]-annotated property receives an explicit JSON null. Nullable date and
date-time fields now deserialize null to null, matching how omitted keys and the
serializer already behave; non-string, non-null values are still rejected.
2.24.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.
2.24.0
(feat): Inline documentation on XML-encoded (TwiML) types: constructors list every documented
$values key with its description, fluent child methods carry the child type’s
description and per-key docs for the $attributes/child array shape, and text
parameters include the text property’s description.
2.23.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.22.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.22.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.
2.22.0
(feat): Object types with XML encoding metadata (e.g. TwiML) now extend XmlSerializableType and
generate toXml()/toXmlElement(), fromXml()/fromXmlElement(), and fluent child builder
methods ($response->say('Hello', ['voice' => 'man'])). Attributes, text, namespaces, prefixes,
wrapped lists, separator-delimited lists and enums round-trip; unknown attributes and children
are preserved and re-emitted, and parsing rejects DTDs, malformed input and wrong root elements.
XML types add ext-dom/ext-libxml to composer.json and a Core\Xml runtime.
2.21.0
(feat): Add rejectEmptyDateTimeStrings (opt-in). deserializeDateTime("") returned
the current wall-clock time rather than raising: PHP’s DateTime
constructor treats an empty string as “construct for the current moment” and
does not throw, so the surrounding catch never fired. Any API that sends ""
to mean “not set” therefore produced a plausible, entirely fabricated
timestamp with no way for the caller to detect it — a value that can be
written back to a database and is indistinguishable from real data
afterwards. A malformed string was always handled correctly; it was
specifically the empty string that slipped through. The sibling
deserializeDate already rejects "", so this also removes an
inconsistency between the two. Opt-in because it turns a value callers
currently receive into an exception.
2.20.4
(fix): Honor endpoint-level retries: { disabled: true } (x-fern-retries). Requests for those
endpoints pin maxRetries to 0, so they are never retried regardless of the client-level or
per-request retry options.
2.20.3
(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.
2.20.2
(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.
2.20.1
(fix): Render an empty body in snippets for an example that omits a request body the API does not
require. The generated request wrapper still requires the body, so the snippet left the
required key out and did not typecheck.
2.20.0
(feat): Add a respectOptionalRequestBody option. When enabled, an endpoint whose request body the API
does not require lets the caller leave that body out of the call, and such a call sends neither a
body nor a Content-Type header. refund(string $id, RefundRequest $request) becomes
refund(string $id, ?RefundRequest $request = null), and a body carried in a request wrapper
becomes an omittable property, so new RefundWithHeaderRequest([]) no longer throws. Snippets
that supply no body render as $client->bulkRefund();. The option defaults to false, so existing
signatures, output, and snippets are unchanged.
2.19.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.
2.18.2
(fix): Fixed wire tests for APIs with basic auth schemes that use custom parameter names
(e.g. accountSid/authToken): the generated test client constructor now uses the
scheme’s actual parameter names instead of hardcoded username/password.
Also fixed WireMock stubs for auth: any APIs combining Basic and OAuth/Bearer:
stubs now expect the Bearer Authorization header the client actually sends instead
of the Basic header.
2.18.1
(fix): The generated composer.json version (and the fallback User-Agent header) now honors
the version passed via fern generate --version for local-file-system output,
instead of always defaulting to 0.0.0. The Composer package name also falls back to
the CLI-provided package name when packageName is not set in generators.yml.
2.18.0
(feat): Add an opt-in allowUserAgentAppInfo config (default false). When enabled, the
generated client accepts an optional appInfo client option
(array{name: string, version?: string, comment?: string}) whose product token is
appended to the User-Agent header for all three branches (the structured platform
value, the configured user-agent template value, and the default
{package}/{version}), producing e.g. acme/sdk/1.0.0 partner-app/3.1.0 (+https://partner.example)
per RFC 9110. Caller-supplied values are sanitized (name/version percent-encoded to
RFC 7230 tchars; comment delimiters and control characters escaped) and trimmed
before encoding, so untrusted values cannot inject additional header content. The
header is still overridable by an explicit User-Agent and suppressed by
omitFernHeaders. Default-off output is byte-identical.
2.17.0
(feat): Add an encode-path-params config option (default false). When enabled, generated clients
pass path parameter values through RawClient::encodePathParam() at the point they are
substituted into the path template, so a value containing / or .. can no longer change which
endpoint the request resolves to. The default false preserves the existing (unencoded)
behavior; TypeScript, Go, Java, and C# already encode path params.
2.16.0
(feat): Add support for per-endpoint auth routing. When the API-level auth requirement is
ENDPOINT_SECURITY, each endpoint now applies only the auth scheme(s) it declares in
its IR security field (OR across the list of requirements, AND within a requirement,
and no auth when security is empty), instead of applying every configured credential
to every request. Behavior for the ALL and ANY auth requirements (the common cases)
is unchanged.