Skip to content Documentation index for agents (llms.txt)
Glotto Beta
Get started

Migrate from Speakeasy

Speakeasy open-sourced its generator in September 2026 and repositioned around AI governance. You keep the SDKs you already generated — but the company that maintained the pipeline behind them sells something else now. Glotto picks it up, and adds the part that is 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 delivered to CI, the release PR, and the console.

You can run this conversion yourself. glotto migrate speakeasy ships in the published CLI — unlike migrating off Fern, where the conversion has to read your API surface through the spec pipeline and stays on our side. Everything it needs is already in your git repo: Speakeasy keeps its config in your tree, not in a dashboard, which is the same practical advantage Fern has and Stainless does not.

What Glotto needs from you

Two files, at two levels — and the second level is the one that gets forgotten.

The root .speakeasy/workflow.yaml says what to generate and from where:

sources:
    Acme Pets API (Public):
        inputs:
            - location: ./openapi/petstore.yaml
        overlays:
            - location: ./overlays/names.yaml
        registry:
            location: registry.speakeasyapi.dev/acme/acme/petstore-public
    Acme Pets API (Internal):
        inputs:
            - location: registry.speakeasyapi.dev/acme/acme/petstore-internal

A .speakeasy/gen.yaml beside each generated SDK says what that SDK is called:

python:
    version: 3.4.1
    packageName: acme-petstore
    moduleName: acmepets

Your package name and module namespace live only in that second file. The conversion finds them by walking each target’s output path, so it reads the tree rather than the one file you point it at. If a target’s gen.yaml is missing or unreadable, you get GLOTTO_MIGRATE_SPEAKEASY_GEN_MISSING naming the exact path it looked for — the target is still migrated from the workflow alone, but its package identity is not, and a partial migration never passes itself off as a complete one.

How you get it

npm install -g @glotto/cli
glotto login
glotto migrate speakeasy

The defaults are the layout you already have: .speakeasy/workflow.yaml in, glotto.yml out. --in and --out move either end, --force overwrites an existing config, and --report also writes the report to a file.

If your workflow declares more than one source

It probably does — most real ones do. glotto.yml takes exactly one input document, so the conversion stops and names them:

GLOTTO_MIGRATE_SPEAKEASY_SOURCE_AMBIGUOUS

Nothing is written. This is the one place the tool refuses rather than choosing, and it refuses on purpose: picking a source silently would regenerate your SDKs from a document you did not select, and nothing would surface it until one of your consumers broke. Re-run naming the one you want — quoted, because real source ids contain spaces and parentheses:

glotto migrate speakeasy --source 'Acme Pets API (Public)'

The message tells you which of the two real shapes you have. Several genuinely different documents (an Azure spec and a GCP spec) are several Glotto projects — migrate each separately. One document under per-language overlay stacks is one API, and you pick the stack you want as the baseline.

If a source is backed by the Speakeasy registry

A registry.speakeasyapi.dev/... input resolves against the service you are leaving, so carrying it across would hand you a config that validates and then 404s in your CI. You get GLOTTO_MIGRATE_SPEAKEASY_DEAD_REGISTRY instead.

Unlike the multi-source case above, this one does not stop — the rest of your workflow still converts, and the conversion writes a glotto.yml with openapi.source: REPLACE_ME where the dead reference would have gone. Export the document to a local path, fill that in, and you are done.

What you get back

A glotto.yml, and a report that gives every key you wrote exactly one of four verdicts. The four are the whole point, and the third and fourth are where the value is:

Verdict What it means
Mapped Translated. The line names the glotto.yml key it became.
Already guaranteed You are not losing this — Glotto provides it unconditionally, so it needs no configuration. enableCustomCodeRegions becomes a three-way merge on every regeneration; forwardCompatibleEnumsByDefault is simply how Glotto emits enums.
Not honored An emission-shape knob Glotto decides itself (maintainOpenAPIOrder, responseFormat, multipartArrayFormat), or vendor bookkeeping (management, features) that records generation checksums rather than anything you chose. Most of a real per-language block is this.
Unmapped Genuinely no glotto.yml home, reported at its precise dotted path. This is the list that needs your attention, and it is short because the other three tiers are not in it.

A migration report that called a guarantee a loss would read as a list of things you are giving up, when it is the list of things you no longer have to configure. Every code has a row on the migration diagnostics reference.

