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

# glotto.yml API surface

## API surface

### `skip` & `only`

```yaml
skip:                    # exclude an operation from specific targets (by operationId)
  createWidget: [go, java]
only:                    # emit an operation ONLY for the listed targets
  internalPing: [typescript]
```

Per-operation target exclusion, keyed by `operationId`. An operationId may not appear in both.

`skip` and `only` are **per target language**. To withhold an endpoint from *everything* Glotto
generates — including your docs site and your MCP server — use [`exclude`](#exclude) instead.

### `exclude`

```yaml
exclude:                 # withhold an operation from EVERY generated artifact
  - createInternalAudit  # by operationId...
  - "post /internal/import"   # ...or by position (Stainless's spelling)
```

An entry addresses its operation either by `operationId` or by its **position** — `"<method> <path>"`,
the spelling Stainless's `unspecified_endpoints` uses. The two are interchangeable: excluding an
operation by position produces byte-identical output to excluding it by name. The verb is
case-insensitive; the path is compared exactly, written as the template your spec declares
(`/pets/{petId}`). Position is the only spelling available for an operation whose `operationId` your
spec omits, since the name Glotto synthesizes for it appears nowhere you can read.

The target-agnostic deny-list. Where `skip` removes an operation from the SDKs you name, `exclude`
removes it from every artifact in the run: all 13 SDKs, the Terraform provider, the CLI and graph
targets, the docs site (its reference pages, sitemap, `llms.txt`, search index **and** the
co-served `openapi.json`, across every documented version), and the generated MCP server — where
the operation is registered as no tool and gets no handler.

Reach for `exclude` rather than listing every target in `skip`: an `exclude` entry stays correct
when you add a target later, whereas an enumerated `skip` list silently re-exposes the endpoint
the day a new target joins.

Notes:

- An operation named here is treated as if your spec never declared it, so an [`aliases`](#aliases--deprecated)
  entry pointing at it does **not** materialize — an alias can't resurrect an excluded endpoint.
  To withhold an alias, remove its `aliases` entry.
- An operation may not be named by both `exclude` and `only` (contradictory) — and that holds
  however each side spells it, so `exclude: ["post /pets"]` alongside `only: { createPet: … }` is
  reported just as naming `createPet` on both sides is. Appearing in both `exclude` and `skip` is
  fine — `exclude` simply subsumes the narrower entry.
- An entry naming an `operationId` your spec doesn't carry is ignored, so a config can outlive a
  spec change.
- `exclude` is a deny-list, not an allow-list: operations you don't name keep shipping. A new
  endpoint added to your spec is published until you exclude it.

Migrating from Stainless? This is the counterpart to `unspecified_endpoints`.

### `client_methods`

```yaml
client_methods:
  - getStatus        # client.getStatus() instead of client.system.getStatus()
  - ping
```

A list of `operationId`s to hoist onto the **client root** so they hang directly off the client
(e.g. `client.getStatus()`), rather than under a resource. Handy for health/status endpoints that
don't belong to a resource. Absent → no client-level methods.

### `aliases` & `deprecated`

```yaml
aliases:
  createRecord: upsertRecord        # keep the old method, routed to the new operation
  createExport:                     # …and when the PATH moved too, say where it used to be
    target: createExportV2
    path: /v1/exports

deprecated:
  createRecord:                     # a plain string works too: `createRecord: Use upsertRecord.`
    default: Use upsertRecord instead.
    python: Use upsert_record() instead.
```

`aliases` maps a method name you want to keep exposing → the `operationId` it should resolve to,
so renaming or re-versioning an endpoint doesn't break your users' call sites — and
Breaking-change detection stops reporting the rename as breaking. A plain string is enough when the
rename leaves the path alone. When the path moves — and with it the resource Glotto derives from it
— use the `{ target, path }` form, where `path` is where the **superseded** method was served; it
places the method on the surface your users already call, and never changes what it requests. Both
of its fields are required.

`deprecated` maps an `operationId` → the migration message, rendered in each language's own
construct (`@deprecated`, `@Deprecated`, `[Obsolete]`, `#[deprecated]`, `// Deprecated:`, …). The
object form's `default` is required; the other keys are per-target overrides. Both absent →
unchanged output.

See [Endpoint migration](/docs/endpoint-migration) for the full workflow.

### `streaming`

```yaml
streaming:
  on_event:
    - { data: "[DONE]", action: done }      # terminate the stream on this sentinel
    - { event_type: error, action: fatal_error }
    - { fallthrough: true, action: skip }   # drop anything else unrecognized, keep going
```

Termination / error / tolerance handlers for SSE & NDJSON stream iterators. Each rule has exactly
one matcher — `data` (the event payload equals this sentinel), `event_type` (the SSE `event:` name;
`null` matches untyped events), or `fallthrough: true` (catch-all) — and an `action`: `break`
(stop), `done` (clean end), `fatal_error` (raise), or `skip` (drop the matched event and continue).

`skip` is the opt-in counterpart to the strict-by-default decode: because rules match the **raw**
payload before any JSON parse, a `skip`-matched frame is dropped *before* it would be decoded, so a
`fallthrough: skip` (or a targeted `data: skip`) lets you tolerate benign non-JSON noise — keepalive
/ control frames, vendor sentinels — without the stream surfacing a parse error. With no `skip` rule,
an unrecognized non-JSON frame still surfaces the parse error (the strict default is unchanged).

```yaml
streaming:
  dual_mode:
    createChatCompletion:                      # operationId
      param_discriminator: stream              # required — the request field selecting the mode
      stream_event_model: ChatCompletionChunk  # optional — the per-event model
      params_type_name: ChatCompletionParams   # optional — names the shared params model
      method_suffix: streaming                 # optional — default "streaming"
```

`dual_mode` handles the **one endpoint, two modes** shape every major AI API uses: a request field
selects between a streamed sequence and a single buffered body, and the two return different types.
Glotto emits **two methods** from the one operation — `createChatCompletion` (buffered) and
`createChatCompletionStreaming` — removes the discriminator from both signatures, and pins the right
value on the wire for each. Optional/additive: with no `dual_mode` block, output is unchanged.

The per-event type is read from the spec when the endpoint documents a streaming media type
alongside `application/json`; otherwise name it with `stream_event_model`. An entry that can't be
applied is reported as `GLOTTO_CONFIG_DUAL_MODE_REFUSED` rather than silently ignored. See
[Streaming → Dual-mode endpoints](/docs/streaming#dual-mode-endpoints-stream-true).

### `query_settings`

```yaml
query_settings:
  array_format: comma    # or repeat (default)
```

How an **array-typed query parameter** is serialized onto the request URL: `repeat`
(`?tags=a&tags=b`, the OpenAPI `form`/`explode` default) or `comma` (`?tags=a,b`). A single,
client-wide policy; defaults to `repeat`.
