> For the complete documentation index, see [llms.txt](/llms.txt)

# Migrate from Stainless

Stainless is winding down its hosted platform. You keep your generated code — but the
pipeline that regenerates it on every spec change is going away. Glotto picks up where it
leaves off: a **superset of Stainless's SDK languages** (all nine, plus React Native, Swift,
Rust, Dart, Elixir, and Terraform), the same **Astro-based docs** approach, the same five
**spec transforms**, release PRs across **GitHub, GitLab, and Bitbucket**, and a multi-mode
**MCP server including Code Mode and dynamic tools** — the architecture Stainless pioneered.

More than the feature list, you land on the part that's hard to rebuild in-house: every
regeneration is **verified, not just emitted** — compile and contract statuses, per-target
drift, and custom-code preservation, recorded in a
[verification report](/docs/verification-report) delivered to CI, the release PR, and the
console. Generating a client is the easy part; keeping every artifact provably in agreement
with a spec that changes weekly is what the pipeline is for.

The conversion is mechanical, complete, and reported line by line.

## What Glotto needs from you

- Your `stainless.yml`.
- Your **OpenAPI document**. Stainless stores the spec out-of-band (uploaded or fetched on
  their side), so export it from your repo or dashboard first — the migrated config needs a
  real `openapi.source` path.

