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

# glotto.yml client behavior

## Optional keys

### `client_settings`

```yaml
client_settings:
  default_timeout: 60s          # per-request overall timeout (default: 30s)
  retry:
    max_attempts: 3
    initial_delay: 200ms
    max_delay: 8s
    jitter: true
    max_elapsed: 90s            # optional overall wall-clock retry deadline
  auth:
    scheme: bearer
    env_var: ACME_API_KEY
    header_prefix: Token        # Authorization: Token <cred> (default: Bearer)
    schemes:                    # per-scheme overrides, keyed by OpenAPI scheme name
      AdminKey:
        env_var: ACME_ADMIN_KEY
      DpopAuth:
        header_prefix: DPoP     # Authorization: DPoP <cred> for this scheme only
  idempotency: true             # or { enabled: true, header: X-Idempotency-Key }
  telemetry_headers: true       # send X-Glotto-Retry-Count + X-Glotto-Timeout
```

SDK runtime defaults. `default_timeout` bounds each request attempt in every generated SDK —
a duration string (`60s`, `500ms`), defaulting to 30 seconds when unset; see
[Retries & timeouts](/docs/retries) for how per-call and per-client overrides compose with it.
See [Retries & timeouts](/docs/retries) for the `retry` block,
[Authentication](/docs/authentication) for `auth`, and [Idempotency keys](/docs/idempotency-key)
for `idempotency` (a boolean, or an object that customizes the injected header name).
`telemetry_headers` (off by default) makes every request carry `X-Glotto-Retry-Count` and
`X-Glotto-Timeout`, so your server can see the client's retry state and timeout budget.

`auth.scheme` / `auth.env_var` / `auth.header_prefix` describe the API's *single* security scheme.
When your spec declares several — a default token plus a per-endpoint admin key, say — `auth.schemes`
sets the same knobs per scheme, keyed by the scheme name as written in the OpenAPI
`securitySchemes`. `env_var` names the environment variable that scheme's credential falls back to,
and `header_prefix` sets its `Authorization` keyword; both are optional, and a scheme you don't list
keeps the defaults. `header_prefix` applies to `bearer` and `oauth2` schemes — the ones with an
`Authorization` keyword to vary — and is ignored on `apikey`, `basic`, and `custom`.

The two levels combine, and the two keys reach different schemes on purpose:

- **`auth.header_prefix`** applies to **every** `bearer`/`oauth2` scheme, so an API that frames all
  its bearer credentials as `Token` needs one line rather than an entry per scheme.
- **`auth.env_var`** applies only to the schemes in your spec's **global** `security` requirement. A
  per-endpoint scheme is a different credential, so it is never given the default scheme's
  environment variable — name its own under `auth.schemes` instead.

A per-scheme value always wins over the top-level one, so `header_prefix: Token` alongside
`schemes.DpopAuth.header_prefix: DPoP` gives `DpopAuth` the `DPoP` keyword and every other
bearer-style scheme `Token`.

### `default_request_options`

Use resource defaults for related calls and method defaults for an exception such as a slow export:

```yaml
resources:
  exports:
    default_request_options:
      headers:
        X-Export-Route: archive
      timeout: 30s
    methods:
      slowExport:
        endpoint: get /exports/{exportId}
        default_request_options:
          timeout: 120s
          max_retries: 0
```

Resources, nested subresources and object-form methods accept `headers`, `timeout` and
`max_retries`. A timeout must be a positive duration. The retry count must be a nonnegative integer;
zero disables retries. Header names are matched without regard to case, and duplicate names in
one declaration are rejected.

Each option resolves from the per-call override, then the method, nearest resource, client and
generated default. Headers merge across those levels; a more specific value replaces the same
header without discarding unrelated headers. A nested resource inherits values it does not set.
For SSE, NDJSON and binary downloads, the timeout bounds opening through the first byte or EOF;
it does not terminate an established stream. Cancellation and explicit close remain available.

These settings affect generated SDK requests. They do not alter the API specification or its
breaking-change classification. Each declaration must address an existing endpoint; a resource
with defaults must contain method declarations, directly or beneath a subresource.

### `transforms`

```yaml
transforms:
  - rename_schema: { from: InvoiceDTO, to: Invoice }
  - dedupe_inline_objects: { threshold: 2 }
```

An ordered list of in-config OpenAPI rewrites. See the [Transforms reference](/docs/transforms)
for every transform and its arguments.

### `naming` & `parameter_naming`

