Skip to content

SDKs

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.

API Change
New field added
Status
Impact
Safe. SDKs ignore unknown fields automatically.
New enum value (open enum)
Status
Impact
Safe. Open enums are the default. Best for SDKs distributed to third parties where API changes are outside consumer control.
New enum value (closed enum)
Status
Impact
Breaking. Closed enums can be useful for API drift detection in test suites. Convert to open enum using x-speakeasy-unknown-values: allow if needed.
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.

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

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

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:

// Unknown values are captured in a type-safe wrapper
const status = response.status;
// Type: "active" | "inactive" | "pending" | Unrecognized<string>
// Check for unknown values
if (typeof status === "object") {
console.log("Unknown status:", status.value);
}

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.

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

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

Language
TypeScript
Enums
Required fields
Smart unions
Unions
Lax coercion
Python
Enums
Required fields
Contact us
Smart unions
Built-in (Pydantic)
Unions
Lax coercion
Built-in (Pydantic)
Go
Enums
Required fields
Smart unions
Unions
Lax coercion
Contact us
Java
Enums
Required fields
Smart unions
Unions
Lax coercion
Contact us
C#
Enums
Required fields
Contact us
Smart unions
Contact us
Unions
Contact us
Lax coercion
Contact us
Ruby
Enums
Required fields
Contact us
Smart unions
Unions
Lax coercion
Contact us
Terraform
Enums
Required fields
Contact us
Smart unions
Contact us
Unions
Contact us
Lax coercion
Contact us
PHP
Enums
Contact us
Required fields
Contact us
Smart unions
Contact us
Unions
Contact us
Lax coercion
Contact us
typescript:
forwardCompatibleEnumsByDefault: true
forwardCompatibleUnionsByDefault: tagged-only
laxMode: lax
unionStrategy: populated-fields

When forwardCompatibleEnumsByDefault is enabled (the default, except for Terraform), 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>

When forwardCompatibleUnionsByDefault is enabled (TypeScript, Python, Go, Java, Ruby), 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 }

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.

Go zero-value defaults

Go SDKs can achieve similar resilience with respectRequiredFields: false, which applies zero-value defaults for missing required fields. However, Go does not perform type coercions like TypeScript’s lax mode.

When unionStrategy is set to populated-fields (the default for new TypeScript, Go, and Ruby 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.

Per-schema overrides

Individual enums and unions can override global defaults using x-speakeasy-unknown-values: allow or x-speakeasy-unknown-values: disallow in the OpenAPI spec. See the TypeScript configuration reference, Ruby configuration reference, or Terraform configuration reference for all available options.

Best practices for making changes without breaking existing SDK users.

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

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.

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

When making required fields optional, include default values:

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

The OpenAPI diff tool identifies potential breaking changes:

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

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

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.

Related resources

For more information about handling breaking changes, see the breaking changes guide.