Speakeasy Logo
Skip to Content

Forward compatibility

Forward compatibility ensures older SDK versions continue to work correctly when APIs evolve by adding new fields, enum values, or other data. This guide covers how Speakeasy SDKs handle these changes automatically and how to configure additional resilience.

Quick reference

API Change
New field added
Status
Impact
Safe. SDKs ignore unknown fields automatically.
New enum value (open enum)
Status
Impact
Safe. With
, SDKs handle new values gracefully.
New enum value (closed enum)
Status
Impact
Breaking. Convert to open enum using
.
Type change
Status
Impact
Breaking. Changing a field's type causes deserialization errors.
Required request field → optional
Status
Impact
Safe. Older SDKs continue sending the field.
Optional request field → required
Status
⚠️
Impact
Depends. Works if clients already send the field; fails otherwise.
Required response field → optional
Status
Impact
Breaking. Older SDKs expect the field and may throw errors.
Required response field → optional with default
Status
Impact
Safe. The API returns a default value when the field is omitted.

How SDKs handle API changes

Speakeasy SDKs automatically handle common API evolution scenarios without requiring code changes.

New fields

Adding new fields to API responses is always safe. SDKs ignore fields not defined in their model.

type: object properties: name: type: string created_at: type: string format: date-time updated_at: # New field - ignored by older SDKs type: string format: date-time

New enum values

APIs often add new enum values over time. The x-speakeasy-unknown-values extension enables SDKs to handle unknown values gracefully instead of throwing errors.

status: type: string x-speakeasy-unknown-values: allow enum: - active - inactive - pending

When the API adds a new value (e.g., suspended), each language handles it differently:

New response codes

Using status code ranges allows APIs to add specific codes without breaking SDKs:

responses: "2xx": description: Success response content: application/json: schema: $ref: "#/components/schemas/SuccessResponse" "4xx": description: Error response content: application/json: schema: $ref: "#/components/schemas/ErrorResponse"

When an API returns an unexpected status code, SDKs match it to the appropriate range (2xx, 4xx, 5xx), parse the response using that range’s schema, and provide access to both the status code and response body.

Unexpected data

SDKs include additional mechanisms for handling unexpected data:

  • Validation errors: Detailed error messages when unexpected data is received
  • OneOf schemas: Attempts to match against known variants before failing
  • Optional fields: Missing optional fields never cause validation errors

Configuring SDK resilience

Generator options enable additional forward compatibility features. These are configured in gen.yaml.

Language
TypeScript
Enums
Unions
Required fields
Lax coercion
Python
Enums
In progress
Unions
In progress
Required fields
Contact us
Lax coercion
Built-in (Pydantic)
Go
Enums
In progress
Unions
In progress
Required fields
Lax coercion
Contact us
Java
Enums
Unions
In progress
Required fields
Lax coercion
Contact us
C#
Enums
Contact us
Unions
Contact us
Required fields
Contact us
Lax coercion
Contact us
Ruby
Enums
Contact us
Unions
Contact us
Required fields
Contact us
Lax coercion
Contact us
PHP
Enums
Contact us
Unions
Contact us
Required fields
Contact us
Lax coercion
Contact us

Forward-compatible enums

When forwardCompatibleEnumsByDefault is enabled (the default for new TypeScript SDKs), enums accept unknown values instead of rejecting the response:

const notification = await sdk.notifications.get(id); // Before: Error: Expected 'email' | 'sms' | 'push' // After: 'email' | 'sms' | 'push' | Unrecognized<string>

Forward-compatible unions

When forwardCompatibleUnionsByDefault is enabled (TypeScript), discriminated unions accept unknown variants:

const account = await sdk.accounts.getLinkedAccount(); // Before: Error: Unable to deserialize into any union member // After: // | { type: "email"; email: string } // | { type: "google"; googleId: string } // | { type: "UNKNOWN"; raw: unknown }

Lax mode (TypeScript)

When laxMode is set to lax (the default for new TypeScript SDKs), the SDK handles missing or mistyped fields by applying zero-value defaults and type coercions:

  • Missing required strings become ""
  • Missing required numbers become 0
  • Missing required booleans become false
  • Missing required dates become Date(0) (Unix epoch)
  • Missing required bigints become 0n
  • Missing required literals become the literal value
  • String "true" and "false" are coerced to booleans
  • Numeric strings are coerced to numbers
  • Numbers are coerced to dates (treated as milliseconds)
  • Strings are coerced to bigints
  • Any non-string value is coerced to string via JSON.stringify()

Lax mode only affects response deserialization and never lies about types.

Smart union deserialization

When unionStrategy is set to populated-fields (the default for new TypeScript and Go SDKs), the SDK picks the best union variant by trying all types and returning the one with the most matching fields. When there’s a tie, it picks the variant with the fewest coerced or inexact fields.

This prevents issues where one union variant is a subset of another and the wrong variant gets selected due to ordering.

Evolving APIs safely

Best practices for making changes without breaking existing SDK users.

Deprecating fields

Mark fields as deprecated before removing them:

properties: name: type: string sku: type: string deprecated: true x-speakeasy-deprecation-message: We no longer support the SKU property.

This keeps fields accessible to older SDKs while new SDKs show deprecation warnings. When removing a field entirely:

  1. Mark the field as optional first
  2. Add deprecation notices
  3. Allow time for users to update
  4. Remove the field after a suitable deprecation period

Forward-compatible union patterns

To create unions that handle future data types, use the oneOf pattern with a string fallback:

oneOf: - { type: "dog" } - { type: "cat" } - { type: string }

This provides strongly typed handling for known variants while gracefully capturing future variants as strings.

Versioning strategies

Common approaches to manage breaking changes:

  • Path-based versioning: /v1/resource, /v2/resource
  • Header-based versioning: Api-Version: 2023-01-01
  • Multiple versions: Maintain multiple API versions during migration periods

Adding defaults

When making required fields optional, include default values:

properties: status: type: string default: "active"

Detecting breaking changes

The OpenAPI diff tool identifies potential breaking changes:

speakeasy openapi diff --base v1.yaml --revision v2.yaml

This highlights changes like removing required fields, changing field types, or modifying oneOf schemas.

Automatic SDK versioning

Speakeasy manages SDK versions based on the nature of changes:

  • Patch: Non-breaking changes
  • Minor: Backward-compatible additions
  • Major: Breaking changes

When generating SDKs, Speakeasy detects breaking changes and provides clear notifications about what changed and how to handle the transition.

Last updated on