```yaml
naming:                  # per-language member renames: model → wire property → target → identifier
  Widget:
    public: { java: isPublic }
  User.address:          # dot-path: rename a NESTED inline-object property (User.address.zipCode)
    zipCode: { python: zip_code }
parameter_naming:        # per-language parameter renames: operation → wire param → target → identifier
  listWidgets:
    class: { python: cls }
```

Rename a model property (`naming`) or a method parameter (`parameter_naming`) for a specific
target language when the wire name collides with that language's keywords — the wire name is
preserved for serialization.

A `naming` key may be a **dot-path** (`Model.field[.field…]`) to address a property on a *nested*
inline object: the first segment is a named model and each later segment is an object field (or an
array-of-object field) that resolves to a deeper object. So `User.address` with property `zipCode`
renames `User.address.zipCode` without naming the generator's synthesized model. A path that doesn't
resolve against the current spec is ignored (like an unknown plain model), so a config can outlive a
schema change.

### `custom_casings`

```yaml
custom_casings:          # declared initialisms: lowercase word → how to render it
  api: API               # getApiKey → getAPIKey
  id: ID                 # widgetId  → widgetID
  url: URL
```

Declare the initialisms your API uses, so generated identifiers render them the way your team
writes them rather than the way a naive word-splitter would. Identifier words are lowercased before
matching, so the key is always the **lowercase** form — `api`, never `API`.

The rendering must be a pure **re-casing** of the key: only letter case may differ. Changing the
word itself is a rename, and belongs in [`naming`](#naming--parameter_naming) (model members) or
[`parameter_naming`](#naming--parameter_naming) (method parameters). A key that is not a lowercase
alphanumeric word, or a rendering that is not a re-casing of its key, is reported as
[`GLOTTO_CONFIG_CUSTOM_CASINGS`](/docs/diagnostics).

**It does not reach every identifier yet.** Casings are applied where the IR carries a per-language
identifier — model members, method parameters, and enum constants — and **not** to method names,
class and type names, or resource accessors. Declaring `{ api: API }` and still reading `getApiKey`
is that gap, not a mistake in your config; Glotto reports it as
[`GLOTTO_CONFIG_CASING_NOT_APPLIED`](/docs/diagnostics) rather than letting you
discover it in the emitted SDK.

### `positional_params`

```yaml
positional_params:       # per-language argument order: operation → target → wire param names
  getRepo:
    typescript: [repoId, orgId]   # getRepo(repoId, orgId, options?) instead of (orgId, repoId, …)
    go: [repoId]                  # PARTIAL: name the first, the rest keep their derived order
```

Override the order a method's positional **path** arguments are emitted in, for one target
language. Useful when your API reads more naturally in another order, or when you are migrating
from a hand-written SDK whose signature your users already call.

Name the parameters by their **wire** name (the `{placeholder}` in the path), not the emitted
identifier — so one entry means the same thing in every language, and it keeps working if you also
rename the parameter with `parameter_naming`.

The list is a **prefix**: any path parameter you do not name keeps its derived position behind the
ones you do, so naming just the first argument is a complete declaration.

It is **per target language** on purpose, because the languages genuinely differ — Ruby's arguments
are keyword arguments, Go leads with `ctx` and Elixir with `client`, and Swift labels every argument
at the call site. Reordering never changes the request: the URL, query string, and headers are
built from the wire names and are byte-identical whichever order you declare.

An entry naming something that is not one of that operation's path parameters — or naming one
twice — is reported and the whole entry is ignored, so the method keeps its derived order rather
than a half-applied one you never reviewed. Reordering the request **body** relative to the path
arguments is not supported yet.

### `enum_naming`

```yaml
enum_naming:             # per-enum-value renames: enum model → wire value → identifier
  StatusCode:
    "200": Ok            # one LOGICAL name, cased per language (Rust `Ok`, Java `OK`, Swift `ok`)
    "404": NotFound
  Kind:
    "in-progress": { java: RUNNING }   # or pin the identifier per target
```

Rename the constant an enum **value** is emitted as. The wire value is always preserved for
serialization — only the language identifier changes. A value key is the value's string form, so a
numeric `200` is written `"200"`.

Most specs need no entry here: values whose derived identifier would be illegal in a target
language — a leading digit (`2xx`, `200`), punctuation (`in-progress`, `n/a`), or a language
keyword — are **repaired automatically**, so generation always produces compiling code. Use
`enum_naming` when you want a *meaningful* name (`Ok`) rather than the derived one.

The bare-string form is one logical name that each language cases idiomatically; the object form
pins an exact identifier for specific targets. `elixir`, `typescript`, and `react_native` emit no
enum constant (they use string lists and literal unions), so entries for them have no effect.
