A common confusion during a Stainless migration is which file is the source of truth for which thing. The short answer:
- OpenAPI is the source of truth for the API itself: paths, operations, parameters, request bodies, responses, schemas, examples.
- stainless.yml is the source of truth for the SDK shape: package names, resource grouping, method names, auth, README examples, publishing settings, MCP host.
Both files matter. Neither replaces the other. When you migrate, you migrate both, in different ways.
What lives in OpenAPI
If you change it, customer requests change. This includes:
- Endpoint paths and HTTP methods.
- Path, query, header, and cookie parameters.
- Request and response schemas.
securityschemes and thesecuritySchemescomponents block.servers(the base URL).- Operation-level
examples:for parameters and responses.
OpenAPI is portable. Every code generator and docs platform reads it.
What lives in stainless.yml
If you change it, the SDK package shape changes but the API itself doesn't:
organization.name— drives the generated client class name (AcmeAPI).targets.typescript.package_nameandtargets.python.package_name— the npm and PyPI names.targets.*.production_repo— where Stainless pushes generated SDKs.targets.*.publish— how npm / PyPI publishing is wired (auth method, OIDC vs token).resources— the grouping of operations into nested namespaces (e.g.client.messages.sendvsclient.send_message).client_settings.opts.*— auth options, header mapping, env-var conventions.readme.example_requests— the example endpoint and parameters used in the generated README snippet.settings.mcp_server— MCP host configuration.
Bloom reads stainless.yml because every field here represents a decision your team already made about your SDK. Re-asking those questions in a different config language is a switching cost without a benefit.
The two failure modes
Drift between OpenAPI and stainless.yml
If stainless.yml configures a method that no longer exists in OpenAPI, the generator has to do something. Stainless and Bloom both warn; the right action is usually to remove the line from stainless.yml.
A typical drift report looks like:
retrieve_opt_out_status: get /api/v2/contacts/opt-out/{phone_number}is configured instainless.ymlbut missing from OpenAPI. (1 method)
Trying to encode SDK shape in OpenAPI
Some teams try to encode "what the TypeScript class looks like" using x-* extensions in OpenAPI. This works in the short term but ties the spec to one generator. The portable approach is to keep the SDK shape in stainless.yml (or whichever generator config you choose) and keep OpenAPI clean.
How Bloom uses both during migration
The config audit page walks every field in stainless.yml against a migration decision: what to preserve, what to review, what Bloom regenerates from the same config. The migration report runs:
- Parse OpenAPI → operation tree, schemas, security.
- Parse
stainless.yml→ SDK shape, package names, README example. - Cross-check → flag drift (configured methods missing from OpenAPI, etc.).
- Generate TypeScript and Python SDK previews using both.
- Diff against your live Stainless SDK → compatibility report.
The compatibility report is the part that decides whether the migration is reversible. If the diff shows zero missing methods and only signature-level differences flagged for review, the migration is low risk. If it shows missing operations or auth changes, fix stainless.yml (or the spec) and rerun before any package release.
What to do this week
- Read stainless.yml migration — every field, mapped to a decision.
- If you have an old generator config you no longer trust, regenerate it from OpenAPI and diff against the current. Most projects find at least one stale entry.
- Skim the checklist before any package changes.