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.
Quick reference
Section titled “Quick reference”x-speakeasy-unknown-values: allow if needed.How SDKs handle API changes
Section titled “How SDKs handle API changes”Speakeasy SDKs automatically handle common API evolution scenarios without requiring code changes.
New fields
Section titled “New fields”Adding new fields to API responses is always safe. SDKs ignore fields not defined in their model.
type: objectproperties: name: type: string created_at: type: string format: date-time updated_at: # New field - ignored by older SDKs type: string format: date-timeNew enum values
Section titled “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 - pendingWhen the API adds a new value (e.g., suspended), each language handles it differently:
// Unknown values are captured in a type-safe wrapperconst status = response.status;// Type: "active" | "inactive" | "pending" | Unrecognized<string>// Check for unknown valuesif (typeof status === "object") {console.log("Unknown status:", status.value);}New response codes
Section titled “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
Section titled “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
Section titled “Configuring SDK resilience”Generator options enable additional forward compatibility features. These are configured in gen.yaml.
typescript:forwardCompatibleEnumsByDefault: trueforwardCompatibleUnionsByDefault: tagged-onlylaxMode: laxunionStrategy: populated-fieldsForward-compatible enums
Section titled “Forward-compatible enums”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>Forward-compatible unions
Section titled “Forward-compatible unions”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 }Lax mode (TypeScript)
Section titled “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.
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.
Smart union deserialization
Section titled “Smart union deserialization”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.
Evolving APIs safely
Section titled “Evolving APIs safely”Best practices for making changes without breaking existing SDK users.
Deprecating fields
Section titled “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:
- Mark the field as optional first
- Add deprecation notices
- Allow time for users to update
- Remove the field after a suitable deprecation period
Forward-compatible union patterns
Section titled “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
Section titled “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
Section titled “Adding defaults”When making required fields optional, include default values:
properties: status: type: string default: "active"Detecting breaking changes
Section titled “Detecting breaking changes”The OpenAPI diff tool identifies potential breaking changes:
speakeasy openapi diff --base v1.yaml --revision v2.yamlThis highlights changes like removing required fields, changing field types, or modifying oneOf schemas.
Automatic SDK versioning
Section titled “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.
Related resources
For more information about handling breaking changes, see the breaking changes guide.