Your publish credentials are dropped, loudly

Your workflow carries registry tokens. A credential is never written into your glotto.yml — not the value, not the key. The intent carries over — publish.pypi.token becomes publish: { pypi: true }, so your release pipeline can be rebuilt — and the credential itself is never written into a file you commit. You get GLOTTO_MIGRATE_SPEAKEASY_SECRET_DROPPED naming each one, because a token dropped silently is a release pipeline that fails later for no stated reason. Configure them as repository secrets for the release workflow instead.

Targets that are not SDK languages

Speakeasy lets a target be something other than an SDK language, and the three cases land in two different places.

terraform and cli become ordinary targets entries — Glotto generates a Terraform provider and a CLI as targets, under those same names. mcp-typescript does not: Glotto’s MCP server is its own top-level mcp block rather than a language target, so the conversion routes it there. Mapping that one by its Speakeasy name would put mcp-typescript under targets, where it is not a valid language, and you would find out at validation rather than here.

What you still fill in by hand

A Speakeasy workflow carries no company identity, so the conversion cannot invent one. The report ends with a placeholder count — two, on a typical project: your organization name and a contact address. Each is written as an obvious stand-in (REPLACE_ME, an example.invalid address) rather than a plausible guess, and each is listed in the report under Placeholders (needs review) rather than left for you to discover.

Your API surface is not one of them. Your workflow points at an OpenAPI document, so Glotto reads it and writes the resources block for you — operations grouped by their first tag, each keyed by its operationId:

resources:
  pets:
    methods:
      createPet: post /pets
      getPet: get /pets/{petId}
      listPets: get /pets

That key is the method name Glotto emits, taken from the same operationId your Speakeasy SDK derived its own name from — so for any operation your spec gives an operationId, the migration does not rename your surface on its own initiative. Renaming is available and deliberate, never something done to you.

An operation with no operationId has no such name to carry, so Glotto builds one from the route (get /pets/{petId} becomes get_pets_petId_) — and says so in the report, one line per operation, because that is a name we chose and yours may differ.

It is not a guarantee that every member name matches, and the report is where you check. Your gen.yaml may carry dated fixes flags and an sdkClassName, each of which moved names in the SDK you have today; the conversion reports every one it finds, under exactly that heading, because those are the reasons a migrated SDK’s public members could differ from the one it replaces. Pinning them is a separate step, not part of this conversion.

If the document cannot be read, you get a clearly marked placeholder resource instead and the report says which case you hit — a registry.speakeasyapi.dev reference, a URL (this command reads local files and will not fetch one), a path that does not resolve, a document that will not parse, or one that declares no operations. That one you do have to replace before you generate.

Overlays and merged documents are applied before the surface is read. Your workflow may stack overlays or merge further documents on top of the document your source names, and an OpenAPI Overlay can add or remove an operation. The conversion applies them first, in order and with the same functions Glotto runs when it generates, so the resources block describes the surface your SDK is generated from: an operation an overlay removes is not declared, and one an overlay adds keeps its own name. The report says so under resources.

When a transform cannot be applied locally, you get a warning naming it and the reason, and the block describes the primary document instead. That happens when an overlay is a URL (this command reads local files and will not fetch one), a file is missing or will not parse, or the document is Swagger 2.0 (Glotto converts it before applying anything). It also happens when an overlay action matches nothing in your document. The Overlay standard skips such an action, but Glotto refuses it, so fix or remove it before you generate. It also happens when an action’s JSONPath filter may read a server URL or an example. Glotto normalizes those values before it applies overlays, so this command cannot tell what the filter will select. Any other correction Glotto refuses, such as an invalid JSONPath or a merged document that changes nothing, is reported the same way, with its reason. When that verdict depends on a server URL or an example, the warning says it cannot be judged locally instead of predicting what glotto generate will do.

A transform that does apply can still get a separate warning. That happens when an overlay action or a merge anchor reaches an example, even through a wildcard, or a merged document writes a server list or an example, or is anchored on a server. Your resources block includes its effect, but glotto generate compares those normalized values and may refuse it, so run it to confirm.

Next steps

Fill in the placeholders the report names, then:

glotto generate

From there every regeneration is checked rather than trusted: drift detection, compile and contract verification, and a verification report on each one. That is the part that was never really the generator’s job, and it is why the second push — and every push after it — is the thing Glotto is actually for.