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

Endpoint migration

Glotto detects breaking changes for you (how). Endpoint migration is the other half: how to avoid one.

When you rename or re-version an endpoint, the generated SDK method changes with it — and every one of your users’ call sites stops compiling. Two glotto.yml blocks let you make that change without shipping a major version.

aliases — keep the old method name working

aliases:
  createRecord: upsertRecord

An alias keeps a method name in the SDK and routes it to the operation you name. The key is the name your users already call; the value is the operationId it should now resolve to.

Glotto materializes the alias as a real method cloned from its target, so it issues the target’s request — the new endpoint, not the removed one:

// Both exist. `createRecord` issues PUT /v1/records, exactly like upsertRecord.
await client.records.upsertRecord({ id: 'rec_1', name: 'Ada' });
await client.records.createRecord({ id: 'rec_1', name: 'Ada' });

Because the alias re-occupies the method the rename vacated, breaking-change detection reports no breaking change for it. The renamed operation still shows up — as a non-breaking addition.

Alias names are resolved against your spec, so an entry whose target doesn’t exist is ignored rather than failing the build. That is what lets one config describe both sides of a diff: when breaking-changes builds your previous spec, the new target isn’t there yet, no alias materializes, and the old operation is simply still present.

deprecated — tell callers where to go

deprecated:
  createRecord: Use upsertRecord instead.

A plain string is the message for every language. To vary it — method names differ across languages — use the object form, which takes a required default plus per-target overrides:

deprecated:
  createRecord:
    default: Use upsertRecord instead.
    python: Use upsert_record() instead.
    go: Use UpsertRecord instead.

default is required so every language always has message text; several targets have no message-less deprecation form.

Each SDK renders it in that language’s own construct, so your users get the warning from their own compiler or editor rather than from release notes:

Language Emitted
TypeScript, React Native @deprecated JSDoc
Python Deprecated: docstring line
Go // Deprecated: (the godoc convention)
Java @Deprecated + @deprecated javadoc
Kotlin @Deprecated("…")
C# [Obsolete("…")]
PHP @deprecated docblock
Ruby # @deprecated (YARD)
Rust #[deprecated(note = "…")]
Swift @available(*, deprecated, message: "…")
Dart @Deprecated('…')
Elixir @deprecated "…"

Deprecation is compile-time and documentation-time only — nothing is added to the request path, so a deprecated call costs your users nothing at runtime.

Putting them together

The two blocks are independent maps keyed the same way, so you can deprecate an alias — which is the usual migration shape: the old name keeps working and warns.

A rename that breaks nobody Nothing below is typed by hand. The two input panes are slices of a demo in this repo; the emitted pane is what one real generate run over those exact inputs produced — including the doc comment recording which method is canonical. Derived from examples/endpoint-migration/inputs — every byte below is sliced from that demo or from one real generate run over it.

Your spec — the operation after the rename

examples/endpoint-migration/inputs/api.v2.yaml paths → /v1/records → put
    put:
      operationId: upsertRecord
      summary: Create or update a record
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Record'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Record'

Your glotto.yml — the alias that reoccupies the vacated method

examples/endpoint-migration/inputs/glotto.yml aliases
# ── The whole feature, in two blocks ──────────────────────────────────────────────────────────
#
# `POST /v1/records` (`createRecord`) became `PUT /v1/records` (`upsertRecord`). Left alone, the
# generated SDKs lose `createRecord()` and every caller breaks.
#
# `aliases` keeps the old method name in the SDK, routed to the NEW operation: core-ir materializes
# `createRecord` as a real method cloned from `upsertRecord`, so it issues `PUT /v1/records` and
# every one of the 13 engines emits it with no alias-specific code. It is also what makes
# breaking-change detection report the rename as NON-breaking — the classifier keys operations by
# their resource-and-method path, and the alias re-occupies the one the rename vacated.
#
# The SECOND entry is the re-versioning shape (#3166). `POST /v1/exports` (`createExport`) became
# `POST /v2/exports` (`createExportV2`) — a different path prefix, so a different path-derived
# resource, and `/v1/exports` is not in the current spec at all. The string form cannot express
# that: it materializes the alias beside its target, under `v2`, leaving the `v1.exports.…` key
# the rename vacated still vacated — and the rename still breaking.
#
# The object form supplies the one fact the config alone can: `path` is where the superseded
# method was SERVED. Glotto derives the resource from it with the same static-segment rule it
# derives every other resource from, so the alias reoccupies exactly the key the rename vacated.
# `path` places the method; it never routes it — the alias still issues `POST /v2/exports`.
aliases:
  createRecord: upsertRecord
  createExport:
    target: createExportV2
    path: /v1/exports

Your glotto.yml — the message callers are told

examples/endpoint-migration/inputs/glotto.yml deprecated
# `deprecated` marks the old name so callers are told where to go, in each language's own
# construct (`@deprecated`, `@Deprecated`, `[Obsolete]`, `#[deprecated]`, `// Deprecated:`, …).
# A plain string is the message for every language; the object form takes a required `default`
# plus per-target overrides — used here because the Python SDK's method is `upsert_record`.
deprecated:
  createRecord:
    default: Use upsertRecord instead.
    python: Use upsert_record() instead.
  createExport: Use createExportV2 instead.

What Glotto emits — a real method, cloned from its target

typescript/src/resources/v1-records.ts V1RecordsResource.createRecord

  /**
   * Alias of `upsertRecord`.
   * @deprecated Use upsertRecord instead.
   */
  createRecord(params: RecordModel, options?: RequestOptions): Promise<RecordModel> {
    return this.core.request<RecordModel>('PUT', '/v1/records', { ...options, body: params, contentType: 'application/json' });
  }
generated-checksum 40eab644eedff65fc7e54714906e8f3d463bbc7540c0ad8021ba3c30f90cdd2a

Re-versioning — when the path moves too

The alias above is materialized in its target’s resource, which is right whenever the rename leaves the path alone. But Glotto derives the resource tree from your URLs, so a rename that also moves the path — /v1/exports/v2/exports — moves the resource with it. The SDK surface your users call (exports.createExport(...) under v1) is then somewhere your current spec no longer describes, and no amount of reading that spec can find it.

Say where it used to live, and the alias works exactly as before:

aliases:
  createExport:
    target: createExportV2
    path: /v1/exports

path is the path the superseded method was served at. Glotto derives its resource from that path with the same rule it uses for every other path in your spec, so the alias reoccupies exactly the surface the move vacated — and breaking-changes reports no breaking change, same as the in-place rename.

path places the method; it never routes it. The alias still issues the target’s request:

// Still under the v1 surface your users already call — and it POSTs /v2/exports.
await client.v1.exports.createExport({ format: 'csv' });

Both fields are required in this form. An alias with only a target is the plain string form written the long way, so Glotto rejects it rather than quietly treating it as one.

Limitations

  • Deprecating a model, field, or individual enum member is not supported yet; deprecated is keyed by operation.

See also