Optionally, the OpenAPI document's Stainless **extensions** (`x-stainless-*`) come across too —
see [Bring your renames across](#bring-your-renames-across) and
[Bring your Terraform attribute shaping across](#bring-your-terraform-attribute-shaping-across)
below. Your original document is never modified in place.

## Run it

The conversion is in the published CLI — nothing to request, no account needed:

```sh
npm i -g @glotto/cli
glotto migrate stainless --openapi openapi.yaml
```

Pass `--openapi` whenever you have the document. Without it your `transforms` are translated
from the shape of each command's value; with it every target is resolved against your real
document using the same selector `glotto generate` uses, so a target that matches nothing is
reported **while you are migrating** rather than breaking a build later. The flags are listed
under [`glotto migrate`](/docs/cli#glotto-migrate).

## What you get back

A `glotto.yml`, plus a **migration report** saying what happened to every key.

The conversion copies every recognized key, normalizes what differs, and is **honest about the
rest**: nothing is silently invented or silently lost.

A config that declares an **AsyncAPI or GraphQL input block** migrates onto Glotto's native
`asyncapi` / `graphql` input blocks (Glotto accepts these as first-class inputs alongside
OpenAPI). `glotto.yml` takes exactly one input source per config, so when several are declared
one is kept (`openapi` over `asyncapi` over `graphql`) and each dropped block is named in the
migration report — the other surface becomes a separate `glotto.yml`.

## Bring your renames across

Stainless keeps per-language member, parameter, and enum-value renames **inside the OpenAPI
document** as the `x-stainless-naming` (property/member renames — e.g. `public` → `isPublic` for
Java), `x-stainless-param` (method-parameter renames — e.g. dodging a Python-reserved argument),
and `x-stainless-renameMap` (enum-value renames — e.g. `Ok` for the value `200`) extensions, and
shapes models with `x-stainless-model` / `x-stainless-model-skip` (name a schema's model, or keep it
inline) and inputs with `x-stainless-soft-required` (demanded by the SDK signature, not by the
protocol) and `x-stainless-const` (supplied by the SDK, so the caller never passes it). Glotto keeps
the same intent in `glotto.yml` instead, under the `naming`, `parameter_naming`, `enum_naming`,
[`models`](/docs/glotto-yml-model-shaping#models), [`soft_required`](/docs/glotto-yml-model-shaping#soft_required), and
[`auto_populate`](/docs/glotto-yml-model-shaping#auto_populate) blocks. Supply the OpenAPI document
alongside your `stainless.yml` and those extensions are read and translated for you:

- `x-stainless-naming` on a schema property → `naming.<Model>.<property>.<target>`.
- `x-stainless-param` on an operation parameter → `parameter_naming.<operationId>.<param>.<target>`.
- `x-stainless-renameMap` on an enum schema → `enum_naming.<Model>.<wireValue>`.
- `x-stainless-model` / `x-stainless-model-skip` on a schema → [`models.<Model>.name`](/docs/glotto-yml-model-shaping#models)
  / `models.<Model>.inline`.
- `x-stainless-nominal` on an enum schema → [`enums.<Model>.nominal`](/docs/glotto-yml-model-shaping#enums).
- `x-stainless-enum-deprecations` on an enum schema → [`enums.<Model>.deprecated_values`](/docs/glotto-yml-model-shaping#enums).
  The extension is an array **parallel to the schema's `enum`**, so each element deprecates the
  member at the same position. A `false` element means "not deprecated" and is skipped; a `true`
  element means "deprecated, no message", which Glotto cannot express — every deprecation carries a
  message — so it is dropped with a warning telling you which member to add one for.
- `x-stainless-soft-required` on an operation parameter, a `requestBody`, or a request-body schema
  property → [`soft_required.<operationId>`](/docs/glotto-yml-model-shaping#soft_required)`.parameters` / `.body` /
  `.body_fields`. A `$ref`'d body schema is followed into `components`, so a shared request model's
  annotation reaches every operation that sends it.
- `x-stainless-const` on an operation parameter or a request-body schema property →
  [`auto_populate.<operationId>`](/docs/glotto-yml-model-shaping#auto_populate)`.parameters` / `.body_fields`.
  There is no whole-`requestBody` form: "the one legal value" is a property a scalar enum position
  has and a body does not.

Target-language keys normalize to Glotto slugs just like `targets` (`node` → `typescript`, …).
Anything that can't be mapped — an unsupported language, a parameter rename on an operation
with no `operationId`, or a malformed value — is dropped and listed in the migration report,
and the report's summary counts how many renames were ingested. Without `--openapi`, migration
behaves exactly as before.

Note that `renameMap` is written the other way round in each tool: Stainless keys by the new name
(`Ok: 200`), Glotto keys by the wire value (`"200": Ok`). The migrator flips each pair for you. A
rename pointing at a value the schema's own `enum` doesn't declare is dropped with a warning rather
than silently carried over.

> **Member renames on TypeScript, React Native, and Python.**
> `parameter_naming` is honored by every SDK. **Member renames (`naming`) apply to 11 of the 13
> languages.** TypeScript and React Native keep the wire name (their DTOs are transparent — the
> generated type *is* the JSON shape, so a member rename would need a (de)serialization remap they
> don't carry), and Python applies member renames only in pydantic mode (`targets.python.pydantic`),
> not the default dataclass output. `migrate` still ingests the renames for these targets, and
> `glotto generate` warns (`GLOTTO_CONFIG_RENAME_NOT_APPLIED`) where one won't take effect — so you
> can prune them or switch Python to pydantic mode.

## Bring your Terraform attribute shaping across

Stainless expresses per-attribute Terraform behavior — whether an attribute is server-assigned,
whether a collection round-trips as a set instead of a list, whether a field must always be sent
on update — as three more OpenAPI extensions: `x-stainless-terraform-configurability`,
`x-stainless-collection-type`, and `x-stainless-terraform-always-send`. Glotto expresses the same
three facts the same way, as [spec extensions](/docs/terraform#attribute-shaping) rather than
`glotto.yml` keys — so translating them means rewriting the spec itself, not the config. Ask for
that and you get back a translated **copy of your OpenAPI document**, with everything else in it
untouched and your original left unmodified:

- `x-stainless-terraform-configurability` on a schema **property** → `x-glotto-terraform-configurability`
  on the same property, value carried through verbatim (`required` / `optional` / `computed` /
  `computed_optional`).
- `x-stainless-terraform-always-send` on a schema **property** → `x-glotto-terraform-always-send`
  on the same property, value carried through verbatim (`true`).
- `x-stainless-collection-type` on an **array schema node itself** (never its `items`) →
  `x-glotto-collection-type` on the same node, value carried through verbatim (`list` / `set`).

Each key is renamed **where it sits** — nothing is moved, and a `$ref` is never followed. That
matters for a property spelled as a pointer: Glotto reads the two property-level facts off the
property exactly as it appears under `properties`, so a `{ $ref: …, x-stainless-terraform-configurability: computed }`
property keeps its stamp beside the `$ref` and works. Collection type is the exception, because
Glotto only reads it from a node that itself declares `type: array` — so
`x-stainless-collection-type` beside a `$ref` is **reported rather than renamed**: the translated
key would look right in your document and be read by nothing. Move it onto the array (the component
the pointer names, or the property spelled inline) and re-run. A value outside the sets
above — a `false` always-send, an unrecognized configurability, an annotation on a non-mapping
node — is **reported and left unstamped** rather than carried through: an unrecognized value would
be silently inert at generate time, so stamping it through would be worse than leaving it for you
to fix by hand. An `x-glotto-*` your document already carries is never overwritten — an identical
value is a silent no-op, so re-running the migration over its own output changes nothing, and a
differing value keeps yours and is reported, naming both.

The spec rewrite is **opt-in**. Without it the migration behaves exactly as it does otherwise —
byte-identical output, and your OpenAPI document untouched.

The rewrite keeps a YAML document's comments and key order; a JSON document is rewritten as JSON at
its own indentation. Two things are worth knowing rather than discovering: a comment trailing a
mapping key moves onto its own line, and a spec written as a multi-document YAML stream is refused
outright rather than partially rewritten. Rewriting your spec **in place** is possible — it is the
translated copy written back over its source — and it is subject to the same overwrite guard as
every other output, so it never happens by accident.

Often you need neither flag: Glotto reads the standard OpenAPI `readOnly` (→ a `Computed`
attribute) and `uniqueItems` (→ a set rather than a list) directly, so a spec that already declares
those gets the right shape with no annotation or migration at all. The spec rewrite is for the
third fact, `x-stainless-terraform-always-send`, which has no standard OpenAPI spelling, and for
the other two where your spec doesn't already carry the standard keywords.

## Read the migration report

The output ends with a report listing exactly what needs your attention:

- **Unmapped (dropped)** — Stainless keys with no Glotto equivalent are dropped from the
  output and each one is reported with its dotted path. Unknown sub-fields are never
  silently copied.
- **Placeholders (needs review)** — required Glotto fields that can't be derived from the
  Stainless input are emitted as `TODO-…` placeholder values and flagged. The common one is
  `openapi.source`: point it at the OpenAPI document you exported above.

Then finish the loop:

```sh
glotto generate
```

A migrated config with resolved placeholders validates cleanly, and `generate` produces your
SDKs across every configured target. See the [pipeline](/docs/pipeline) for what runs under
the hood.

## Mapping reference

What translates automatically, what needs review, and what is dropped. Every key the
conversion handles has a row here — the table is the complete disposition
list, so anything not named below falls under the catch-all in its final row.

| Stainless surface | Disposition |
| --- | --- |
| `organization` (`name`, `contact`, `homepage`) | Translated. Missing required `name`/`contact` become `TODO-…` placeholders. |
| `targets` | Translated, with language names normalized to Glotto slugs (`node`/`js`/`ts` → `typescript`, `react-native` → `react_native`, `c#`/`dotnet` → `csharp`, `golang` → `go`, …). Per-target publish config carries across opaquely. An unsupported language is dropped with a warning. |
| `targets.<go>.module_path` / `targets.<java\|kotlin>.coordinates` | Translated to the recognized code-identity fields: `module_path` → `namespace` (the `go.mod` module path); `coordinates` (`group:artifact`) → `namespace` (= group) + `package_name` (= artifact). A malformed `coordinates` is dropped with a warning. See [`namespace`](/docs/glotto-yml#targets). |
| `targets.openapi` (`production_repo`) | Translated onto Glotto's `spec_repo` target — not a target language, so it is handled separately from the rest of `targets` above. `production_repo` becomes `targets.spec_repo.repo`. The Glotto slug is `spec_repo` rather than the vendor's `openapi`, since Glotto ingests OpenAPI, AsyncAPI and GraphQL and `openapi` would misname two of the three (and would sit confusingly beside the top-level `openapi:` input block). A missing or blank `production_repo` drops `targets.openapi` entirely with a warning; any other `targets.openapi.*` key has no Glotto equivalent and is dropped with its own warning. |
| `resources` / `methods` / `subresources` | Translated deeply, recursing through `subresources`. String methods (`get /path`) carry verbatim; method objects keep the `endpoint`, `paginated`, `streaming`, `polling` flags. The `mcp`, `type`/`to`, `deprecated` and `positional_params` keys are consumed into top-level blocks — see the rows below. Any other method flag is dropped with a warning. |
| `client_settings` | `default_timeout`, `retry` (`max_attempts`, `initial_delay`, `max_delay`, `jitter`), and `auth` (`scheme`, `env_var`) translate. `base_url` folds into `environments.production`. |
| `targets.<node>.options.mcp_server` | Translated: `package_name` → [`mcp.package_name`](/docs/glotto-yml-project-settings#mcp). `enable_all_resources: true` matches Glotto's register-everything default (consumed silently); `enable_all_resources: false` and `generate_cloudflare_worker` are dropped with notes. Narrow tools at runtime with the server's `--resource`/`--operation`/`--tag` filters. For `generate_cloudflare_worker`, **hosting and authorization carry over separately**: the emitted server already ships an embedded `fetchHandler` for Workers-style hosts and hosted MCP Cloud serves it managed, while Stainless's worker is *also* an OAuth authorization server (consent flow, API-key collection, token vault, and a generated client-properties input UI). If your API declares an OAuth `authorizationCode` flow, [`mcp.upstream_oauth`](/docs/mcp-server) covers that half. If it does not, Glotto emits no authorization server by design — [forward the caller's credential](/docs/mcp-server) instead. |
| `resources.*.methods.*.mcp` | Translated: a curated `description` → `mcp.operations.<tool>.description` and a curated `tool_name` → `mcp.operations.<tool>.name`, both keyed by the canonical `<resource>_<method>` operation identity (the rename flows through the single tool-name derivation, so the emitted server, hosted gateway catalog, and RBAC policies stay in lockstep). `mcp: true` matches the default (silent); `mcp: false` is dropped with a pointer note (narrow tools at runtime with the server's filters); a `tool_name` that isn't a valid MCP tool name (`^[a-zA-Z0-9_-]{1,64}$`) or repeats an already-mapped rename is dropped with a warning. |
| `resources.*.methods.*.type: alias` + `to` | Translated to the top-level [`aliases`](/docs/glotto-yml-api-surface#aliases--deprecated) block — `aliases.<method> = <to>` — at any subresource depth, so the superseded method name keeps working and breaking-change detection stops reporting the rename as breaking. Both names carry **verbatim**: they resolve at generate time against your spec, not at migrate time, and an entry that matches no operation is reported then (a `--openapi`-resolved operationId would go stale the next time you rename one). A `type:` other than `alias`, a `type: alias` with no `to:`, a bare `to:`, a self-alias (`to:` naming its own method — it could never materialize), and a second method colliding on an already-mapped alias name are each dropped with a warning. |
| `resources.*.methods.*.deprecated` | Translated to the top-level [`deprecated`](/docs/glotto-yml-api-surface#aliases--deprecated) block, keyed by the same method name — so deprecating an alias by its own name works, which is the intended migration shape (the old name keeps working *and* warns). A message string carries verbatim; a per-language map has its language keys normalized to Glotto slugs. Glotto has no message-less deprecation, so a map with no `default` gets one **synthesized from its first message** rather than being dropped — listed under *Placeholders (needs review)* in the report, since nothing was dropped and the wording is yours to confirm, and a `deprecated: true` is dropped with a warning naming the missing message. An unsupported language key or a non-string message is dropped with a warning; its well-formed siblings still map. |
| `resources.*.methods.*.positional_params` | Translated to the top-level [`positional_params`](/docs/glotto-yml-client-behavior#positional_params) block — **needs `--openapi`**. Glotto keys that block by `operationId`, and a Stainless method key (`retrieve`) is Stainless's name for the method inside its own resource tree, not the operationId; so the migrator reads the method's `endpoint:` for the verb and path and takes the `operationId` the OpenAPI declares there. Without `--openapi` — or when that endpoint is absent from the document, or its operation declares no `operationId` — the order is dropped with a warning naming the remedy. Stainless's key is not per-language and Glotto's is, so one declared order is emitted under **every target you configured**; a language whose SDK takes no positional arguments (Ruby, whose path arguments are keyword arguments) simply ignores it. The order carries verbatim, and a short list stays short — Glotto reads it as a partial prefix, so arguments you did not name keep their derived positions behind the ones you did. The whole order is dropped with a warning — never partly applied — when it names anything that is not one of that endpoint's path parameters: Stainless also promotes body and query parameters to positional arguments, which Glotto's block does not express. A non-list value, a non-string or empty name, a repeated name, and an empty list are each dropped with a warning. |
| `environments` | Translated — plain-URL and `{ url }` forms both resolve. If none survive, a placeholder `production` entry is emitted. |
| `openapi.source` | Usually a **placeholder**: Stainless uploads the spec out-of-band, so the path can't be derived. Point it at your exported OpenAPI document. |
| `openapi.code_samples.formats` | Translated. |
| `asyncapi` (top-level) | Translated onto Glotto's native [`asyncapi`](/docs/glotto-yml) input block — both the mapping form and the `asyncapi: ./events.yaml` string shorthand, which becomes `asyncapi.source`. A missing source becomes a `TODO-path-to-asyncapi-spec` placeholder; an unrecognized sub-key, or a value that is neither a mapping nor a path, is dropped with a warning at its own dotted path. **Only one input source survives per config** — see the row below. |
| `graphql` (top-level) | Translated onto Glotto's native [`graphql`](/docs/glotto-yml) input block, with the Stainless spellings normalized: `schema` → `source` and `autogenerate_operations` → `autogenerate`; `operations` carries verbatim. The `graphql: ./schema.graphql` string shorthand becomes `graphql.source`. An alias that duplicates the canonical key it maps to is dropped with a warning and never overwrites it; a missing source becomes a `TODO-path-to-graphql-schema` placeholder. **Only one input source survives per config** — see the row below. |
| Several input blocks at once (`openapi` + `asyncapi` + `graphql`) | `glotto.yml` accepts exactly one input source, so the migrator keeps one by presence precedence — `openapi` over `asyncapi` over `graphql` — and each losing block is **dropped with a warning naming the winner**, never silently. Migrate the other surface into a separate `glotto.yml`. |
| `transforms` | Read from **both** spellings — the top-level key and `openapi.transforms`, where Stainless's own examples put it. The five shared semantic ops translate 1:1 — `rename_schema`, `flatten_composition`, `dedupe_inline_objects`, `extract_ref`, `fix_invalid_example`. Stainless's **generic JSONPath commands** translate into [`apply_overlay`](/docs/transforms#apply_overlay) entries, one entry per command, in their original order, with each command's `reason` carried as the action's `description`: `remove` becomes `remove: true`; `append` becomes an `update` (Overlay appends when the target selects an array); `update` and `merge` become an `update` (Overlay's `update` *is* a recursive merge). A command whose value is **not an object** targets a scalar, which Overlay deliberately leaves undefined — so the target is shortened by one segment and the value wrapped in that key: `….schema.type` with `"string"` becomes `….schema` with `update: { type: string }`. **Pass `--openapi` and the translation gets exact**: each target is resolved against your document with the same selector `glotto generate` uses, so whether it selects an object, a list, or a scalar is *known* rather than inferred from the value — an `update` that replaces a scalar with an object is rewritten to the form that applies instead of failing at your next generate, an `update` on a list replaces it rather than silently appending, and a target that matches nothing (or matches nodes of differing kinds) is reported while you are migrating rather than breaking the build later. `move` and `copy` also translate with `--openapi`, as a `remove` plus an `update` carrying the value read from your document — the emitted action's `description` records that the value is a **snapshot** taken at migration time, so you know to re-check it if the upstream spec moves on. Dropped with a warning naming the reason: `move` and `copy` **without** `--openapi` (an Overlay action's `update` is a literal value, and Overlay cannot reference another node, so there is no value to write — re-run with the flag, or re-express them by hand); a `target` using JSONPath outside [the supported subset](/docs/transforms#apply_overlay); a non-object value whose target ends in the document root, a wildcard, an array index, a filter, or a descendant segment, leaving no single key to lift; and — with `--openapi` — an `append` onto something that is not a list, a `merge` onto a list (Overlay would append, but a merge could equally mean replace, so it is not guessed), and a `remove` targeting the document root. Every dropped command is reported individually at its own path, carrying your `reason` — none is silently lost, and none is emitted optimistically to fail later at generate time. |
| `x-stainless-naming` / `x-stainless-param` (OpenAPI extensions) | With `--openapi`, translated into `glotto.yml` `naming` / `parameter_naming`. Unsupported languages, parameter renames on an operation with no `operationId`, and malformed values are dropped with a warning; the report counts what was ingested. |
| `x-stainless-renameMap` (OpenAPI extension) | With `--openapi`, translated into `glotto.yml` `enum_naming` (the pair is inverted: Stainless keys by the new name, Glotto by the wire value). A non-mapping extension, an empty rename target, a non-scalar value, and a value the schema's `enum` doesn't declare are each dropped with a warning. |
| `x-stainless-model` / `x-stainless-model-skip` (OpenAPI extensions) | With `--openapi`, translated into `glotto.yml` [`models`](/docs/glotto-yml-model-shaping#models) — `name` (the emitted model name) and `inline` (emit no standalone type). Neither extension is per-language, so both map to the single-value form. A non-string or empty model name, and a non-boolean skip, are each dropped with a warning. |
| `x-stainless-nominal` (OpenAPI extension) | With `--openapi`, translated into `glotto.yml` [`enums`](/docs/glotto-yml-model-shaping#enums) — `nominal` (its own named type, or an alias over the primitive). Not per-language, so it maps to the single-value form; a non-boolean value is dropped with a warning. **The defaults are opposite** — Stainless aliases an enum unless told otherwise, Glotto gives it a named type — and only enums you actually annotated are translated. Glotto never synthesizes a directive you didn't write, so review the enums that carried no extension and add `nominal: false` where you want the Stainless shape. |
| `x-stainless-enum-deprecations` (OpenAPI extension) | With `--openapi`, translated into `glotto.yml` [`enums`](/docs/glotto-yml-model-shaping#enums) — `deprecated_values`, which annotates the member with your target language's own deprecation construct. The extension is an array **parallel to the schema's `enum`**, so position — not a key — selects the member; a `false` element is skipped. Dropped with a warning: a `true` element (Glotto has no message-less deprecation, so add a message to migrate it), a value that isn't an array, an element that is neither a string nor a boolean, an empty message, and an element past the end of the `enum` (or on a schema declaring none). An array shorter than the `enum` is fine — the members it doesn't reach simply aren't deprecated. |
| `x-stainless-soft-required` (OpenAPI extension) | With `--openapi`, translated into `glotto.yml` [`soft_required`](/docs/glotto-yml-model-shaping#soft_required), keyed by `operationId` — from a parameter, a `requestBody`, or a body-schema property (following a `$ref` into `components`). Only `true` translates; `false` is a no-op. A non-boolean value, a nameless parameter, and an annotation on an operation with no `operationId` are each dropped with a warning. |
| `x-stainless-const` (OpenAPI extension) | With `--openapi`, translated into `glotto.yml` [`auto_populate`](/docs/glotto-yml-model-shaping#auto_populate), keyed by `operationId` — from a parameter or a body-schema property. Only `true` translates; `false` is a no-op, since the block only ever REMOVES an input and there is no "un-const" to express. A non-boolean value, a nameless parameter, and an annotation on an operation with no `operationId` are each dropped with a warning. Whether a named position is actually eligible — its schema must permit exactly one value with a sendable wire form — is decided at generation time and reported there. |
| `x-stainless-terraform-configurability` / `x-stainless-collection-type` / `x-stainless-terraform-always-send` (OpenAPI extensions) | With the opt-in spec rewrite (plus `--openapi`), translated into a rewritten copy of your OpenAPI document as `x-glotto-terraform-configurability` / `x-glotto-collection-type` / `x-glotto-terraform-always-send`, value carried through verbatim — see [Bring your Terraform attribute shaping across](#bring-your-terraform-attribute-shaping-across). Glotto expresses per-attribute Terraform shaping as spec extensions rather than `glotto.yml` keys, so this is the one translation that writes a second output file rather than folding into the config. A value outside the closed set is reported and left unstamped; an `x-glotto-*` you already wrote is never overwritten. Without `--openapi-out`, unchanged: re-annotate by hand, or rely on the standard OpenAPI `readOnly` (→ a `Computed` attribute) and `uniqueItems` (→ a set rather than a list) where your spec already declares them. |
| `unspecified_endpoints` (top-level) | Translated to Glotto's target-agnostic [`exclude`](/docs/glotto-yml-api-surface#exclude) deny-list, **verbatim and in source order** — each entry carries across in the `"post /internal_endpoint"` spelling you already wrote, because `exclude` accepts that positional form alongside the `operationId` one. Nothing is resolved at migrate time, so an entry stays correct even if the operation is later renamed. A non-list value is dropped with a warning; a non-string or empty entry is dropped with a warning at its own index while its well-formed siblings still map. The block is emitted only when at least one entry maps — never an empty `exclude: []`. |
| `settings` | Partly translated: `license` → top-level `license`, and `detect_breaking_changes` → [`settings.detect_breaking_changes`](/docs/glotto-yml-project-settings#settings). `mock_server` is dropped with a pointer note — Glotto's mock is not a config toggle — and `per_endpoint_security` is dropped because Glotto delivers it with no toggle to set. Any other `settings.*` key is dropped with a warning naming it. |
| `code_owners` (top-level) | Translated onto Glotto's [`code_owners`](/docs/glotto-yml-project-settings#code_owners) block, **verbatim and in source order** — order is semantic on both sides, since a CODEOWNERS file resolves last-match-wins, so nothing is sorted or deduplicated. Owner syntax is deliberately *not* rewritten: which spelling is legal depends on the forge each target's repo lives on, which Glotto knows from `targets.<slug>.repo_provider` and the migration does not — so a GitHub handle carried into a GitLab-targeted config is reported there, at its pointer, with the provider named. Where Stainless emits a file for GitHub only, Glotto covers all four providers: GitHub and GitLab get a committed CODEOWNERS from `glotto generate`, and Bitbucket and Azure Repos are configured through their APIs. Dropped with a warning: a non-mapping block, a rule whose value is not a list, a non-string or empty owner (at its own index, while its siblings still map), and a rule that maps no owners at all. |
| `pagination` (top-level) | Dropped with a warning — Glotto declares pagination per-method via the `paginated` flag instead. |
| Any other top-level key | Dropped with a warning naming the key. |

## What carries over

Everything the wind-down puts at risk has a home — including the three capabilities
earlier revisions of this guide tracked as open gaps, each since shipped (tracked to
completion in the open):

- **Every Stainless SDK language and more** — TypeScript, Python, Go, Java, Kotlin, Ruby,
  C#, PHP, Terraform, plus React Native, Swift, Rust, Dart, and Elixir.
- **Docs** — an Astro site you own, with multi-language snippets, search, theming, and
  try-it ([guide](/docs/generated-docs-site)).
- **MCP** — both architectures, per-operation tools *and* **Code Mode** (`execute` +
  `search_docs`), with filters and OAuth ([guide](/docs/mcp-server)); the generated
  multi-mode server self-hosts anywhere Node or Docker runs, and managed hosting ships
  with the hosted platform.
- **Release flow** — release PRs on spec change across GitHub, GitLab, and Bitbucket, with
  [drift detection](/docs/drift-detection) keeping committed output honest.
- **Custom code preservation** — the `lib/` directory *and* patch preservation
  ([guide](/docs/custom-code)).
  If you hand-patched your Stainless SDKs, migration does not cost you those edits:
  hand-edits to generated files survive regeneration via a checksum-keyed three-way merge,
  with conflicts surfaced in the release PR rather than overwritten.
- **Registry publishing** — a released SDK ships to its package registry
  (npm, PyPI, crates.io, RubyGems, Maven Central, …) as part of
  [the release flow](/docs/multi-vcs-release).

And one thing the old pipeline never gave you: **the proof**. Every regeneration produces
a [verification report](/docs/verification-report) — pinned inputs, per-target drift and
integrity, compile and contract statuses — with a downloadable signed attestation.

## Next steps

- [Getting started](/docs/getting-started) — install the CLI and run the full loop.
- [The glotto.yml reference](/docs/glotto-yml) — every key your migrated config can use.
- [CLI reference](/docs/cli) — `migrate`, `validate`, `generate`, `drift-check`.
- [How Glotto compares](/docs/sdk-generation-comparison) — the idiomatic-output and
  cross-language parity story, versus the other generators.
