glotto.yml project settings
Project settings
diagnostics
diagnostics:
rules:
no-error-response: error # remap a lint rule's severity (off | warn | error)
mutation-no-idempotency-key: off # suppress a rule
max_warnings: 10 # release-gate: linting fails above this budget
Tunes spec linting without touching the rules engine: rules overrides per-rule severity (off
suppresses), and max_warnings is a release-gating threshold (lint exits non-zero once warnings
exceed it). Optional and additive. See the Diagnostics reference
for the full rule list and behavior.
settings
settings:
detect_breaking_changes: true
Stainless-parity toggles. detect_breaking_changes makes
breaking-change detection call out every breaking change at the top of
the preview comment on your pull request, rather than leaving it in a table cell. Optional and
additive.
code_owners
code_owners:
'*':
- '@acme/sdk-team'
'src/**':
- '@acme/core-team'
- '@octocat'
Declares who reviews changes to the repos Glotto manages. Optional and additive — set nothing and your output is byte-for-byte what it was.
Rules are emitted in the order you declare them, because a CODEOWNERS file is resolved
last-match-wins: the rule furthest down that matches a path is the one that owns it. So the block
above gives the SDK team everything and hands src/** to the core team and @octocat.
Owner syntax is per-provider, resolved from each target’s
repo_provider and defaulting to github:
| Provider | Owners it accepts |
|---|---|
github |
@login (letters, digits and single interior hyphens, ≤39 characters), @org/team-slug at exactly two segments, or an email address. |
gitlab |
The same, plus _ and . inside a segment, and @group/subgroup/… nested to any depth. |
bitbucket |
A workspace member’s handle, account id, uuid, or display name — resolved against your workspace when you apply it. |
azure-repos |
An identity’s display name, unique name, or email — resolved against your organization when you apply it. |
A GitHub team handle is not a GitLab one, and an owner that a forge cannot resolve makes it ignore
the whole line — the path is left unowned and nothing says so. That is why Glotto
rejects an owner the target’s provider could not resolve rather than emitting it
(GLOTTO_CONFIG_CODE_OWNERS).
Two of the four providers read no CODEOWNERS file at all. GitHub and GitLab receive
.github/CODEOWNERS and .gitlab/CODEOWNERS from glotto generate; Bitbucket configures a
repo-wide default-reviewer set and Azure Repos a required-reviewers branch policy, both through
their APIs — so for those two, Glotto applies the block by calling the provider rather than by
writing a file into your repository.
It authenticates with the credentials you connected the repository with; see Connect your VCS. Bitbucket’s default reviewers have no path scoping, so the result names which of your patterns had to be flattened, and any owner whose directory matched nothing, rather than reporting a plain success.
A target with a repo_path — an SDK living in a subtree of a shared repo — receives no
file either: GitHub and GitLab read CODEOWNERS only from the repository root, so one written
into the subtree would never be opened. Every one of these cases is reported by name, by
glotto generate, as
GLOTTO_CONFIG_CODE_OWNERS_NOT_APPLIED.
mcp
mcp:
package_name: "@acme/api-mcp" # emitted package identity (default: <org>-mcp; a valid npm name)
registry_name: io.github.acme/api # opt-in: emit a server.json for the official MCP registry
modes: [code, tools, dynamic] # MCP server modes to enable (omit for all three)
filters_enabled: true
search_docs: true
operations: # per-tool overrides (canonical <resource>_<method> keys)
pets_get: { description: "Fetch a single pet by id.", name: fetch_pet }
permissions: # method allow/block sets (convenience layer, NOT a security boundary)
allow_http_gets: true # allow every operation mapped to HTTP GET
allowed_methods: [pets\..*] # regexes over the qualified <resource>.<method> name
blocked_methods: [pets\.delete] # applied last — beats both allow keys
experimental:
async_tasks: false # EXPERIMENTAL: task-capable tools for long-running operations (default off)
Configuration for the generated multi-mode MCP server. See the MCP server guide.
permissions narrows which operations the emitted server can reach: patterns match the
fully-qualified <resource path>.<method> name (pets.photos.add) and are fully anchored, the
allow set is constrained only when allowed_methods or allow_http_gets is set, and
blocked_methods is subtracted last. Denied operations are never registered in Tools/Dynamic Mode,
and Code Mode’s execute refuses submitted code that references one before running it. It is a
convenience layer that keeps an agent in its lane, not a security boundary — the Code Mode half
is static analysis, which obfuscation defeats; use a scoped API token or the MCP Cloud gateway for
the real boundary. See Method permissions.
experimental.async_tasks opts the operations your spec marks long-running into the MCP
async tasks capability — see
Experimental: async tasks. It is experimental and
off by default — and currently withheld: the final 2026-07-28 MCP spec moved tasks to the
io.modelcontextprotocol/tasks extension, and until the emission is rebuilt for that shape,
enabling the flag over a long-running-capable API fails generation with an actionable error.
docs
docs:
theme: { primary: "#5b21b6" }
authoring: { format: mdx, base_path: ./docs }
deploy: { target: cloudflare, custom_domain: docs.acme.com }
ask_ai: { endpoint: https://ask.acme.com } # docs "Ask AI" widget
analytics: { posthog_key: phc_yourkey } # cookieless analytics (opt-in; PostHog or Plausible)
search: # rename and order the generated search facets
facets:
- { filter: method, label: HTTP method }
- { filter: resource, label: API resource }
i18n: { default_locale: en, locales: [en, de] } # site locales (routing + lang + chrome strings)
structured_data: {} # schema.org JSON-LD on every page (opt-in)
versions: [ { slug: v2, default: true, openapi: { source: ./v2.yaml } } ]
The generated docs site: theme, authoring, deploy target (cloudflare / vercel / netlify /
static / self-host), the Ask-AI widget, opt-in analytics, locales, structured data, and
multi-version docs.
search.facets controls the names and display order of the generated search filter panel. Each
entry’s filter is one of method, resource, language, or parameter; label is optional,
and the list order is the display order. A filter you omit keeps its default name and appears after
the configured filters. Omitting search preserves the default generated site unchanged.
structured_data is off unless you set it (structured_data: { enabled: false } turns it back off
explicitly). With it on, every generated page carries one schema.org JSON-LD block derived from your
spec and your configured site identity — an APIReference per operation with the same title,
description, and canonical URL its <head> already advertises, a SoftwareSourceCode per code
sample you show, breadcrumbs matching the page’s place in the navigation, and WebSite +
Organization on the home page. It is re-derived on every regeneration, so it cannot drift from
your spec the way hand-maintained markup does.
For the things your spec cannot know — your organization’s sameAs profiles, a HowTo on a guide,
a locale-prefixed route — add your own nodes:
docs:
structured_data:
extra_nodes: # appended to every page
- "@type": Organization
"@id": https://acme.com/#org
sameAs: ["https://github.com/acme"]
extra_nodes_by_route: # appended to one route only
/guides/quickstart:
- "@type": HowTo
name: Quickstart
A node is free-form: whatever keys you write are emitted as authored, inside the same single
JSON-LD document, escaped the same way the derived nodes are. What Glotto does check is the node’s
shape — that it is a non-empty object, that it carries a @type a consumer can dispatch on, and
that @id/@context hold the forms JSON-LD defines for them. A node that fails one of those, or
that claims an @id belonging to a node Glotto derived from your spec, is reported by
glotto generate (GLOTTO_CONFIG_DOCS_STRUCTURED_DATA) and left out; your
other nodes still emit. This is a structural check, not a vocabulary one — a misspelled @type is
emitted as you wrote it. The merge is additive — your nodes
follow the derived ones and cannot replace, reorder, or remove them, so the graph Glotto guarantees
stays the graph Glotto guarantees. Route keys are matched leniently (guides/quickstart,
/guides/quickstart/ and /guides/quickstart are the same route), and a route that isn’t a
generated page still gets its nodes, which is how you reach locale-prefixed routes. Set neither key
and your site is byte-identical to the derived-only output; set enabled: false and the whole
feature is off, injected nodes included.
deploy.custom_domain is the bare hostname — docs.acme.com, not a URL, and no path, port, or
wildcard — because it becomes both your canonical URLs’ authority and the domain handed to your
deploy provider (GLOTTO_CONFIG_DOCS_DEPLOY_DOMAIN). Each versions[].slug
becomes a /<slug>/ route, so it may only use characters a URL path keeps verbatim
(GLOTTO_CONFIG_DOCS_VERSIONS). Colours under
og_images.template.colors are painted by the card renderer rather than a browser, so hex,
comma-separated rgb()/rgba(), and CSS named colours work but hsl() and gradients do not
(GLOTTO_CONFIG_DOCS_OG_TEMPLATE). glotto generate reports each of these.
Each versions entry declares its snapshot with exactly one of openapi, asyncapi, or
graphql — the same three input keys the top level accepts, in the same shapes, so a GraphQL
snapshot carries its own operations document:
docs:
versions:
- slug: v1
graphql: { source: ./v1.graphql, operations: ./v1-operations.graphql }
- slug: v2
default: true
openapi: { source: ./v2.yaml }
Versions may mix input kinds — the case a migration produces — and each version’s reference pages
are built from its own snapshot. Only an openapi version co-serves an openapi.json (and so
contributes a service-desc link to the site’s API catalog); the others simply serve none.
Versioning is docs-only: your SDKs always come from the top-level source.
analytics activates the site’s built-in cookieless analytics against your own project —
PostHog by default (posthog_key is the publishable client key; optional posthog_host selects
EU cloud or self-hosted ingestion) or Plausible via provider: plausible + plausible_domain
(optional plausible_host for self-hosted). It counts page views plus three product events —
search queries, feedback votes, and playground calls — with no cookies, Do-Not-Track honored,
and nothing sent beyond each event’s named fields (never feedback comment text, parameter
values, or credentials). i18n declares the
site’s locales: default_locale sets the <html lang> and stays at the unprefixed routes,
additional locales get /<locale>/ route prefixes for your translated pages plus a generated
chrome string catalog to translate (src/lib/ui-strings.mjs).
license
license: MIT # or Apache-2.0
The permissive license stamped onto generated SDKs and emitted templates. Defaults to MIT.
release
release:
mode: auto # or manual
Controls the release flow for generated SDKs. mode is the only key this block accepts. Any
other is a configuration error reported against your glotto.yml before anything is generated, so
a file naming branches here fails at the door rather than being silently ignored.
Absent, releases behave as auto: Glotto opens the release pull request for you, and merging it
publishes. manual stops Glotto opening that pull request — you open it yourself from the branch
Glotto has already pushed. Publishing on merge is an auto-only behaviour: the release webhook
does not act on a repository whose project is in manual mode.
The branch names are not a glotto.yml key. To move one of them, use
targets.<lang>.release, which takes branch, baseline_branch and
base_branch per target:
targets:
typescript:
repo: acme/acme-typescript
release:
branch: sdk-next # the release PR's head — the merged result
baseline_branch: sdk-generated # the pristine generator output (the merge base)
base_branch: trunk # the release PR's base — released code
Unset, each defaults to Glotto’s
generated → next → main vocabulary.
A project-level set of the same three names exists, but it lives in the project’s
control-plane configuration (PUT /v1/projects/:id/config) rather than in your repository’s
glotto.yml. Setting them once for every target from glotto.yml is not supported yet.
One of those three carries a second meaning worth knowing before you set it: the project-level
base_branch is both the branch Glotto watches on your spec repository and the base of the
pull request opened on a split-layout target. Moving it changes which pushes trigger a release
and where the release lands — set it when a repository’s trunk is not called main, not to
retarget one of the two.
The staging repositories Glotto hosts are not affected by any of these names. Their three
branches are created with the repository and are always generated, next and main; every
build pushes its pristine output there whatever your targets are called.
readme
readme:
example_requests:
headline: listPets # operationId fronting the README quickstart
pagination: listInvoices # operationId fronting the Pagination section
streaming: streamEvents # operationId fronting the Streaming section
# or, with example-value overrides (wire parameter names):
# headline:
# operation: updatePet
# params: { petId: p_42, notify: true }
Configures the README.md shipped inside each generated SDK — every SDK language target ships
one. example_requests.headline selects the operation fronting the README’s usage example by
operationId — as a plain string, or as { operation, params } where params overrides the
example values rendered in the quickstart, keyed by wire parameter name (path and query parameters
and top-level request-body fields). example_requests.pagination and example_requests.streaming
select, in the same two forms, the operations fronting the README’s Pagination and
Streaming sections (the named operation must actually paginate / stream; a slot’s params
apply only to its own section’s example). Absent slots default to the first matching operation,
with example values sampled from the spec’s example/default fields. glotto generate warns
when an operation or a params key doesn’t match the spec, or when a pagination/streaming
operation isn’t of that kind. The README’s code blocks are the same generated snippets the docs
site renders, so they always match the current client surface.
hosted
hosted:
project: prj_9f2c81
Binds the repository to its Glotto control-plane project. Hosted mode resolves the
project as --project, then GLOTTO_PROJECT_ID, then this committed value — so a checkout with
only the API token in the environment can publish check-results to the hosted run. The project id
is not a secret; credentials (GLOTTO_API_TOKEN) and the API URL stay environment-only and have
no config key. Optional and additive, and ignored by generation entirely.