Forward compatibility
This guide explains how Speakeasy-generated SDKs maintain forward compatibility when APIs evolve. Forward compatibility ensures older SDK versions continue to work correctly when the API adds new fields, enum values, or other data.
Forward compatibility at a glance
Forward compatibility ensures older SDK versions continue to work when APIs evolve. The table below shows which changes are safe and which require special handling.
Common forward compatibility scenarios
This section covers the most frequent scenarios encountered when evolving APIs and how Speakeasy handles them to maintain forward compatibility.
Handling new fields
Adding new fields to API responses is safe because older SDK versions ignore these fields. When an API response includes fields not defined in the SDK’s model, these fields are simply not deserialized.
Older SDK versions will continue to work without errors, ignoring the updated_at field. This allows APIs to evolve by adding new data without breaking existing integrations.
Handling new enum values
APIs often need to add new enum values over time. Speakeasy provides the x-speakeasy-unknown-values extension to handle this gracefully.
status:
type: string
x-speakeasy-unknown-values: allow
enum:
- active
- inactive
- pendingWhen the API adds a new enum value (e.g., suspended), older SDK versions handle it according to the language:
- TypeScript
- Python
- Go
- Java
This prevents runtime errors when new enum values are encountered, allowing APIs to add new states without breaking existing clients.
Handling unexpected data
Speakeasy-generated SDKs include built-in mechanisms to handle unexpected data:
-
Validation errors: SDKs provide detailed validation errors when unexpected data is received, making debugging easier
-
OneOf schemas: When using
oneOfschemas, SDKs can handle evolving data structures by attempting to match against known variants -
Optional fields: Fields marked as optional in the OpenAPI spec won’t cause validation errors if missing
Handling unexpected response codes
APIs evolve over time and may introduce new response codes. Speakeasy-generated SDKs are designed to handle unexpected response codes gracefully:
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"Benefits of status code ranges
-
Flexible status codes: Using
2xxand4xxpatterns allows APIs to add new specific status codes (like201or429) without breaking existing SDKs -
Consistent error handling: All error responses follow the same structure, making it easier to handle new error types
-
Graceful degradation: Even when encountering unexpected status codes, SDKs can still extract useful information from the response
When an API returns a status code that wasn’t explicitly defined in the original specification, Speakeasy SDKs:
- Match it to the appropriate range (
2xx,4xx,5xx) - Parse the response using the defined schema for that range
- Provide access to both the status code and response body
Advanced forward compatibility techniques
These advanced techniques help maintain forward compatibility in more complex scenarios.
Deprecating fields
When evolving APIs, deprecating fields is a common necessity. Speakeasy provides extensions to handle field deprecation gracefully while maintaining forward compatibility:
properties:
name:
type: string
sku:
type: string
deprecated: true
x-speakeasy-deprecation-message: We no longer support the SKU property.Benefits of proper deprecation
- Fields remain accessible to older SDK versions
- New SDK versions mark these fields with proper deprecation annotations
- Generated documentation includes deprecation notices
- Developers receive clear guidance on migration
Field removal process
When planning to remove a field entirely:
- Mark the field as optional first
- Add deprecation notices with the
deprecatedkeyword andx-speakeasy-deprecation-message - Allow sufficient time for users to update implementations
- Remove the field only after a suitable deprecation period
Forward-compatible unions
To create forward-compatible unions that can handle new data types added in the future, use the oneOf pattern with a string fallback:
oneOf:
- { type: "dog" }
- { type: "cat" }
- { type: string }Benefits of string fallback
- Provides strongly typed handling for known variants (
dogandcattypes) - Gracefully captures any future variants as string values
- Prevents runtime errors when new variants are introduced
- Allows SDK users to handle unknown variants safely
Language-specific union handling
Each language handles these unions differently: - TypeScript: Uses native
union types with string fallback - Python: Leverages typing.Union with
string fallback - Go: Generates helper methods for both known and unknown
types - Java: Provides type discrimination with generic string handling
TypeScript-specific configuration
TypeScript SDKs have additional generator options that enable forward compatibility and fault tolerance by default. These options work together to ensure your SDK gracefully handles API evolution.
typescript:
forwardCompatibleEnumsByDefault: true
forwardCompatibleUnionsByDefault: tagged-only
laxMode: lax
unionStrategy: populated-fieldsForward-compatible enums
When forwardCompatibleEnumsByDefault is enabled (the default for new TypeScript SDKs), enums used in responses accept unknown values instead of rejecting the response. Unknown values are captured in a type-safe Unrecognized<string> wrapper:
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 (the default for new TypeScript SDKs), discriminated unions accept unknown values. Unknown variants are captured with their raw data and accessible via the UNKNOWN discriminator value:
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
When laxMode is set to lax (the default for new TypeScript SDKs), the SDK gracefully handles missing or mistyped fields in responses by applying zero-value defaults and type coercions. This ensures one missing field doesn’t break the entire response:
- Missing required strings become
"" - Missing required numbers become
0 - Missing required booleans become
false - String
"true"and"false"are coerced to booleans - Numeric strings are coerced to numbers
Lax mode only affects response deserialization and never lies about types. The SDK types remain accurate.
Smart union deserialization
When unionStrategy is set to populated-fields (the default for new TypeScript SDKs), the SDK uses a smarter algorithm to pick the best union variant. Instead of trying each type in order and returning the first valid match, it tries all types and returns the one with the most matching 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 the global defaults using x-speakeasy-unknown-values: allow or x-speakeasy-unknown-values: disallow in your OpenAPI spec. See the TypeScript configuration reference for all available options.
Guard-rails for breaking changes
Speakeasy provides several tools to detect and prevent breaking changes:
Version your API
Create a versioning strategy for your API to manage breaking changes:
- Use path-based versioning (e.g.,
/v1/resource,/v2/resource) - Include version in request headers (
Api-Version: 2023-01-01) - Maintain multiple API versions simultaneously during migration periods
Add defaults for optional fields
When making required fields optional:
- Always include default values to maintain backward compatibility
- Document the default behavior clearly
- Use the
defaultproperty in your OpenAPI specification:properties: status: type: string default: "active"
Open your enums
Convert closed enums to open enums using the Speakeasy extension:
status:
type: string
x-speakeasy-unknown-values: allow
enum:
- active
- inactive
- pendingUse the OpenAPI diff tool
The OpenAPI diff tool identifies potential breaking changes between API specification versions:
speakeasy openapi diff --base v1.yaml --revision v2.yamlThis highlights changes that might break backward compatibility, such as:
- Removing required fields
- Changing field types
- Modifying oneOf schemas
SDK version management
Speakeasy automatically manages SDK versioning based on the nature of changes:
- Patch version for non-breaking changes
- Minor version for backward-compatible additions
- Major version for breaking changes
Breaking change notifications
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.
Last updated on