Changelog

3.91.0

(feat): Add the opt-in deepObjectMapQueryParameters configuration option. When enabled, query parameters typed as a map (e.g. map<string, string>, which is what an OpenAPI type: object + additionalProperties schema imports as) are handed to the query builder and serialized in deepObject form (?metadata[env]=prod) instead of being JSON-stringified (?metadata=%7B%22env%22...). Maps whose values are objects, nested maps, lists, sets, or date-times are supported too: with the serde layer enabled they are run through the generated schema first, so nested keys use wire names, sets become arrays, and dates are ISO strings (?metadata[env][created_at]=2026-09-04T00:00:00.000Z). Defaults to false, so existing SDKs are unaffected.

3.90.0

(feat): Add the packageJsonMergeStrategy option (shallow | deep, default shallow) controlling how packageJson overrides in generators.yml are merged into the generated package.json.

shallow is the existing behavior and remains the default: nested objects such as exports["."] are replaced wholesale by the override, so adding a single custom export condition drops the generated import/require/default conditions.

3.89.0

(feat): Add a guardProcessEnvAccess config option (boolean, defaults to false). When enabled, the generated header, bearer, basic and OAuth auth providers read environment variables through a typeof process !== "undefined" guard, so a missing credential surfaces the normal error instead of ReferenceError: process is not defined in runtimes without a Node process global (browsers/Vite, Cloudflare Workers, Deno). Defaults to false, so generated output is unchanged unless the option is enabled.

3.88.3

(fix): Respect custom OAuth token headers and prefixes for client credentials authentication.

3.88.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 reference.md section, snippets and docs comments.

(fix): Positional path parameters that an example does not supply are now rendered with their client-default value instead of undefined, so generated snippets typecheck.

3.88.1

(fix): Endpoints that disable retries (x-fern-retries: { disabled: true } in OpenAPI, or retries: { disabled: true } in a Fern definition) now always emit maxRetries: 0, overriding both client-level and request-level maxRetries. Previously this configuration was only honored by the Python SDK generator and was silently ignored in TypeScript.

3.88.0

(fix): With respectOptionalRequestBody enabled, examples that omit an optional request body are no longer dropped from the generated reference, snippets, and docs comments.

(feat): Add respectOptionalRequestBody. With it enabled, an endpoint whose request body the API does not require takes an optional body parameter — refund(id: string, request?: RefundRequest) — keeping the body’s own type rather than widening it to RefundRequest | undefined, and sending no body when the caller omits it. Examples that supply no body render as client.refund(id), and the generated wire test for such an example no longer asserts a request body. Defaults to false, so signatures and snippets are unchanged until you opt in.

3.87.4

(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 docstrings, the snippets and the generated tests.

3.87.3

(fix): Fix dynamic snippets emitting a dangling argument delimiter (e.g. client.refunds.refund("id", )) when an example omits the request body.

3.87.2

(fix): Send the caller’s request when fetching the first page of a next_uri/next_path paginated endpoint. Previously the first page was requested through the same headers-only helper used for subsequent pages, so query parameters, request bodies and requestOptions.queryParams were silently dropped (and the unused request parameter was emitted as _request). Subsequent pages keep using the next URL as-is.

3.87.1

(fix): The appInfo User-Agent helper emitted when allowUserAgentAppInfo is enabled no longer uses a control-character regex range, so generated SDKs lint cleanly under rules such as Biome’s noControlCharactersInRegex without a suppression comment. Escaping behavior is unchanged.

3.87.0

(feat): Add a requireBaseUrl config option. When enabled, baseUrl is a required client option and environment is optional, for APIs whose users always pass an explicit server URL. Generated snippets, README, and wire tests use baseUrl accordingly. The option is ignored for APIs with multiple base URLs, whose clients resolve each URL from environment. Defaults to false, so existing SDKs are unchanged.

3.86.0

(feat): Add opt-in allowUserAgentAppInfo config. When enabled, generated clients accept an appInfo: { name, version?, comment? } option whose product token is appended to the User-Agent header ({sdk}/{version} ... {product}/{product-version} ({comment})), following RFC 9110. Disabled by default, so existing generated output is unchanged, and it composes with includePlatformHeaders, the user-agent template config, and the default {package}/{version} value. Caller-supplied values are token-encoded.

3.85.2

(fix): Respect the user-agent config template when includePlatformHeaders is enabled. The structured User-Agent previously always used the npm package name and version, ignoring the configured template.

3.85.1

(fix): Fixed the generated wire tests for endpoints that use offset pagination. The tests asserted page.hasNextPage() was true for every example, but the generated client reports no next page when the response returns fewer items than the requested page size (the pagination step), or when the response’s has-next-page property is false. The wire test now evaluates the same condition against the mocked response, and only asserts hasNextPage()/getNextPage() when a next page is actually expected.

3.85.0

(feat): Add an opt-in optional-auth config flag. When enabled, client auth parameters are made optional even when the API spec mandates auth on all endpoints (isAuthMandatory=true), so the client can be constructed without credentials. Requests are then sent unauthenticated when no credentials are supplied, instead of throwing from the auth provider. Useful for hand-maintained wrapper clients that authenticate via external means (e.g. cloud provider credentials). Defaults to false, so existing behavior is unchanged.

3.84.0

(feat): Generated error classes now redeclare the body property with the error’s typed body (e.g. public declare readonly body: MyErrorBody;) instead of inheriting body?: unknown from the base API error class. This makes err.body.<field> type-check in catch blocks without casting when the error has a declared body schema.

3.83.2

(fix): Global headers declared with an env now fall back to that environment variable at runtime. The resolution order for a global header is the explicit option, then the environment variable, then the client default.

3.83.1

(fix): Scope the passthrough client.fetch() auth headers to the configured base URL. Auth headers are now only attached when the resolved request URL has the same origin as the configured base URL (or environment), so passing an absolute cross-origin URL into the passthrough fetch escape hatch no longer leaks the SDK’s credentials to an unrelated host. Relative paths and same-origin absolute URLs are unaffected. Passthrough debug logs now redact credentials in the request URL, matching the main fetcher.

3.83.0

(feat): Add support for webhook body-hash binding in the generated verifySignature helper. When a webhook’s signature declares a body-hash binding, the helper now hashes the raw request body and compares it to the hash transmitted separately as a query parameter on the notification URL (e.g. Twilio’s bodySHA256) before verifying the HMAC signature. Both checks must pass. The body-hash algorithm and encoding are independent of the outer HMAC’s.