Skip to content
FeedbackDashboard

Fix your OpenAPI spec with transforms

Use spec transforms to fix auto-generated specs or customize your OpenAPI without modifying the source

Spec transforms modify your OpenAPI spec during SDK generation without editing the source file. Define transforms in your stainless.yaml config file and Stainless applies them automatically every time it generates your SDKs.

Common uses for transforms:

  • Fix incorrect types in auto-generated specs
  • Add missing required fields while you fix the source spec
  • Rename properties to avoid language conflicts
  • Add Stainless metadata (x-stainless-naming, x-stainless-skip)
  • Remove incorrect defaults or deprecated fields

Suppose your API auto-generates a spec where account IDs are typed as number, but they’re actually strings. Add a transform to fix it:

stainless.yaml
openapi:
transformations:
- command: update
reason: "Account IDs are strings, not numbers"
args:
target: '$.paths..parameters[?(@.name == "account_id")].schema.type'
value: "string"

The target uses JSONPath to find all parameters named account_id. The transform replaces their type with "string". The reason documents why this transform exists.

openapi:
transformations:
# Fix specific property
- command: update
reason: "User age should be integer"
args:
target: "$.components.schemas.User.properties.age.type"
value: "integer"
# Fix all instances across endpoints
- command: update
reason: "All limit parameters should be int32"
args:
target: '$.paths..parameters[?(@.name == "limit")].schema'
value:
type: "integer"
format: "int32"

Use append for temporary fixes. Fails if the field exists, which alerts you when the spec is fixed.

openapi:
transformations:
- command: append
reason: "Spec missing id field (TODO: fix in source)"
args:
target: "$.components.schemas.User.properties"
value:
id:
type: "string"
format: "uuid"
- command: append
reason: "Mark id as required"
args:
target: "$.components.schemas.User.required"
value: "id"

When the spec includes the id field, your transform will fail with “Properties already exist”, prompting you to remove it.

openapi:
transformations:
# Rename schema
- command: move
reason: "Normalize to PascalCase"
args:
from: "$.components.schemas.user_response"
to: "$.components.schemas.UserResponse"
# Rename property
- command: move
reason: "Fix snake_case to camelCase"
args:
from: "$.components.schemas.User.properties.first_name"
to: "$.components.schemas.User.properties.firstName"

Use merge for permanent additions like Stainless extensions:

openapi:
transformations:
# Language-specific naming
- command: merge
reason: "Avoid shadowing Python's timeout builtin"
args:
target: "$.components.schemas.Request.properties.timeout"
value:
x-stainless-naming:
python:
method_argument: "api_timeout"
# Skip parameters for specific SDKs
- command: merge
reason: "Terraform doesn't need pagination"
args:
target: '$.paths..parameters[?(@.name == "limit" || @.name == "offset")]'
value:
schema:
x-stainless-skip: ["terraform"]
openapi:
transformations:
# Remove specific keys
- command: remove
reason: "Remove incorrect default"
args:
target: "$.components.schemas.User.properties.middleName"
keys: ["default"]
# Remove entire nodes
- command: remove
reason: "Remove all deprecated parameters"
args:
target: '$.paths..parameters[?(@.deprecated == true)]'
# Remove examples
- command: remove
reason: "Remove auto-generated examples"
args:
target:
- "$.paths.*.*.examples"
- "$.components.schemas.*.example"

Use {{value}} templating to modify existing strings:

openapi:
transformations:
- command: update
reason: "Mark deprecated endpoint"
args:
target: "$.paths./v1/users.get.summary"
value: "{{value}}\n\n**DEPRECATED**: Use /v2/users"
template: true

Use matches_schema to find all schemas that match a template and replace them with a $ref.

openapi:
transformations:
- command: update
reason: "Replace duplicate error schemas with $ref"
args:
target:
- matches_schema: $.components.schemas.ErrorTemplate
ignore: $.components.schemas.ErrorTemplate
value:
$ref: "#/components/schemas/Error"

This finds all schemas that exactly match ErrorTemplate and replaces them with a reference. The ignore field prevents replacing the template itself.

Transforms run in order, so later transforms see the effects of earlier ones.

Extract an inline schema to components.

openapi:
transformations:
# Copy inline schema to components
- command: copy
reason: "Extract error schema for reuse"
args:
from: "$.paths./users.get.responses.404.content.application/json.schema"
to: "$.components.schemas.NotFoundError"
# Replace inline schema with $ref
- command: update
reason: "Use $ref instead of inline"
args:
target: "$.paths./users.get.responses.404.content.application/json.schema"
value:
$ref: "#/components/schemas/NotFoundError"

Transforms use JSONPath Plus for targeting. Common patterns:

# Exact path
target: "$.components.schemas.User.properties.id"
# Wildcard (all schemas)
target: "$.components.schemas.*"
# Recursive (all parameters anywhere)
target: "$..parameters"
# Filter expressions
target: '$.paths..parameters[?(@.name == "account_id")]'
target: '$.components.schemas[?(@.type == "object")]'
# Multiple targets
target:
- "$.components.schemas.User"
- "$.components.schemas.Admin"
  • Use append for temporary fixes (fails when the spec includes the field)
  • Use merge for permanent overrides such as x-stainless metadata
  • Document your reasons - future you will thank you
  • Test one transform at a time
  • Prefer explicit paths over broad wildcards when possible
  • Keep transforms focused - one transform should do one thing

“JSONPath did not match any nodes”

Your query didn’t find anything. Check for typos, verify the path exists, or simplify your query to test.

“Properties already exist”

You’re using append on a property that exists. Either remove the transform (if the spec was fixed) or switch to merge if you want to overwrite.

Transform does nothing

Check the order. If one transform renames a field and another targets the old name, the second won’t find anything. Adjust the order or update the target path.

For complete documentation of all commands and options, see the Transforms Reference.