glotto.yml client behavior
Optional keys
client_settings
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 for how per-call and per-client overrides compose with it.
See Retries & timeouts for the retry block,
Authentication for auth, and Idempotency keys
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_prefixapplies to everybearer/oauth2scheme, so an API that frames all its bearer credentials asTokenneeds one line rather than an entry per scheme.auth.env_varapplies only to the schemes in your spec’s globalsecurityrequirement. A per-endpoint scheme is a different credential, so it is never given the default scheme’s environment variable — name its own underauth.schemesinstead.
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:
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
transforms:
- rename_schema: { from: InvoiceDTO, to: Invoice }
- dedupe_inline_objects: { threshold: 2 }
An ordered list of in-config OpenAPI rewrites. See the Transforms reference for every transform and its arguments.
naming & parameter_naming
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
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 (model members) or
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.
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 rather than letting you
discover it in the emitted SDK.
positional_params
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
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.