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

# Endpoint migration

Glotto **detects** breaking changes for you ([how](/docs/breaking-changes)).
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

```yaml
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:

```ts
// 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

```yaml
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:

```yaml
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`)

```yaml
    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`)

```yaml
# ── 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`)

```yaml
# `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.
```

**glotto generate**

**What Glotto emits — a real method, cloned from its target** (`typescript/src/resources/v1-records.ts` `V1RecordsResource.createRecord`)

```ts
  /**
   * 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:

```yaml
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:

```ts
// 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

- [`glotto.yml` reference](/docs/glotto-yml) — the full config surface.
- [Drift detection](/docs/drift-detection) — the gate that keeps committed output honest.
- [The verification report](/docs/verification-report) — what every regeneration proves.
